数据结构之堆

良辰

共 1798字,需浏览 4分钟

 ·

2022-05-04 16:32

堆是一种特殊的数据结构,通常可以看作是一颗完全二叉树的数组对象。

堆的特性

  1. 它是完全二叉树,除树的最后一层节点不需要是满的,其他的每一层从左到右都是满的,如果最后一层不是满的,则要求左满右不满。


    ee810268ffebeff6a62fffa12c455550.webp

  2. 它通常使用数组来实现

    具体方法是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子节点的子结点分别在位置4、5、6和7,以此类推。

    f87e53e587e9f67afa6550e59338f2ec.webp

    如果一个结点的位置是k,则它父节点的位置为[k/2],而它两个子结点的位置分别为2k和2k+1。

  3. 每个结点都大于它的两个子结点

堆的API设计

02286a253c669428516313beb488106a.webp

堆的实现

insert插入方法的实现

堆插入元素图解(上浮算法):

93e4cd606425ee486d144534e8d4ae5e.webp

112edef228e5b1f1fcf747442d768c4d.webp

5ecd6e9066e44123ae88d8d81fc47b57.webp

f9e070fa8580685f8fb5cd151e203e58.webp

所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它父节点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。

删除方法的实现

删除堆元素图解(下沉算法):

985958b86b333eaac55e45661d480ec2.webp

31011722e77c8704b86b29e303b48e4d.webp

d45a45243a8ca8d745c52060308455d9.webp

0ef8f2450e6dd6a97c534380bbe9d0c5.webp

beb87fa557411feecae72902a12ea247.webp

所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断拿着当前节点a[k]和它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。

堆的代码实现

public class Heap<T extends Comparable<T>> {    private T[] items;    /**     * 堆大小,索引0处不存元素,从索引1开始     */    private int size;    /**     * 构造函数     *     * @param capacity     */    public Heap(int capacity) {        items = (T[]) new Comparable[capacity + 1];        size = 0;    }    /**     * 向堆中插入一个元素     *     * @param item     */    public void insert(T item) {        //向数组添加一个元素        items[++size] = item;        //使用上浮算法,使得元素处于一个正确位置        swim(size);    }
/** * 删除堆中最大元素 * * @return */ public T delMax() { //根据堆的特性,堆中最大的值是根结点的值 T max = items[1]; //先将堆中最后一个结点和根结点交换位置 exch(1,size); //将最后一个结点置为null,元素个数-1 items[size--] = null; //使用下沉算法使得更新后的根结点处在正确的位置 sink(1); return max;    }     /** * 使用上浮算法,使得索引k处的值处于一个正确的位置 * @param k */ private void swim(int k){ while (k > 1){ if(less(k/2,k)){ exch(k/2,k); k = k/2; }else { break; } } }
/** * 使用下沉算法,使得索引k处的值处于一个正确的位置 * @param k */ private void sink(int k){ while (2*k <= size){ //找到两个子结点较大的那个的索引 int max = 2*k; if(2*k+1 < size){ if(less(2*k,2*k+1)){ max = 2*k + 1; } } //比较当前索引处的值和较大结点处的值,如果当前索引处的值小于较大结点处的值,则交换两处的值 if(!less(k,max)){ break; } exch(k,max); k = max; } } /** * 比较索引i处和索引j处的值大小,i处小于j处则返回true * * @param i * @param j * @return */ private boolean less(int i, int j) { return items[i].compareTo(items[j]) < 0; }
private void exch(int i, int j) { T temp = items[i]; items[i] = items[j]; items[j] = temp; }
@Override public String toString() { return "Heap{" + "items=" + Arrays.toString(items) + ", size=" + size + '}'; }}
//测试代码public class HeapTest { public static void main(String[] args) { Heap heap = new Heap<>(6); heap.insert("C"); heap.insert("B"); heap.insert("A"); heap.insert("D"); heap.insert("F");        heap.insert("E"); System.out.println(heap); String result; while ((result=heap.delMax()) != null){ System.out.println(result); } }}

堆排序

给定一个数组:

String[] arr = {"S","O","R","T","E","X","A","M","P","L","E"};

请对数组中的字符从小到大排序。

API设计:

类名

HeapSort>

成员方法

//对source数组进行从小到大排序
public static void sort(Comparable[] source);
//根据原数组创建heap堆
private static void createHeap(Comparable[] source,Comparable[] heap);
//比较堆中索引i处和索引j处的值,小于返回true
private static boolean less(Comparable[] heap,int i,int j);
//交换heap堆中i索引和j索引处的值
private static void exchange(Comparable[] heap,int i,int j);
//在堆中,对target处的元素进行下沉,范围0-range
private static void sink(Comparable[] heap,int target,int range);

实现步骤

  1. 构造堆heap

    1. 将原数组复制给一个新数组

    2. 对数组中元素进行下沉,让它有序,符合堆的定义

  2. 根据堆的特性,根结点的值最大,交换根结点和最大索引处的值,将堆分为两部分,未排序的数组(0-length-2),和已排序的数组(length-1)然后对根节点在未排序数组中进行下沉

  3. 继续执行步骤2,直到最后一个元素

  4. 将堆heap的元素复制给原数组

代码实现

public class HeapSort<T extends Comparable<T>> {

   //对source数组进行从小到大排序
   public static void sort(Comparable[] source) {
       Comparable[] heap = new Comparable[source.length+1];
       createHeap(source,heap);
       //记录未排序最大索引
       int N = heap.length -1;
       while (N!=1){
           //交换根结点和最后一个未排序索引处的值
           exchange(heap,1,N);
           N--;
           //对交换过的根结点进行下沉,范围除去已排序的索引
           sink(heap,1,N);
      }
       //把heap中的数据复制回source数组
       System.arraycopy(heap,1,source,0,source.length);
  }

   //根据原数组创建heap堆
   private static void createHeap(Comparable[] source, Comparable[] heap) {
       //复制原数组到heap
       System.arraycopy(source,0,heap,1,source.length);

       //对堆中的元素进行下沉,(从长度一半开始,往1处扫描)
       for (int i = heap.length/2; i > 0; i--) {
           sink(heap,i, heap.length-1);
      }
  }

   //比较堆中索引i处和索引j处的值,小于返回true
   private static boolean less(Comparable[] heap, int i, int j) {
       return heap[i].compareTo(heap[j]) < 0;
  }

   //交换heap堆中i索引和j索引处的值
   private static void exchange(Comparable[] heap, int i, int j) {
       Comparable temp = heap[i];
       heap[i] = heap[j];
       heap[j] = temp;
  }

   //在堆中,对target处的元素进行下沉,范围0-range
   private static void sink(Comparable[] heap, int target, int range) {
       while (2*target <= range){
           //找出当前结点较大子结点
           int max = 2*target;
           if(2*target+1 <= range){
               if (less(heap,2*target,2*target+1)){
                   max = 2*target+1;
              }
          }
           //比较当前结点和较大子结点的值
           if (!less(heap, target, max)){
               break;
          }
           exchange(heap,target,max);
           target = max;
      }
  }
}

思考:

  1. 上面实现的也叫最大堆,堆中第一个元素总是最大的,还有一种堆叫最小堆,即堆中第一个元素总是最小的,又应该如何实现?

  2. 上面的实现都是固定容量大小的,如果需要实现可变容量又应该如何实现?


你知道的越多,不知道的就越多,关注我,我们一起进步。如果觉得文章还可以,请给帮我点个赞,谢谢~我们下期见。


浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报