【STL学习】堆相关算法详解与C++编程实现(Heap)

转自:https://blog.csdn.net/xiajun07061225/article/details/8553808

堆简介

堆并不是STL的组件,但是经常充当着底层实现结构。比如优先级队列(Priority Queue)等等。

堆是一种完全二叉树,因此我们可以用数组来存储所有节点。在这里的实现中,采用了一个技巧:将数组中索引为0的元素保留,设置为极大值或者为极小值(依据大顶堆或者小顶堆而定)。那么当某个节点的索引是i时,其左子节点索引为2*i,右子节点索引为2*i+1.父节点是i/2(这里/表示高斯符号,取整)。这种以数组表示树的方式,我们成为隐式表述法(implicit reprentation)。我们这里用C++ STL中的容器vector实现替代数组的功能。

堆分为大顶堆和小顶堆。这里介绍并实现的是大顶堆。

堆的主要相关算法介绍

push_heap算法

此操作是向堆中添加一个节点。为了满足完全二叉树的条件,新加入的元素一定放在最下面的一层作为叶节点,并填补在由左至右的第一个空格,在这里放在底层容器vector的end()处。

很显然,新元素的加入很可能使得堆不在满足大顶堆的性质---每个节点的键值都大于或等于其子节点的键值。为了调整使得其重新满足大顶堆的特点,在这里执行一个上溯(percolate up)操作:将新节点与父节点比较,如果其键值比父节点大,就交换父子的位置,如此一直上溯,直到不需要交换或者到根节点为止。

pop_heap算法

此操作取走根节点。对于大顶堆,取得的是堆中值最大的节点,对于小顶堆,取得的是堆中值最小的节点。STL实现并不是将这个节点直接删除,而是将其放在底层容器vector的尾端。而原尾端的节点插入到前面的适当位置。

我们首先保存原vector尾端的节点值,然后将根节点值存储在此处。为了实将原尾端节点的值插入适当位置,重新构建大顶堆,我们实施如下调整堆的操作:

先执行下溯(percolate down)操作:从根节点开始将空洞节点(一开始是根节点)和较大子节点交换,并持续向下进行,直到到达叶节点为止。然后将已保存的原容器vector尾端节点赋给这个已到达叶层的空洞节点。

注意,到这里并没有结束。因为这时候可能还没有满足大顶堆的特性。还需要执行一次上溯操作。这样,便重新构建了大顶堆。

make_heap算法

此操作是依据已有的各元素构建堆。

其中,各元素已经存放在底层容器vector中。

构建堆实质是一个不断调整堆(即前面pop_heap算法中的调整堆的操作)的过程---通过不断调整子树,使得子树满足堆的特性来使得整个树满足堆的性质。

叶节点显然需要调整,第一个需要执行调整操作的子树的根节点是从后往前的第一个非叶结点。从此节点往前到根节点对每个子树执行调整操作,即可构建堆。

sort_heap算法

堆排序算法。执行此操作之后,容器vector中的元素按照从小到大的顺序排列。

构建大顶堆之后,不断执行pop_heap算法取出堆顶的元素,即可。因为每次取得的是最大的元素,将其放在容器vector的最尾端。所以到最后vector中的元素是从小到大排列的。

编程实现(C plus plus)

详细代码包括两个文件Heap.h以及HeapTest.cpp:

