【算法基础】13.十大排序算法——堆排序
参考资料
下面这个资料展示了堆排序的数据结构、构建堆的向上向下的基本过程、构建堆的全过程、堆的弹出与压入,较为齐全
堆排序https://zhuanlan.zhihu.com/p/417623885
下面这个资料示意图较直观,但仅仅展示了堆排序
堆排序算法详解https://blog.csdn.net/qq_35344198/article/details/107067432
直观理解
以小根堆为例,即parent节点必小于child节点的堆。
实现目标是已经构建好的堆,不停地弹出其根节点,即可获得一个升序集合。
第一个问题在于给定无序集合,如何初始化构建堆:
(1)先使用数组来存储数据,用数组地址来索引节点
(2)为了使左右节点的索引/2就得到父节点,数组中从[1]开始存储数据,而不是常规地从[0]开始存储数据,这仅仅是为了处理方便而设计的小trick
(3)有点像归并排序。在将数据视为一个堆后,从最后一个非叶子节点A开始执行down操作(与自己的左右子节点比较大小),如果有必要的话就进行交换,交换后对发生了更新的子节点继续执行down操作,直到不再需要往下交换或没有子节点;
结束之后,再对A前一个节点展开down操作,直至到达根节点并完成对根节点的down操作;
第二个问题在于弹出堆顶元素之后如何处理:
(1)取出堆顶元素后,
(2)将堆的最后一个元素放到堆顶,即heap[0]=heap[hSize],
(3)对新的根节点进行down操作,即可重新达到新的稳定状态;
第三个问题在与对于已经构建好的堆,如何压入新元素:
(1)将新元素放至堆的最后;
(2)对新节点展开up操作,即与parent节点进行比较,如果小则互换,直至不再需要互换或到达根节点;
例子先行
构建堆、查看堆顶元素、弹出堆顶元素、压入新元素
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 vector<int> heap;//用于堆存储 7 void BuildLittileHeap(vector<int>& array); 8 void downOp(vector<int>& array,int tgtNode); 9 bool heapPop(int& ret); 10 void heapPush(int val); 11 12 //程序入口 13 int main() 14 { 15 //初始元素 16 vector<int> arr{55,44,33,22,11}; 17 18 cout<<"堆构建测试"<<endl; 19 //输出验证 20 BuildLittileHeap(arr); 21 int cur=0; 22 while(heapPop(cur)){ 23 cout<<cur<<endl; 24 } 25 26 cout<<"压入新元素测试"<<endl; 27 BuildLittileHeap(arr); 28 heapPush(30); 29 //输出验证 30 while(heapPop(cur)){ 31 cout<<cur<<endl; 32 } 33 34 return 0; 35 } 36 37 //构建小根Heap 38 void BuildLittileHeap(vector<int>& array){ 39 //数据复制、初始化 40 heap.clear(); 41 heap.push_back(0);//占位 42 heap.insert(heap.end(),array.begin(),array.end());//附加 43 int dataLen = heap.size()-1; 44 45 //只需要遍历非叶子节点,注意跳过[0] 46 for(int i = dataLen / 2; i > 0; i--){ 47 downOp(heap,i); 48 } 49 50 } 51 52 //down操作 53 void downOp(vector<int>& array,int tgtNode){ 54 int nextId = tgtNode; 55 int dataLen=array.size()-1; 56 57 //nextId始终指向三脚堆的最小的元素,注意左右节点要始终与[nextId]节点进行比较 58 //如果左节点较小 59 if(2*tgtNode <= dataLen && array[2*tgtNode] < array[nextId]){ 60 nextId=2*tgtNode; 61 } 62 //如果右节点更小 63 if((2*tgtNode+1) <= dataLen && array[2*tgtNode+1] < array[nextId]){ 64 nextId=2*tgtNode+1; 65 } 66 67 if(nextId != tgtNode){ 68 swap(array[nextId],array[tgtNode]);//交换 69 downOp(array,nextId); 70 } 71 } 72 73 //是否为空堆 74 bool isEmpty(){ 75 //是否为空堆,仅有1个元素 76 return heap.size()<=1; 77 } 78 79 //获取堆顶元素 80 bool getTop(int& ret){ 81 ret=0; 82 83 //是否为空堆 84 int dataLen=heap.size()-1; 85 if(dataLen<=0){ 86 return false; 87 } 88 89 ret=heap[1]; 90 return true; 91 } 92 93 //弹出堆顶元素 94 bool heapPop(int& ret){ 95 //是否为空堆 96 int dataLen=heap.size()-1; 97 if(dataLen<=0){ 98 return false; 99 } 100 101 //弹出堆顶 102 ret=heap[1]; 103 104 //将最后一个元素放到堆顶 105 swap(heap[1],heap[dataLen]); 106 107 //删除最后一个元素 108 heap.erase(heap.begin()+dataLen); 109 110 //对堆顶的新元素进行down操作 111 downOp(heap,1); 112 113 return true; 114 } 115 116 //压入新元素 117 void heapPush(int val){ 118 //存入最后 119 heap.push_back(val); 120 int curId=heap.size()-1; 121 122 //up操作 123 while((curId/2)>0){ 124 //中止up 125 if(heap[curId/2]<=heap[curId]){ 126 break; 127 } 128 129 //交换 130 swap(heap[curId/2],heap[curId]); 131 curId/=2; 132 } 133 }
总结提炼
1将[0]空出的小trick,方便了索引的计算,并不是必须的;
2down和up的操作要掌握;
3已经构建好的堆,不停地弹出其堆顶,即可获得一个升序/降序集合;
拓展方向
1优先队列
C++中标准库中的数据类型priority_queue,就是堆结构,默认为大根堆
本文作者:OhOfCourse
本文链接:https://www.cnblogs.com/OhOfCourse/p/16917724.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步