【数据结构】MinHeap 的实现

面试一个大厂,让我实现最小堆的的两个功能:

  • insert:插入元素,保持最小堆特性
  • pop:推出第一个最小的元素

首先,堆是一种二叉树结构,但经过了排序,最小的元素永远是根元素,且对所有子树都成立。

但是对于每一个树,其左右子节点的大小则并不一定是排序的,也就是说左子节点和右子节点谁大谁小不一定。且左右子节点的值可能等于根节点。

根据 [1] 和观摩其他大神写的最小堆,实现最小堆不需要显式的实现 Node,因为堆总是向左填充(维持排序),其数据结构一直被完好的维持。若使用显式节点实现会破坏结构也会影响性能。

实现如下

MInheapSource.h:

 

 1 #pragma once
 2 #ifndef MINHEAPSOURCE_H
 3 #define MINHEAPSOURCE_H
 4 
 5 template<class T> // 可使用多种数据类型作为模板
 6 class MinHeap {
 7 public:
 8     MinHeap(int maxSize);   // 构造空最小堆
 9     MinHeap(T* arr, int size);   // 从数组中构造堆
10     MinHeap(MinHeap& m);    // 从其他最小堆构造
11     ~MinHeap();
12 
13     // 节点位置计算
14     inline int left(int index) { return 2 * index + 1; }
15     inline int right(int index) { return 2 * index + 2; }
16     inline int parent(int index) { return (index - 1) / 2; }
17 
18     // 返回目前MinHeap大小
19     inline int size(){ return _currentSize + 1 ;}
20     bool isEmpty();
21     bool isFull();
22     void insert(const T& t);
23     bool pop(T& result);    // 推出第一个最小元素
24 
25     T* getMinHeap();
26     // 显式数组
27     void print();
28 private:
29     T* _minheap = nullptr;  // 堆本体
30     int _currentSize; // 目前大小
31     int _maxSize;   // 最大容量
32     void shiftDown(const int index, const int end); // 向下调整堆以维持堆的层级
33     void shiftUp(int index); // 向上调整堆以维持堆的层级
34 };
35 
36 #endif

 