Heap.h:

  1. //STL堆算法实现(大顶堆)
  2. //包含容器vector的头文件:Heap用vector来存储元素
  3. #include <vector>
  4. #include <iostream>
  5. #include <functional>
  6. #define MAX_VALUE 999999 //某个很大的值,存放在vector的第一个位置(最大堆)
  7. const int StartIndex = 1;//容器中堆元素起始索引
  8. using namespace std;
  9. //堆类定义
  10. //默认比较规则less
  11. template <class ElemType,class Compare = less<ElemType> >
  12. class MyHeap{
  13. private:
  14. vector<ElemType> heapDataVec;//存放元素的容器
  15. int numCounts;//堆中元素个数
  16. Compare comp;//比较规则
  17. public:
  18. MyHeap();
  19. vector<ElemType> getVec();
  20. void initHeap(ElemType *data,const int n);//初始化操作
  21. void printfHeap();//输出堆元素
  22. void makeHeap();//建堆
  23. void sortHeap();//堆排序算法
  24. void pushHeap(ElemType elem);//向堆中插入元素
  25. void popHeap();//从堆中取出堆顶的元素
  26. void adjustHeap(int childTree,ElemType adjustValue);//调整子树
  27. void percolateUp(int holeIndex,ElemType adjustValue);//上溯操作
  28. };
  29. template <class ElemType,class Compare>
  30. MyHeap<ElemType,Compare>::MyHeap()
  31. :numCounts(0)
  32. {
  33. heapDataVec.push_back(MAX_VALUE);
  34. }
  35. template <class ElemType,class Compare>
  36. vector<ElemType> MyHeap<ElemType,Compare>::getVec()
  37. {
  38. return heapDataVec;
  39. }
  40. template <class ElemType,class Compare>
  41. void MyHeap<ElemType,Compare>::initHeap(ElemType *data,const int n)
  42. {
  43. //拷贝元素数据到vector中
  44. for (int i = 0;i < n;++i)
  45. {
  46. heapDataVec.push_back(*(data + i));
  47. ++numCounts;
  48. }
  49. }
  50. template <class ElemType,class Compare>
  51. void MyHeap<ElemType,Compare>::printfHeap()
  52. {
  53. cout << "Heap : ";
  54. for (int i = 1;i <= numCounts;++i)
  55. {
  56. cout << heapDataVec[i] << " ";
  57. }
  58. cout << endl;
  59. }
  60. template <class ElemType,class Compare>
  61. void MyHeap<ElemType,Compare>::makeHeap()
  62. {
  63. //建堆的过程就是一个不断调整堆的过程,循环调用函数adjustHeap依次调整子树
  64. if (numCounts < 2)
  65. return;
  66. //第一个需要调整的子树的根节点多音
  67. int parent = numCounts / 2;
  68. while(1)
  69. {
  70. adjustHeap(parent,heapDataVec[parent]);
  71. if (StartIndex == parent)//到达根节点
  72. return;
  73. --parent;
  74. }
  75. }
  76. template <class ElemType,class Compare>
  77. void MyHeap<ElemType,Compare>::sortHeap()
  78. {
  79. //堆排序思路
  80. //每执行一次popHeap操作,堆顶的元素被放置在尾端,然后针对前面的一次再执行popHeap操作
  81. //依次下去,最后即得到排序结果
  82. while(numCounts > 0)
  83. popHeap();
  84. }
  85. template <class ElemType,class Compare>
  86. void MyHeap<ElemType,Compare>::pushHeap(ElemType elem)
  87. {
  88. //将新元素添加到vector中
  89. heapDataVec.push_back(elem);
  90. ++numCounts;
  91. //执行一次上溯操作,调整堆,以使其满足最大堆的性质
  92. percolateUp(numCounts,heapDataVec[numCounts]);
  93. }
  94. template <class ElemType,class Compare>
  95. void MyHeap<ElemType,Compare>::popHeap()
  96. {
  97. //将堆顶的元素放在容器的最尾部,然后将尾部的原元素作为调整值,重新生成堆
  98. ElemType adjustValue = heapDataVec[numCounts];
  99. //堆顶元素为容器的首元素
  100. heapDataVec[numCounts] = heapDataVec[StartIndex];
  101. //堆中元素数目减一
  102. --numCounts;
  103. adjustHeap(StartIndex,adjustValue);
  104. }
  105. //调整以childTree为根的子树为堆
  106. template <class ElemType,class Compare>
  107. void MyHeap<ElemType,Compare>::adjustHeap(int childTree,ElemType adjustValue)
  108. {
  109. //洞节点索引
  110. int holeIndex = childTree;
  111. int secondChid = 2 * holeIndex + 1;//洞节点的右子节点(注意:起始索引从1开始)
  112. while(secondChid <= numCounts)
  113. {
  114. if (comp(heapDataVec[secondChid],heapDataVec[secondChid - 1]))
  115. {
  116. --secondChid;//表示两个子节点中值较大的那个
  117. }
  118. //上溯
  119. heapDataVec[holeIndex] = heapDataVec[secondChid];//令较大值为洞值
  120. holeIndex = secondChid;//洞节点索引下移
  121. secondChid = 2 * secondChid + 1;//重新计算洞节点右子节点
  122. }
  123. //如果洞节点只有左子节点
  124. if (secondChid == numCounts + 1)
  125. {
  126. //令左子节点值为洞值
  127. heapDataVec[holeIndex] = heapDataVec[secondChid - 1];
  128. holeIndex = secondChid - 1;
  129. }
  130. //将调整值赋予洞节点
  131. heapDataVec[holeIndex] = adjustValue;
  132. //此时可能尚未满足堆的特性,需要再执行一次上溯操作
  133. percolateUp(holeIndex,adjustValue);
  134. }
  135. //上溯操作
  136. template <class ElemType,class Compare>
  137. void MyHeap<ElemType,Compare>::percolateUp(int holeIndex,ElemType adjustValue)
  138. {
  139. //将新节点与其父节点进行比较,如果键值比其父节点大,就父子交换位置。
  140. //如此,知道不需要对换或直到根节点为止
  141. int parentIndex = holeIndex / 2;
  142. while(holeIndex > StartIndex && comp(heapDataVec[parentIndex],adjustValue))
  143. {
  144. heapDataVec[holeIndex] = heapDataVec[parentIndex];
  145. holeIndex = parentIndex;
  146. parentIndex /= 2;
  147. }
  148. heapDataVec[holeIndex] = adjustValue;//将新值放置在正确的位置
  149. }

