Halo

A magic place for coding

0%

最小堆的C++实现

Introduction

  堆是一种很重要的数据结构,它是优先队列的底层,而优先队列是软件工程实践中经常会使用到的一个数据结构,所以今天我们就来看看堆是怎么去实现优先队列的。


原理

  堆实际上是一颗完全二叉树,对于最小堆而言,非叶子节点的值不大于左孩子和右孩子的值,所以维护起来根节点就是最小的。这样如果我们把节点的值看作是权重(1最大,10最小),这样就是一个优先队列了。

  堆的操作有两个:插入和取出。

  • 取出操作就是取出根节点(最小的值),取出后需要调整二叉树的结构。因为我们需要维护根节点是最小的,所以我们就要从这棵二叉树中找出最小的节点。
  • 插入操作就是插入一个数字到堆中,然后维护堆的性质。

  接下来我们就来看看这两个操作如何实现。先看取出,取走根节点后,我们需要在全局找一个最小的节点去替换,需要从上到下一直寻找,不断交换父节点和子节点的值即可。因为这个过程是自上到下的,所以也称作下沉shiftDown()

  再看插入,因为堆是一颗完全二叉树,所以插入的时候直接插在最后一个空节点,然后再不断向上检查与父节点的大小关系是否满足,再做调整。因为这个过程是自下到上的,所以也称作上浮shiftUp()


C++实现

  一般来说对于一颗完全二叉树来说,都会使用数组存储。而父子节点的下标有如下规律:父节点的下标是i,则其左子节点的下标是2 * i + 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <iostream>
using namespace std;


const int default_size = 100;

template <class T>
class MinHeap {
public:
MinHeap(int size = default_size);
MinHeap(T arr[], int n);
~ MinHeap();

bool Insert(const T &x);
bool Remove();
int GetMin();
bool IsEmpty() const;
bool IsFull() const;
private:
T* arr; // dynamice array
int currentSize;
void shiftDown(int start, int m);
void shiftUp(int start);
};

template <class T>
MinHeap<T>::MinHeap(int size) {
arr = new T[size];
currentSize = 0;
}

template <class T>
MinHeap<T>::MinHeap(T arr[], int n) {
this->arr = new T[default_size];

currentSize = n;
for (int i = 0; i < n; i++) {
cout << arr[i] << endl;
this->arr[i] = arr[i];
}
int currentPos= currentSize / 2 - 1;
while (currentPos >= 0) {
shiftDown(currentPos, currentSize - 1);
currentPos--;
}
}

template <class T>
MinHeap<T>::~MinHeap() {
delete []arr;
arr = NULL;
currentSize = 0;
}

template <class T>
bool MinHeap<T>::Insert(const T &x) {
if (currentSize == default_size) {
return false;
}
arr[currentSize] = x;
shiftUp(currentSize);
currentSize++;
return true;
}

template <class T>
bool MinHeap<T>::Remove() {
int x = -1;
if (IsEmpty()) {
return -1;
}

x = arr[0];
arr[0] = arr[currentSize - 1];
currentSize--;
shiftDown(0, currentSize - 1);
return x;
}

template <class T>
int MinHeap<T>::GetMin() {
if (IsEmpty()) {
return -1;
}
return this->arr[0];
}

template <class T>
bool MinHeap<T>::IsEmpty() const {
return currentSize == 0;
}

template <class T>
bool MinHeap<T>::IsFull() const {
return currentSize == default_size;
}

template <class T>
void MinHeap<T>::shiftDown(int start, int m) {
int i = start, child = 2 * i + 1; // left node

T temp = arr[i];
while (child <= m) {
if (child < m && arr[child] > arr[child + 1]) {
// choose the larger child
child++;
}
if (temp <= arr[child]) {
break;
} else {
arr[i] = arr[child];
i = child;
child = child * 2 + 1; // left node
}
}
arr[i] = temp;
}

template <class T>
void MinHeap<T>::shiftUp(int start) {
int i = start, parent = (i - 1) / 2; // parent

T temp = arr[i];
while (i > 0) {
if (arr[parent] <= temp) {
break;
} else {
arr[i] = arr[parent];
i = parent;
parent = (i - 1) / 2; // parent
}
}
arr[i] = temp;
}


int main() {
cout << "MinHeap Test" << endl;
int arr[] = {9, 17, 65, 23, 45, 78, 87, 53};
int size = sizeof(arr) / sizeof(arr[0]);
MinHeap<int> heap = MinHeap<int>(arr, size);
cout << "Initialize MinHeap Done" << endl;
cout << "GetMin() " << heap.GetMin() << endl;
heap.Insert(3);
cout << "GetMin() " << heap.GetMin() << endl;
cout << "IsEmpty() " << heap.IsEmpty() << endl;
cout << "IsFull() " << heap.IsFull() << endl;

while (!heap.IsEmpty()) {
heap.Remove();
cout << "GetMin() " << heap.GetMin() << endl;
}
cout << "IsEmpty() " << heap.IsEmpty() << endl;
return 0;
}

小结

  通过这篇博客,相信大家已经了解到优先队列的底层——堆。也同时了解了一个简单的最小堆是如何实现的,其实最大堆的原理也一样。后续如果有和堆相关的一些技巧和知识也会继续和大家分享,感谢你的支持!


Reference

  1. [最小堆构建、插入、删除的过程图解](

Welcome to my other publishing channels