MInheapSource.cpp:

  1 #include "MinHeapSource.h"
  2 #include <iostream>
  3 
  4 template<class T>
  5 MinHeap<T>::MinHeap(int maxSize) {
  6     _maxSize = maxSize;
  7     _minheap = new T[_maxSize];
  8     if (_minheap == nullptr) {
  9         std::cerr << "MinHeap Memory allocation failed" << std::endl;
 10         exit(-1);
 11     }
 12     _currentSize = 0;
 13 }
 14 
 15 template<class T>
 16 MinHeap<T>::MinHeap(T* arr, int size) {
 17     _maxSize = size;
 18     _minheap = new T[_maxSize];
 19     if (_minheap == nullptr) {
 20         std::cerr << "MinHeap Memory allocation failed" << std::endl;
 21         exit(-1);
 22     }
 23     // 逐个赋值
 24     _currentSize = size;
 25     for (int i = 0; i < _currentSize; ++i) {
 26         _minheap[i] = arr[i];
 27     }
 28 
 29     // 利用下滑算法形成最小堆
 30     // NOTE: 以为curPos指向的时下标,所以要减2
 31     int curPos = (_currentSize - 2) / 2;
 32     while (curPos >= 0) {
 33         shiftDown(curPos, _currentSize - 1);
 34         --curPos;
 35     }
 36 }
 37 
 38 template<class T>
 39 MinHeap<T>::MinHeap(MinHeap& m) {
 40     _minheap = new T[_maxSize]; // 申请新的空间
 41     if (_minheap == nullptr) {
 42         std::cerr << "MinHeap Memory allocation failed" << std::endl;
 43             exit(-1);
 44     }
 45     for (int i = 0; i < _maxSize; ++i)
 46         *_minheap[i] = *m._minheap[i];
 47     _currentSize = m._currentSize;
 48     _maxSize = m._maxSize;
 49 }
 50 
 51 template<class T>
 52 MinHeap<T>::~MinHeap() {
 53     if(_minheap != nullptr)
 54         delete []_minheap;
 55 }
 56 
 57 template<class T>
 58 bool MinHeap<T>::isEmpty() {
 59     return _currentSize == 0;
 60 }
 61 
 62 template<class T>
 63 bool MinHeap<T>::isFull() {
 64     return _currentSize == _maxSize;
 65 }
 66 
 67 template<class T>
 68 T* MinHeap<T>::getMinHeap() {
 69     return _minheap;
 70 }
 71 
 72 template<class T>
 73 void MinHeap<T>::insert(const T& t) {
 74     if (isFull()) {
 75         std::cerr << "the MinHeap is full, can't insert more element" << std::endl;
 76         return;
 77     }
 78     _minheap[_currentSize] = t;
 79     shiftUp(_currentSize);    // 自下而上调整最小堆
 80     ++_currentSize;
 81 }
 82 
 83 template<class T>
 84 bool MinHeap<T>::pop(T& result) {
 85     if (_currentSize > _maxSize) {
 86         std::cerr << "the MinHeap is empty, no element to pop" << std::endl;
 87         return false;
 88     }
 89     result = _minheap[0];
 90     // 将当前的第一个元素替换为最后一个元素,然后自上而下调整为最小堆
 91     _minheap[0] = _minheap[_currentSize - 1];
 92     _currentSize--;
 93     shiftDown(0, _currentSize - 1);  // 自上而下调整为最小堆
 94     return true;
 95 }
 96 
 97 // 自下而上调整最小堆(修复 insert)
 98 template<class T>
 99 void MinHeap<T>::shiftUp(int index) {
100     if (isEmpty()) return;
101 
102     int current = index;
103     int parentC = parent(current);
104     // 若抵达顶点,返回
105     if (current <= 0)    return;
106     // 若父节点大于当前节点时,递归交换
107     if (_minheap[parentC] > _minheap[current]) {
108         int temp = _minheap[parentC];
109         _minheap[parentC] = _minheap[current];
110         _minheap[current] = temp;
111         shiftUp(parentC);
112     }
113 }
114 
115 // 自上而下调整最小堆(修复 pop)
116 template<class T>
117 void MinHeap<T>::shiftDown(const int index, const int end) {
118     if (isEmpty()) return;
119 
120     int current = index;    // 保留当前索引
121     int leftC = left(current);
122 
123     // 若左子节点或右子节点超出范围,返回
124     if (leftC > end)
125         return;
126 
127     // 比较左右子节点(主要和左子节点作比较)
128     if (leftC < end && _minheap[leftC] > _minheap[leftC + 1]) 
129         ++leftC;
130 
131     // 若当现结点大于左子节点时,递归交换
132     if (_minheap[current] > _minheap[leftC]) {
133         int temp = _minheap[current];
134         _minheap[index] = _minheap[leftC];
135         _minheap[leftC] = temp;
136         shiftDown(leftC, end);
137     }
138 }
139 
140 template<class T>
141 void MinHeap<T>::print() {
142     for (int i = 0; i < _currentSize; ++i) {
143         std::cout << _minheap[i] << "\t";
144     }
145     std::cout << std::endl;
146 }

 

主程序:

 1 // MinHeap.cpp : This file contains the 'main' function. Program execution begins and ends there.
 2 //
 3 #include "MinHeapSource.h"
 4 #include "MinHeapSource.cpp"    // include source file because the template class has to include definition explict in the main
 5 #include <iostream>
 6 
 7 using namespace std;
 8 
 9 int main() {
10 
11     int a[] = { 9, 6, 5, 4, 3, 2, 1 };
12     MinHeap<int> heap(a, 7);
13 
14     cout << "Init the heap" << endl;
15     heap.print();
16 
17     int p;
18     heap.pop(p);
19     cout << "Pop smallest element: " << p << endl;
20     heap.print();
21 
22     cout << "Insert 10" << endl;
23     heap.insert(10);
24     heap.print();
25 
26     return 0;
27 }
28 
29 /*
30 运行结果:
31 Init the heap
32 1       3       2       4       6       9       5
33 Pop smallest element: 1
34 2       3       5       4       6       9
35 Insert 10
36 2       3       5       4       6       9       10
37 */

 

注:

1)为什么要下滑和上滑(ShiftDown & ShiftUp)?

  这主要是因为堆结构在插入(insert)和删除最小元素(pop)操作时会破坏堆的结构,所以需要下滑和上滑两个操作。下滑用于修复插入后堆元素的破坏,上滑用于修复删除最小元素时对堆元素的破坏。[3]

 

参考资料:

[1] 实现指引:https://www.coder.work/article/2752123

[2] 实现参考:https://blog.csdn.net/qq_37623612/article/details/88696924

[3] 为什么要ShiftDown & ShiftUp:https://mingshan.fun/2019/05/14/heap/

posted @ 2020-09-05 21:18  DamienTian  阅读(896)  评论(0编辑  收藏  举报