main.cpp:

  1. #include "Heap.h"
  2. #include <iostream>
  3. using namespace  std;
  4. int main()
  5. {
  6. const int n = 9;
  7. int data[n] = {0,1,2,3,4,8,9,3,5};
  8. MyHeap<int> *intHeapObj = new MyHeap<int>;
  9. intHeapObj->initHeap(data,n);
  10. intHeapObj->printfHeap();
  11. intHeapObj->makeHeap();
  12. intHeapObj->printfHeap();
  13. intHeapObj->pushHeap(7);
  14. intHeapObj->printfHeap();
  15. intHeapObj->popHeap();
  16. cout << "The top of heap :" << intHeapObj->getVec().back() << endl;
  17. intHeapObj->getVec().pop_back();
  18. intHeapObj->printfHeap();
  19. intHeapObj->sortHeap();
  20. cout << "Sorted data :";
  21. for (int i = 1;i <= n;++i)
  22. cout << intHeapObj->getVec()[i] << " ";
  23. cout << endl;
  24. delete intHeapObj;
  25. return 0;
  26. }

运行环境:Win7 + VS2008.

运行结果:

原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/9027074.html

时间: 2024-12-11 18:53:08

【STL学习】堆相关算法详解与C++编程实现(Heap)的相关文章

Bresenham画线算法详解及其OpenGL编程实现

http://blog.csdn.net/xiajun07061225/article/details/7018719 Bresenham是由Bresenham提出的一种精确而有效地光栅线生成算法,该算法仅使用增量整数计算.另外,它还可以用于显示其它曲线. 我们以斜率大于0小于1的线段来进行考虑.以单位x间隔进行取样.每次绘制了当前像素点(xk,yk)之后,需要确定下一个要绘制的点是(xk+1,yk)还是(xk+1,yk+1),需要判断哪一个点像素点更接近线路径. 在取样位置,我们使用d1和d2

机器学习经典算法详解及Python实现--聚类及K均值、二分K-均值聚类算法

摘要 聚类是一种无监督的学习(无监督学习不依赖预先定义的类或带类标记的训练实例),它将相似的对象归到同一个簇中,它是观察式学习,而非示例式的学习,有点像全自动分类.说白了,聚类(clustering)是完全可以按字面意思来理解的--将相同.相似.相近.相关的对象实例聚成一类的过程.机器学习中常见的聚类算法包括 k-Means算法.期望最大化算法(Expectation Maximization,EM,参考"EM算法原理").谱聚类算法(参考机器学习算法复习-谱聚类)以及人工神经网络算法

