堆排序

 

1.     基本概念

堆,分为大顶堆(大堆)和小顶堆(小堆),是顺序存储的完全二叉树,并且满足以下特性之一:

(1)    任意非终端结点关键字不小于左右子结点(大堆)

ki >= k2i+1并且ki>=k2i+2 其中,0 <= i <= (n-1)/2,n是数组元素个数

(2)    任意非终端结点关键字不大于左右子结点(小堆)

ki <= k2i+1并且ki<=k2i+2 其中,0 <= i <= (n-1)/2,n是数组元素个数

 堆排序

 

调整(也有叫筛选):

从当前结点(要求是非终端结点)开始,

对于大堆,要求当前结点关键字不小于子结点,如不符合,则将最大的子结点与当前结点交换。循环迭代交换后的子树,确保所有子树都符合大堆特性。

堆排序

 

小堆调整过程类似。

 

2.     基本思想

堆排序就是利用构建堆和输出堆顶元素的过程,不断对堆进行调整以保证当前结点及其孩子结点满足堆特性,从而达到对初始数组元素进行排序的目的。

大堆通常对应升序序列,小堆通常对应降序排列。

 

核心步骤:

1)      构建堆(大堆/小堆)

从最后一个非终端结点开始,向前进行调整,保证当前结点及其子树符合堆特性;

2)      输出有序序列

交换堆顶与末尾叶子结点,堆顶输出到数组的有序序列末尾,而不参与堆的调整。从交换后的堆顶开始调整,以确保当前结点及其子树符合堆特性。

 

3.     实例

下面举个例子,利用小堆进行降序排列。

初始序列

49

38

65

97

76

13

27

49‘

位置

0

1

2

3

4

5

6

7

说明:

  1. 符号’表示第二个相同元素

 

3.1.  构建堆

1)      初始序列对应初始堆

堆排序

 

从最后一个非叶子结点开始,向前进行调整,确保符合特性

最后一个非叶子结点位置:(n-1) / 2 = 3, n=8

总共调整次数:(n-1)/2 +1 = 4

 

第1次调整:选择最后一个非叶子结点元素为97(位置3)为当前父结点,与其子结点进行比较,选择最小的结点作为当前父结点。

 

第1次调整后序列

49

38

65

49’

76

13

27

97

位置

0

1

2

3

4

5

6

7

 

 堆排序

 

第2次调整:选择上一次结点的前一个结点65(位置2)为当前结点进行调整。

第2次调整后序列

49

38

13

49’

76

65

27

97

位置

0

1

2

3

4

5

6

7

堆排序

 

第3次调整:选择上一次结点的前一个结点38(位置1)为当前结点进行调整。

第3次调整后序列

49

38

13

49’

76

65

27

97

位置

0

1

2

3

4

5

6

7

 

堆排序

 

第4次调整:选择上一次结点的前一个结点49(位置0)为当前结点进行调整。

第4次调整后序列

13

38

27

49’

76

65

49

97

位置

0

1

2

3

4

5

6

7

 堆排序

堆排序

 

3.2.  输出堆顶元素

将已经构建好的小堆,输出堆顶元素,和末尾元素交换,相当于堆顶移动到数组末尾形成有序序列,未排序元素移动到堆顶。从新的堆顶开始进行调整,直到堆重新符合小堆特性。

 

交换堆顶和末尾(堆的末尾,不包括已经排好序的部分),并将交换后的堆末尾作为有序序列的一部分,而不再属于堆。 

 堆排序

 

一次交换后,发现97新的位置比子结点大,需要继续调整。

 堆排序

 

..

这样,不断输出所有堆顶到数组末尾,最终可以得到

有序序列

97

76

65

49

49’

38

27

13

位置

0

1

2

3

4

5

6

7

 

 

4.     实现代码

4.1.  核心算法代码

 1 /**
 2  * 小堆 <=> 升序排列
 3  * 从最后一个非叶子结点开始, 向前进行调整
 4  * @param a 待排序序列(数组)
 5  * @param n 待排序元素个数
 6  */
 7 void HeapSort(int a[], int n)
 8 {
 9     // S1 建堆
10     // 从最后一个非叶子结点开始, 向前进行调整
11     for(int i = LocOfLastNonLeaf(a, n); i >= 0; i --)
12     {// LocOfLastNonLeaf : (n-1) / 2
13         HeapAdjust(a, i, n);
14     }
15 
16     // S2 输出并调整
17     for(int j = n-1; j > 0; j --)
18     {// 判断条件不用加"=", 因为j=0时等价于数组只有一个元素, 即只有一个根节点, 而无子树
19         Swap(a[0], a[j]); // 形参为同名参数, 直接交换a[0]和a[j]
20         HeapAdjust(a, 0, j);
21     }
22 }

 

 1 /**
 2  * 筛选位置i, 调整堆
 3  * @param a 待排序序列(数组)
 4  * @param i 筛选位置
 5  * @param len 数组元素个数
 6  */
 7 void HeapAdjust(int a[], int i, int n)
 8 {
 9     if(i > n / 2 - 1)
10     {// 叶子结点, 无子树
11         return;
12     }
13 
14     // 检查结点i是否符合小堆特性, 如果不符合, 需要与最小子结点交换
15     for(int k = 2*i +1; k < n; k = 2*k + 1)
16     {
17         // 判断右子树是否比左子树更小
18         if(k+1 < n && a[k+1] < a[k])
19         {
20             k ++; // 更新最小子结点
21         }
22 
23         if(a[i] > a[k])
24         {
25             Swap(a[i], a[k]); // 与最小的子结点交换
26             i = k;  // 将左子结点设为当前结点
27         }
28         else
29         {// 符合小堆特性
30             break;
31         }
32     }
33 }

 

4.2.  调用

 1 int main()
 2 {
 3     int a[] = {
 4 //            10,2,5,9,5,55,21,33,15
 5             49, 38, 65, 97, 76, 13, 27, 49
 6     };
 7     int a_len = sizeof(a) / sizeof(a[0]);
 8 
 9     printf("原始序列: ");
10     PrintArrary(a, a_len); // 打印数组
11 
12     HeapSort(a, a_len); // 堆排序
13 
14     printf("小堆排序后序列(降序): ");
15     PrintArrary(a, a_len); // 打印数组
16 
17     return 0;
18 }

 

4.3.  测试结果

 堆排序

 

原文链接: https://www.cnblogs.com/fortunely/p/10254161.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    堆排序

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/403227

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月21日 上午11:25
下一篇 2023年4月21日 上午11:25

相关推荐