常见的排序算法(七):堆排序

  堆排序英语:Heapsort)是指利用这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质子节点的键值总是小于(或者大于)它的父节点

  堆是通过一维数组来实现的。在数组起始位置为0(根节点)的情形中:

  • 父节点i的左子节点在位置(2i+1)

  • 父节点i的右子节点在位置(2i+2)

  • 子节点i的父节点在位置floor((i-1)/2)

   若以升序排序说明,把数组转换成最大堆积(Max-Heap Heap),这是一种满足最大堆积性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。

  重复从最大堆积取出数值最大的结点,把根结点最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆积维持最大堆积性质。

  通俗来说就是把数组维持成一个最大堆,最大堆根节点最大的数值,然后与数组最后一个元素交换,并把最后那个位置移除堆,剩下的堆因为交换过去的元素破坏最大堆的性质,所以要维持最大堆的性质,再继续把最大的根节点与堆数组的最后一个交换,重复值堆中只有两个元素,最后两个元素比较后叫完成了排序。

堆排序算法如下:

  维持最大堆的函数

 1     /**
 2      * 调整索引为 index 处的数据,使其符合堆的特性。
 3      *
 4      * @param index 需要堆化处理的数据的索引
 5      * @param len 未排序的堆(数组)的长度
 6      */
 7     private void maxHeapify(int index, int len) {
 8         int li = (index << 1) + 1;  // 左子节点索引
 9         int ri = li + 1;            // 右子节点索引
10         int cMax = li;              // 子节点值最大索引,默认左子节点。
11         if(li > len)                // 左子节点索引超出计算范围,直接返回。
12             return;
13         if(ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。
14             cMax = ri;
15         if(arr[cMax] > arr[index]) {
16             swap(cMax, index);      // 如果父节点被子节点调换,
17             maxHeapify(cMax, len);  // 则需要继续判断换下后的父节点是否符合堆的特性。
18         }
19     }

堆调整的时间复杂度

  从堆调整的代码可以看到是当前节点其值较大的子节点(子节点比较一次)比较,交换一次。父节点与哪一个子节点进行交换,就对该子节点递归进行此操作,设对调整的时间复杂度为T(k)(k为该层节点到叶节点的距离),那么有
  T(k)=T(k-1)+3, k∈[2,h]
  T(1)=3
  迭代法计算结果为:
    T(h)=3h=3 floor(log n)
  所以堆调整的时间复杂度是O(log n) 。

建堆的时间复杂度:

  n个节点的堆,树高度是h=floor(log n)。对深度为于 h-1 层的节点,比较2次,交换1次,这一层最多有2^(h-1)个节点,总共操作次数最多为3*2^(h-1));对深度为h-2层的节点,总共有2^(h-2)个,每个节点最多比较4次,交换2次,所以操作次数最多为3*2^(h-2))……
  以此类推,从最后一个父节点到根结点进行堆调整的总共操作次数为O(n),建堆时间复杂度为O(n)。

  空间复杂度O(1)。

 

  堆排序入口:

 1     /**
 2      * 堆排序的主要入口方法,共两步。
 3      */
 4     public void sort() {
 5         /*
 6          *  第一步:将数组堆化
 7          *  beginIndex = 第一个非叶子节点。
 8          *  从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
 9          *  叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
10          */
11         int len = arr.length - 1;
12         int beginIndex = (arr.length >> 1) - 1;
13         for(int i = beginIndex; i>=0; i--) {
14             maxHeapify(i, len);
15         }
16         /*
17          * 第二步:对堆化数据排序
18          * 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
19          * 然后从新整理被换到根节点的末尾元素(比较小),使其符合堆的特性。
20          * 直至未排序的堆长度为 0。
21          */
22         for(int i = len; i > 0; i--) {
23             swap(0, i);
24             maxHeapify(0, i-1);
25         }
26     }

  测试代码:

1     public static void main(String[] args) {
2         int[] arr = new int[] {3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6};
3         new HeapSort(arr).sort();
4         System.out.println(Arrays.toString(arr));
5     }
posted @ 2019-08-18 22:19  賣贾笔的小男孩  阅读(364)  评论(0编辑  收藏  举报