最短路算法 :Bellman-ford算法 &amp; Dijkstra算法 &amp; floyd算法 &amp; SPFA算法 详解

 本人QQ :2319411771   邮箱 : [email protected] 若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接   :http://www.cnblogs.com/Yan-C/p/3916281.html . 很早就想写一下最短路的总结了,但是一直懒,就没有写,这几天又在看最短路,岁没什么长进,但还是加深了点理解. 于是就想写一个大点的总结,要写一个全的. 在本文中因为邻接表在比赛中不如前向星好写,而且前向星效率并

jvm堆内存优化详解

在日常的运维工作中用到tomcat,都需要对tomcat中的jvm虚拟机进行优化,只有知道需要优化参数的具体用处,才能深刻体会优化jvm的意义所在. 在平常的工作中我们谈对jvm的优化,主要是针对java的堆内存的优化和垃圾回收机制的优化. JVM堆内存示意图: JVM的堆内存的组成: young generation:新生代 eden:伊甸园区 surived:存活区 其中存活区有2个,第1个为S0,第2个为S1 old generation:老年代 permanent generation:

机器学习经典算法详解及Python实现---朴素贝叶斯分类及其在文本分类、垃圾邮件检测中的应用

摘要: 朴素贝叶斯分类是贝叶斯分类器的一种,贝叶斯分类算法是统计学的一种分类方法,利用概率统计知识进行分类,其分类原理就是利用贝叶斯公式根据某对象的先验概率计算出其后验概率(即该对象属于某一类的概率),然后选择具有最大后验概率的类作为该对象所属的类.总的来说:当样本特征个数较多或者特征之间相关性较大时,朴素贝叶斯分类效率比不上决策树模型:当各特征相关性较小时,朴素贝叶斯分类性能最为良好.另外朴素贝叶斯的计算过程类条件概率等计算彼此是独立的,因此特别适于分布式计算.本文详述了朴素贝叶斯分类的统计学

J2EE学习篇之--JDBC详解

今天我们来说一下关于JDBC的相关知识,关于JDBC我想大家都不陌生了,而且我记得早就开始使用它了,记得那是大二的时候做课程设计,但是那时候是为了完成任务,所以遇到问题就google,那时候也没有时间去整理,所以这次就来详细说一下关于JDBC的知识 摘要: JDBC(Java Data Base Connectivity,java数据库连接),由一些接口和类构成的API. J2SE的一部分,由java.sql,javax.sql包组成. 应用程序.JDBC API.数据库驱动及数据库之间的关系

机器学习经典算法详解及Python实现--线性回归(Linear Regression)算法

(一)认识回归 回归是统计学中最有力的工具之一.机器学习监督学习算法分为分类算法和回归算法两种,其实就是根据类别标签分布类型为离散型.连续性而定义的.顾名思义,分类算法用于离散型分布预测,如前面讲过的KNN.决策树.朴素贝叶斯.adaboost.SVM.Logistic回归都是分类算法:回归算法用于连续型分布预测,针对的是数值型的样本,使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅是离散的类别标签. 回归的目的就是建立一个回归方程用来预测目

STL list链表的用法详解(转)

本文以List容器为例子,介绍了STL的基本内容,从容器到迭代器,再到普通函数,而且例子丰富,通俗易懂.不失为STL的入门文章,新手不容错过! 0 前言 1 定义一个list 2 使用list的成员函数push_back和push_front插入一个元素到list中 3 list的成员函数empty() 4 用for循环来处理list中的元素 5 用STL的通用算法for_each来处理list中的元素 6 用STL的通用算法count_if()来统计list中的元素个数 7 使用count_i

风螺旋公切线算法详解

风螺旋公切线算法详解 2017-12-29 刘崇军 风螺旋线 好久不见,近来一切可好?2017年最后这段时间里,狂补了一把C#,希望未来能够从软件代码层面实现风螺旋算法的验证与推广.今天跟大家分享的这个话题的底图就是最近一段时间的学习成果:一个基于WPF架构的非常简单的绘图框架,以及对风螺旋的自动化绘制进行的实现.闲话少叙,开始今天的主题. 在掌握了风螺旋切线计算的基础上,就可以开始公切线算法的研究了.公切线的计算是飞行程序模板中非常关键的一项内容,因此,在开始模板算法分享之前,详细回顾一下公切