数据结构 堆排序

  堆排序是一种树形选择排序方法。在排序过程中,将L[1...n]看成是一棵完全二叉树的顺序存储结构,通过完全二叉树的双亲结点(parent)和孩子结点之间的内在关系,在当前无序区中选择最大(或最小)的元素。

  堆的定义:n个关键字序列L[1...n]称为堆,当且仅当该序列满足:

  1 L[i]<=L[2i]且L[i]<=L[2i+1]

  2 L[i]>=L[2i]且L[i]>=L[2i+1]

  (1<=i<=|_n/2_|,后者表示小于或等于n/2的最大整数)

  满足第一种情况的堆称为小顶堆,满足第二种情况的堆称为大顶堆。在小顶堆中,对于任意结点,它是作为根结点的子树中最小元素,根结点是最小元素。大顶堆与此相反,根结点是最大元素。

  构造初始堆:n个结点的完全二叉树,最后一个结点是第|_n/2_|个结点的孩子结点。对某个结点为根的子树进行筛选(对于大顶堆,如果根结点值小于孩子结点较大者,则交换),如果破坏了下一级的堆,则向后筛选,直到以该结点为根的子树构成堆。向前依次对各结点(|_n/2_|~1)重复上述过程。

  举例(自下向上逐步调整为大顶堆):

  

  

  

  Java代码:

