堆排学习笔记
原理:
设有n个数字,初始状态n个数字随机排序,最终实现从大到小的顺序
堆排的本质是构成一个二叉树,其二叉树的特性是子树都比其父亲的数值要小(针对从大到小的排序而言,这种树叫最大堆)
堆排序一共分成三步
1.构建初始二叉树,将本无序的堆栈,以最大堆的形式重新构造,以便于接下来的步骤的进行
2.将最大的数字(位于树的根部)和最底端的数字交换(换到数组最后面),再将最底端被交换的那个数字(此时已经被换到了树的根部)沉没下去,使二叉树再次变成一个最大堆(不算被交换到数组底部的那个数字),接着再次重复此步骤
3.完成所有步骤之后,按顺序打印,即可获得一个从大到小的序列
图示:
图源:https://blog.csdn.net/sxhelijian/article/details/50295637
第一步:构造的过程:从最后一个节点开始遍历,每次走到一个节点时:比较它的父亲,兄弟,以及它自己三者大小,并且将最大的移到父亲的位置

构造结束后,堆中的情况:

第二步:交换的过程

第三步:打印的过程,不再赘述
代码:
明白思路过后,就要搞出一份代码来,下面是我的各个步骤的代码,每个人的代码风格格式均不相同,仅供参考
下沉过程:down
1 void down(int x)//把小的元素下沉 x是数字在数组中的位置 n代表数组中未完成排序的数的总量 2 { 3 while((x*2<=n&&number[x]<number[x*2])||(x*2+1<=n&&number[x]<number[x*2+1]))//当该节点的数比它的儿子小时 4 { 5 if(number[x*2]<number[x*2+1]&&x*2+1<=n)//如果比右儿子比左儿子大,交换该节点和它的右儿子 6 { 7 swap(number[x],number[x*2+1]); 8 x=x*2+1; 9 } 10 else if(number[x*2+1]<=number[x*2])//如果左儿子比右儿子大,交换该节点和它的左儿子 11 { 12 swap(number[x],number[x*2]); 13 x=x*2; 14 } 15 else if(number[x*2+1]>number[x*2]&&x*2+1>n)//特殊情况,右儿子大于左儿子,但是右儿子是已经排序完的数字,不可以交换 16 { 17 break; 18 } 19 } 20 }
上浮过程:up
1 void up(int x)//把大的元素上浮 2 { 3 while(number[x]>number[x/2]&&x>1)//如果该节点大于它的父亲节点,那么将它和它的父亲节点交换 4 //这里务必要注意x>1的条件,不然会遍历到number[0],我没有在number[0]中放数字所以我加了这个条件 5 { 6 swap(number[x],number[x/2]); 7 x=x/2;//继续循环直到这个节点不再大于它的父亲节点 8 } 9 }
在数组尾端插入新数:Insert
1 void Insert(int newNumber)//插入新的数字进入堆中 2 { 3 n++;//堆中数字总数增加 4 number[n]=newNumber; 5 up(n);//把新加入的数字上浮,确保堆不被破坏仍然是一个最大堆 6 }
主函数:
因为使用堆的情况不同,这里我就不放我的主函数了,简要说明一下堆排主函数大致的实现方法
1.将每个数字用Insert函数插入到数组中,使其一直保持为一个最大堆(或最小堆)
2.将根节点和数组最后的节点交换,并将n减少1,此后的操作不再改变该数字在数组中的位置
3.将被转移到现在的根节点的位置的数,沉没下去,以便该堆仍为一个最大堆,也保证了沉没后的堆的根节点是最大值(除了被处理完的数以外最大)
4.重复2.3两步直到n==1为止
5.此时的数组就是一个从小到大顺序排列好的数组
以上就是堆排的主要操作的过程,其中,下沉过程中的if 判断很值得思考,我在第一次使用堆排的时候发生了很严重的错误,因为没有考虑全if的条件导致最后发生死循环。
如果您也是一位初学者,我非常建议您在理解堆排的过程中不仅要自己敲一遍代码,还应该画出堆中节点的swap过程,这样有助于理解和记忆。
希望我的这篇学习笔记对您的学习有所帮助。

浙公网安备 33010602011771号