堆是一种完全二叉树

  • 最大堆:对树中的每个结点来说,其结点的值都大于等于其孩子结点的值。
  • 最小堆:对树中的每个结点来说,其结点的值都小于等于其孩子结点的值。
  • 特性:最大堆中的最大值在堆顶,最小堆中的最小值在堆顶。

堆中操作:

  • 搜索堆顶元素。O(1)
  • 添加新结点。先将新结点添加到最后的位置,然后将该新结点的值与其父节点的值相比较,在最大堆中如果新结点值比父结点值大(也即父节点值比新结点值小,不符合最大堆的定义了,需要调整),则交换父子结点的数据;按照此方式逐层向上调整,直到到达堆顶或者父节点值大于等于新节点值。O(log(n))
  • 删除堆顶元素。在以vector为底层容器实现的最大堆中,堆顶元素并不直接删除,而是先将其(索引为0)与最后一个元素(索引为n-1)交换,然后堆的大小减一,vector中有效元素少一个,相当于删除了堆顶元素;然后将堆顶元素的值与其左右子结点元素的值相比,如果其左(右)孩子结点值在三者中最大,则交换根和左(右)孩子结点,然后递归调整左(右)孩子结点,直到到达叶子结点或者根节点值比左右孩子结点值都大。O(log(n))

最大堆(最小堆)常用优先队列(默认底层容器类是vector)实现。