1 // 构造初始大顶堆
2 private void buildMaxHeap(int[] arr, int length) {
3     // 反复调整堆
4     for (int i = length / 2; i >= 1; i--) {
5         adjustDown(arr, i, length);
6     }
7

  

 1 // 向下调整堆
 2 private void adjustDown(int[] arr, int parent, int length) {
 3     // 暂存双亲结点值
 4     int temp = arr[parent]; 
 5 
 6     // 沿着较大的孩子结点向下筛选
 7     for (int child = parent << 1; child <= length; child <<= 1) {
 8         // 如果存在右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点的下标
 9         if (child + 1 <= length && arr[child + 1] > arr[child]) {
10             child++;
11         }
12 
13         // 如果双亲结点值大于孩子结点,则结束筛选
14         if (temp >= arr[child]) {
15             break;
16         }
17 
18         // 把孩子结点的值赋给双亲结点
19         arr[parent] = arr[child];
20         // 把孩子结点的下标赋给双亲结点
21         parent = child;
22     }
23     
24     // 把双亲结点值放在最终位置上
25     arr[parent] = temp;
26 }

 

  排序堆:交换堆顶元素和堆底元素(堆的最后一个元素),原来的堆顶元素不再属于堆,从根结点开始向下调整,把剩余的元素重新建成堆。重复上述过程,直到堆中只剩一个元素。

  举例:

  

  Java代码:

 1 // 堆排序
 2 public void heapSort(int[] arr, int length) {
 3     // 构造初始大顶堆
 4     buildMaxHeap(arr, length);
 5     
 6     // length-1次趟排序堆
 7     for (int i = length; i > 1; i--) {
 8         // 交换堆顶元素和堆底元素
 9         int temp = arr[i];
10         arr[i] = arr[1];
11         arr[1] = temp;
12 
13         // 把剩余的i-1个元素调整成大顶堆
14         adjustDown(arr, 1, i - 1);
15         System.out.format("第%d趟: ", length - i);
16         print(arr, 1, length);
17     }
18 }

  完整的Java代码:

 1 public class HeapSort {
 2     // 构造初始大顶堆
 3     private void buildMaxHeap(int[] arr, int length) {
 4         // 反复调整堆
 5         for (int i = length / 2; i >= 1; i--) {
 6             adjustDown(arr, i, length);
 7         }
 8     }
 9     
10     // 向下调整堆
11     private void adjustDown(int[] arr, int parent, int length) {
12         // 暂存双亲结点值
13         int temp = arr[parent]; 
14 
15         // 沿着较大的孩子结点向下筛选
16         for (int child = parent << 1; child <= length; child <<= 1) {
17             // 如果存在右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点的下标
18             if (child + 1 <= length && arr[child + 1] > arr[child]) {
19                 child++;
20             }
21 
22             // 如果双亲结点值大于孩子结点,则结束筛选
23             if (temp >= arr[child]) {
24                 break;
25             }
26 
27             // 把孩子结点的值赋给双亲结点
28             arr[parent] = arr[child];
29             // 把孩子结点的下标赋给双亲结点
30             parent = child;
31         }
32         
33         // 把双亲结点值放在最终位置上
34         arr[parent] = temp;
35     }
36     
37     // 堆排序
38     public void heapSort(int[] arr, int length) {
39         // 构造初始大顶堆
40         buildMaxHeap(arr, length);
41         
42         // length-1次趟排序堆
43         for (int i = length; i > 1; i--) {
44             // 交换堆顶元素和堆底元素
45             int temp = arr[i];
46             arr[i] = arr[1];
47             arr[1] = temp;
48 
49             // 把剩余的i-1个元素调整成大顶堆
50             adjustDown(arr, 1, i - 1);
51             System.out.format("第%d趟: ", length - i);
52             print(arr, 1, length);
53         }
54     }
55 
56     // 打印
57     public void print(int[] arr, int begin, int end) {
58         for (int i = begin; i <= end; i++) {
59             System.out.print(arr[i] + " ");
60         }
61         
62         System.out.println();
63     }
64 
65     public static void main(String[] args) {
66         // 待排序数组
67         int[] arr = {0, 57, 40, 38, 11, 13, 34, 48, 75, 6, 19, 9, 7};
68 
69         // 调用快速排序方法
70         HeapSort heap = new HeapSort();
71         System.out.print("排序前: ");
72         int length = 12;
73         heap.print(arr, 1, length);
74         heap.heapSort(arr, length);
75         System.out.print("排序后: ");
76         heap.print(arr, 1, length);
77     }
78 }

  输出结果:

 1 排序前: 57 40 38 11 13 34 48 75 6 19 9 7 
 2 第0趟: 57 40 48 11 19 34 38 7 6 13 9 75 
 3 第1趟: 48 40 38 11 19 34 9 7 6 13 57 75 
 4 第2趟: 40 19 38 11 13 34 9 7 6 48 57 75 
 5 第3趟: 38 19 34 11 13 6 9 7 40 48 57 75 
 6 第4趟: 34 19 9 11 13 6 7 38 40 48 57 75 
 7 第5趟: 19 13 9 11 7 6 34 38 40 48 57 75 
 8 第6趟: 13 11 9 6 7 19 34 38 40 48 57 75 
 9 第7趟: 11 7 9 6 13 19 34 38 40 48 57 75 
10 第8趟: 9 7 6 11 13 19 34 38 40 48 57 75 
11 第9趟: 7 6 9 11 13 19 34 38 40 48 57 75 
12 第10趟: 6 7 9 11 13 19 34 38 40 48 57 75 
13 排序后: 6 7 9 11 13 19 34 38 40 48 57 75 

 

  堆排序性能分析:

  空间复杂度:仅使用了常数个辅助单元,所以空间复杂度是O(1)。

  时间复杂度:建堆时间复杂度是O(n),之后执行n-1次向下调整操作,每次调整的时间复杂度是O(h),所以在最好、最坏和平均情况下,堆排序的时间复杂度是O(nlog2n)。

  稳定性:筛选后可能把后面的相同元素调整到前面,所以堆排序不稳定。例如,表L={1,2,2},构造初始堆后L={2,1,2},最终排序结果是L={1,2,2},相同元素的相对次序发生了变化。

 

  参考资料

  《2017年数据结构联考复习指导》P296-298

  数据结构示例——堆排序过程

原文地址:https://www.cnblogs.com/WJQ2017/p/8446265.html