堆排序 全面总结

堆排序:虽然用到类似二叉树的思想,但是仍是原地排序。堆分为大根堆和小根堆两种。堆顶元素一定是该序列最值。对于从小到大的排序,适合采用大根堆,每轮都将堆顶的最大值与堆末元素交换。


堆排序的基本思想:由于大根堆的堆顶元素是最大值,因此我每次都可以找到当时的最大值,从而达到排序的目的。具体的,先把原始序列变成堆,然后把堆顶最大值调换到尾部作为已排好序的部分(堆外),此时堆变小且被破坏,要对其调整,重复执行这一过程。

注意:由小到大排序的时候,使用大根堆,这样每次将最大的数交换到末尾,达到由小到大排序的目的。由大到小排序的时候,使用小根堆。


堆的调整过程:调整的方向是从根向下到叶子的方向。保证在这一子树中,较大的元素留在父节点

这个过程是堆排序最关键的操作,一般写为一个sink函数:找较大孩子,若较大孩子比父节点大,就交换,然后进其较大孩子那侧继续。


堆的初建过程:为了保证整个序列满足堆的性质,即要所有子树都满足堆的性质。要从最后一个子树开始进行调整过程

单独一个节点的子树必然满足堆的性质,所以要从最后一个非单独节点的子树(即,最后一个非叶子节点)开始进行调整过程。


堆排序有两种方式:递归方式和非递归方式。其整体思路一致,区别仅在于调整过程的形式:只不过递归方式是递归的形式来遍历和调整子树,非递归方式是用循环的形式来遍历和调整子树。


递归形式代码:

[cpp]  view plain copy
  1. //参数:堆起始,调整元素位置,当前堆大小  
  2. void sink(int a[], int i, int n)  
  3. {  
  4.     int lc = 2*i;  
  5.     if (lc > n)  
  6.         return//已到叶子节点  
  7.     int rc = lc + 1;  
  8.     int mc = (rc > n)? lc : ((a[lc]>a[rc])?lc:rc);  
  9.     //mc为较大的孩子  
  10.     if(a[i] >= a[mc])   
  11.         return//此时子树不会有变化,无需继续调整
  12.   
  13.     swap(&a[i], &a[mc]);//较大的孩子对应的子树发生变化  
  14.     sink(a, mc, n);  
  15. }  
  16.   
  17.   
  18. void heap_sort_recursion(int a[], int n)  
  19. {  
  20.     int i;  
  21.     for(i=n/2;i>=1;i--)  //建堆过程:从最后一个非叶子节点到根节点,都要做sink
  22.         sink(a, i, n);  
  23.       
  24.     for(i=1;i<=n;i++)  
  25.     {  
  26.         swap(&a[1],&a[n-i+1]);  //逐个将堆根交换出去
  27.         sink(a, 1, n-i);  
  28.     }  
  29. }  


非递归形式代码:

[cpp]  view plain copy
  1. void sink_nonrecursion(int a[], int i, int n)  
  2. {  
  3.     int lc, rc, mc;  
  4.     lc = 2*i;  
  5.     while(lc <= n) //从i处向下直到叶子结点  
  6.     {  
  7.         rc = lc + 1;  
  8.         mc = (rc > n)?lc : ((a[lc]>a[rc])?lc:rc);  
  9.         //mc为较大的孩子  
  10.         if(a[i] < a[mc])  
  11.         {  
  12.             swap(&a[i], &a[mc]);  
  13.             //较大的孩子对应的子树发生变化  
  14.             i = mc;  
  15.             lc = 2*i;  
  16.         }  
  17.         else  
  18.             return;//此时子树不会有变化  
  19.     }  
  20. }  
  21.   
  22. void heap_sort_nonrecursion(int a[], int n)  
  23. {  
  24.     int i;  
  25.     for(i=n/2;i>=1;i--)  
  26.         sink_nonrecursion(a, i, n);  
  27.       
  28.     for(i=1;i<=n;i++)  
  29.     {  
  30.         swap(&a[1],&a[n-i+1]);  
  31.         sink_nonrecursion(a, 1, n-i);  
  32.     }  
  33. }  

编码时注意:

1、待排序列(同时也是堆)在数组中的起始编号是1还是0? 

这关系到求孩子位置的计算公式。 本文中使用的是数组起始编号(即堆的根节点编号)从1开始。也就是说0位置没有用到。

2、区分整个序列的大小和堆的大小。

堆随着轮次的进行越来越小。


辅助代码:

[cpp]  view plain copy
  1. void swap(int *a, int *b)  
  2. {  
  3.     int tmp;  
  4.     tmp = *a;  
  5.     *a = *b;  
  6.     *b = tmp;  
  7. }  
  8. void show(int a[], int n)  
  9. {  
  10.     for(int i=1;i<=n;i++)  
  11.         printf("%d ",a[i]);  
  12.     printf("\n");  
  13. }  
  14.   
  15. int main()  
  16. {  
  17.     int a[] = {0, 1, 3 ,45,5 ,7,66,50 ,9, 2, 4, 6, 8};  
  18.     int n = sizeof(a)/sizeof(int)-1;  
  19.     show(a, n);  
  20.     //heap_sort_nonrecursion(a, n);  
  21.     heap_sort_recursion(a, n);  
  22.     show(a, n);  
  23.     return 1;  
  24. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值