最小堆实现

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. /**
  5. * 以vector为底层容器的最小堆(完全二叉树)
  6. * 有效元素从索引为0的位置开始存储,
  7. * 索引为i的元素的左、右结点索引分别是2*i+1, 2*i+2,
  8. * 索引为i的元素的父节点的索引是(i-1)/2,
  9. * 最后一个非叶子节点索引为n/2-1。
  10. */
  11. template <class DT>
  12. class MinHeap{
  13. private:
  14. vector<DT> data; // 存储元素使用的线性表
  15. int n; // data中有效元素个数
  16. void heapAdjust(int k){
  17. // 将data中索引位置在[0,k]的元素调整为最小堆
  18. // 有效元素k+1个,第一个非叶子节点的位置是(k+1)/2-1
  19. int node_idx = (k+1)/2-1;
  20. while(node_idx >= 0){
  21. adjustNode(node_idx);
  22. //this->print();
  23. node_idx --;
  24. }
  25. }
  26. void adjustNode(int i){
  27. // 调整索引i位置的结点,它一定有左孩子,不一定有右孩子
  28. int lc_idx = 2 * i + 1; // 索引i的左子结点索引
  29. int rc_idx = lc_idx + 1; // 索引i的右子结点索引
  30. if(lc_idx >= n){
  31. // 左孩子不存在,那右孩子一定不存在,无需调整了。
  32. return;
  33. }
  34. else if(rc_idx >= n){
  35. // 左孩子存在,右孩子不存在
  36. if(data[i] < data[lc_idx]){
  37. // 根节点值小于左子结点,无需调整
  38. return;
  39. }
  40. else{
  41. // 根节点值大于左子结点,交换根和左子结点
  42. this->swap(i, lc_idx);
  43. // 由于右孩子结点不存在,因此左孩子结点一定是叶子结点,无需递归调整左子节点
  44. }
  45. }
  46. else{
  47. // 左、右孩子都存在
  48. if(data[i] < data[lc_idx] && data[i] < data[rc_idx]){
  49. // 根结点是最小的,无需调整
  50. return;
  51. }
  52. else if(data[lc_idx] < data[i] && data[lc_idx] < data[rc_idx]){
  53. // 左子结点最小的,根与左子结点交换,然后递归调整左子结点
  54. this->swap(i, lc_idx);
  55. adjustNode(lc_idx);
  56. }
  57. else{
  58. // 右子结点是最小的,根与右子结点交换,然后递归调整右子结点
  59. this->swap(i, rc_idx);
  60. adjustNode(rc_idx);
  61. }
  62. }
  63. }
  64. void swap(int i, int j){
  65. // 交换索引位置在i,j处的元素
  66. DT tmp = this->data[i];
  67. this->data[i] = this->data[j];
  68. this->data[j] = tmp;
  69. }
  70. void heapUp(int i){
  71. // 新插入时调用,将索引为i的元素的值调整到正确的位置上
  72. // 要求原来数据已经是最小堆
  73. int root_idx = (i-1)/2;
  74. if(root_idx < 0 || data[root_idx] < data[i]){
  75. return;
  76. }
  77. else{
  78. // 新结点值比根结点小
  79. this->swap(root_idx, i);
  80. this->heapUp(root_idx);
  81. }
  82. }
  83. void heapDown(int i){
  84. // 删除结点后调用
  85. int lc_idx = 2 * i + 1; // i的左孩子索引
  86. int rc_idx = lc_idx + 1; // i的右孩子索引
  87. if(lc_idx >= this->n){
  88. // 左孩子不存在,则右孩子肯定也不存在,无需调整
  89. return;
  90. }
  91. else if(rc_idx >= this->n){
  92. // 左孩子存在,右孩子不存在
  93. if(data[i] < data[lc_idx]){
  94. // 根节点值小于左孩子结点值,已经是最小堆,无需调整
  95. return;
  96. }
  97. else{
  98. // 根节点值大于左孩子结点值,需要交换根和左孩子
  99. this->swap(i, lc_idx);
  100. // 因为左孩子已经是叶子结点,没有子树了,不用递归调整
  101. }
  102. }
  103. else{
  104. // 左右子结点都存在
  105. if(data[i] < data[lc_idx] && data[i] < data[rc_idx]){
  106. // 根结点是三个中最小的,符合最小堆,无需调整了
  107. return;
  108. }
  109. else if(data[lc_idx] < data[i] && data[lc_idx] < data[rc_idx]){
  110. // 左孩子是三者中最小的,交换根和左孩子结点
  111. this->swap(i, lc_idx);
  112. // 递归向下调整左孩子结点
  113. this->heapDown(lc_idx);
  114. }
  115. else{
  116. // 右孩子是三者中最小的,交换根和右孩子结点
  117. this->swap(i, rc_idx);
  118. // 递归向下调整右孩子结点
  119. this->heapDown(rc_idx);
  120. }
  121. }
  122. }
  123. public:
  124. MinHeap():n(0){}
  125. MinHeap(vector<DT> _data): data(_data), n(data.size()){
  126. this->heapAdjust(n-1);
  127. }
  128. void push(DT x){
  129. if(this->data.size() < n){
  130. // data中有空余位置
  131. this->data[n] = x;
  132. }
  133. else{
  134. // data中每个位置都有元素,也即所有元素都是有效元素
  135. this->data.push_back(x);
  136. }
  137. n += 1;
  138. this->heapUp(n-1);
  139. this->print();
  140. }
  141. void pop(){
  142. if(this->empty()){
  143. return;
  144. }
  145. this->swap(0, n-1);
  146. n -= 1;
  147. this->heapDown(0);
  148. this->print();
  149. }
  150. DT& top(){
  151. return data[0];
  152. }
  153. bool empty(){
  154. return this->n <= 0;
  155. }
  156. void print() {
  157. cout << "[";
  158. if (n > 0) {
  159. cout << this->data[0];
  160. }
  161. for (int i = 1; i < n; i++) {
  162. cout << "," << this->data[i];
  163. }
  164. cout << "]" << endl;
  165. }
  166. };
  167. int main() {
  168. MinHeap<int> mh(vector<int>({7, 4, 3, 5, 9, 8, 1, 2}));
  169. vector<int> nv({91, 14, 23, 41, 22, 11, 87, 90});
  170. for(int v : nv){
  171. mh.push(v);
  172. }
  173. while (!mh.empty()){
  174. cout << mh.top() << " , ";
  175. mh.pop();
  176. }
  177. cout << endl;
  178. return 0;
  179. }