数据结构—堆
堆也被称为优先队列,就是一棵完全二叉树。
队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头取出元素。而堆也是一样,在堆底插入元素,在堆顶取出元素,但是堆中元素的排列不是按照到来的先后顺序,而是按照一定的优先顺序排列的。这个优先顺序可以是元素的大小或者其他规则。所以堆又分为最大堆与最小堆,或者最大完全二叉树与最小完全二叉树。
最大堆:堆中每一个节点的值都大于或等于其子节点的值。
最小堆:堆中每一个节点的值都小于或等于其子节点的值。
堆可以看成一个完全二叉树,所以可以考虑使用二叉树的表示方法来表示堆。但是因为堆中元素按照一定的优先顺序排列,因此可以使用更简单的方法——动态数组——来表示,这样可以节省子节点指针空间,并且可以快速访问每个节点。
1 /********************************************
2 堆结构(最大)完全二叉树类模板
3 数组实现
4 2018/3/22
5 *********************************************/
6
7 #pragma once
8 #include <iostream>
9 #include <cmath>
10
11 template<class T>
12 class MyHeap
13 {
14 public:
15 MyHeap();
16 ~MyHeap();
17 MyHeap(const MyHeap& val);
18 void clear(); //清空堆
19 void _sizeExpand(); //判断堆是否已满,若是,则进行扩容
20 void push(const T& srcData); //在树尾部插入,再进行交换
21 void initHeap(const T arr[], int len);
22 T deleteHeap();
23
24 void prePrint(int index = 0)const;
25 size_t size()const;
26 T *getHeapRoot();
27
28
29 private:
30 T *pRoot;
31 size_t len;
32 size_t maxSize;
33
34 };
下面,以最大堆为例,说明堆的初始化、插入、删除等操作。
插入
堆的插入步骤:
- 将新元素增加到堆的末尾;
- 按照优先顺序,将新元素与其父节点比较,如果新元素小于父节点则将两者交换位置;
- 不断进行第2步操作,直到不需要交换新元素和父节点,或者达到堆顶;
- 最后通过得到一个最大堆。
通过将新元素与父节点从下向上调整的操作,叫做上滤。
1)添加66到堆中 1)交换66和5 1)交换66和9 1)交换66和10
初始化
1.首先按照原始数据的位置依次加入到堆中;
2.然后,从最后一个有子节点的位置开始比较,看是否满足最大堆的规则,如果不满足往下交换,直到满足规则达到叶节点或者到达叶节点;
删除
堆的删除操作与插入操作相反,插入操作从下往上调整堆,而删除操作则从上往下调整堆。
- 删除堆顶元素(通常是将堆顶元素放置在数组的末尾)
- 比较左右子节点,将小的元素上调。
- 不断进行步骤2,直到不需要调整或者调整到堆底。
上述调整的方法称为下滤(percolate down)。
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
所以在已有数据已构成堆得情况下,采用堆排序不失为一种好的选择。
堆排序的基本思想是:将待排序序列构造成一个最大堆,此时,整个序列的最大值就是堆顶的根节点。从堆中删除数据,并以此保存删除的数据(删除函数返回值),就能得到一个有序序列。
C++实现:
[MyHeap.h]
1 /****************************************
2 堆结构(最大)完全二叉树类模板
3 数组实现
4 2018/3/22
5 ****************************************/
6
7 #pragma once
8 #include <iostream> 9 #include <cmath>
10
11 template<class T>
12 class MyHeap
13 {
14 public:
15 MyHeap();
16 ~MyHeap();
17 MyHeap(const MyHeap& val);
18 void clear(); //清空堆
19 void _sizeExpand(); //判断堆是否已满,若是,则进行扩容
20 void push(const T& srcData); //在树尾部插入,再进行交换
21 void initHeap(const T arr[], int len);
22 T deleteHeap();
23
24 void prePrint(int index = 0)const;
25 size_t size()const;
26 T *getHeapRoot();
27
28
29 private:
30 T *pRoot;
31 size_t len;
32 size_t maxSize;
33
34 };
35
36 template<class T>
37 T * MyHeap<T>::getHeapRoot()
38 {
39 return pRoot;
40 }
41
42 template<class T>
43 size_t MyHeap<T>::size()const
44 {
45 return len;
46 }
47
48 template<class T>
49 void MyHeap<T>::prePrint(int index /*= 0*/)const
50 {
51 if (index < (int)len && index >= 0)
52 {
53 cout << pRoot[index] << " ";
54 prePrint(2 * index + 1);
55 prePrint(2 * index + 2);
56 }
57 }
58
59 template<class T>
60 void MyHeap<T>::clear()
61 {
62 if (pRoot)
63 delete[]pRoot;
64 pRoot = nullptr; //内存释放后 指针置空
65 len = maxSize = 0;
66 }
67
68
69 template<class T>
70 MyHeap<T>::MyHeap(const MyHeap& val)
71 {
72 len = val.len;
73 maxSize = val.maxSize;
74 pRoot = NULL;
75 if (len)
76 {
77 pRoot = new T[len];
78 memmove_s(pRoot, len*sizeof(T), val.pRoot, len*sizeof(T));
79 }
80 }
81
82
83 template<class T>
84 MyHeap<T>::MyHeap()
85 {
86 pRoot = nullptr;
87 len = maxSize = 0;
88 }
89
90 template<class T>
91 MyHeap<T>::~MyHeap()
92 {
93 clear();
94 }
95
96 template<class T>
97 T MyHeap<T>::deleteHeap()
98 {
99 //最大堆删除数据 总是从根节点删除,在把堆中最后一个数拿到根节点,在把根节点往下进行交换
100 if (len==0)
101 throw "there is no data in the heap";
102 T returnVal = pRoot[0]; //删除的元素作为返回值,用于堆排序
103 pRoot[0] = pRoot[len - 1]; //堆中最后一个数放到根节点
104 len--; //删除一个数
105
106 int index = 0;
107 while (true)
108 {
109 int leftChild = 2 * index + 1;
110 int rightChild = 2 * index + 2;
111 if (leftChild >= (int)len) //没有左子节点
112 break;
113 else if (rightChild >= (int)len) //有左子节点 没右子节点
114 {
115 if (pRoot[index]<pRoot[leftChild])
116 {
117 T temp = pRoot[leftChild];
118 pRoot[leftChild] = pRoot[index];
119 pRoot[index] = temp;
120 index = leftChild;
121 }
122 else
123 break;
124 }
125 else //有左子节点 也有右子节点
126 {
127 if (pRoot[leftChild] > pRoot[rightChild])
128 {
129 if (pRoot[index] < pRoot[leftChild])
130 {
131 T temp = pRoot[leftChild];
132 pRoot[leftChild] = pRoot[index];
133 pRoot[index] = temp;
134 index = leftChild;
135 }
136 else
137 break;
138 }
139 else
140 {
141 if (pRoot[index] < pRoot[rightChild])
142 {
143 T temp = pRoot[rightChild];
144 pRoot[rightChild] = pRoot[index];
145 pRoot[index] = temp;
146 index = rightChild;
147 }
148 else
149 break;
150 }
151 }
152 }
153 return returnVal;
154 }
155
156 template<class T>
157 void MyHeap<T>::initHeap(const T arr[], const int length)
158 {
159 clear();
160 len = maxSize = length;
161 pRoot = new T[maxSize];
162 if (len)
163 memmove_s(pRoot, len*sizeof(T), arr, len*sizeof(T)); //将数组中的元素拷贝到堆中,再进行交换
164 else
165 return;
166 for (int i = (len - 1) >> 1; i >= 0; --i)
167 {
168 int index = i; //找到最后一个有子节点的下标
169 while (true) //往下交换
170 {//1.没有左节点 结束循环 2.只有左子节点 3.有右节点 且左节点大 4.右右节点 且右节点大
171 int leftChild = 2 * index + 1;
172 int rightChild = 2 * index + 2;
173 if (leftChild >= (int)len) //没有左子节点 结束循环
174 break;
175 else if (rightChild >= (int)len) // 有左节点 没有右节点
176 {
177 if (pRoot[index] < pRoot[leftChild])
178 {
179 T temp = pRoot[index];
180 pRoot[index] = pRoot[leftChild];
181 pRoot[leftChild] = temp;
182 index = leftChild;
183 }
184 else
185 break;
186 }
187 else //有左节点 也有右节点 ,左右节点中较大的 和当前节点比较
188 {
189 if (pRoot[leftChild] > pRoot[rightChild]) //左节点大
190 {
191 if (pRoot[index] < pRoot[leftChild])
192 {
193 T temp = pRoot[index];
194 pRoot[index] = pRoot[leftChild];
195 pRoot[leftChild] = temp;
196 index = leftChild; //当前节点和 左子节点后,还需要比较左子节点及其子节点大小
197 }
198 else
199 break; //若当前节点大于 左右子节点中最大值
200 }
201 else //右节点大
202 {
203 if (pRoot[index] < pRoot[rightChild])
204 {
205 T temp = pRoot[index];
206 pRoot[index] = pRoot[rightChild];
207 pRoot[rightChild] = temp;
208 index = rightChild; //当前节点和 右子节点后,还需要比较右子节点及其子节点大小
209 }
210 else
211 break; //若当前节点大于 左右子节点中最大值
212 }
213 }
214 }
215 }
216 }
217
218 template<class T>
219 void MyHeap<T>::_sizeExpand()
220 {
221 if (len >= maxSize)
222 {
223 maxSize = maxSize + ((maxSize >> 1) > 1 ? (maxSize >> 1) : 1); //三目运算符的优先级比较低,注意加括号
224 T*temp = new T[maxSize];
225 if (pRoot)
226 {
227 memmove_s(temp, len*sizeof(T), pRoot, len*sizeof(T));
228 delete[]pRoot;
229 }
230 pRoot = temp;
231 }
232 }
233
234 template<class T>
235 void MyHeap<T>::push(const T& srcData)
236 {
237 _sizeExpand(); //判断容器是否已满
238 pRoot[len++] = srcData; //堆尾部插入,在进行交换
239 size_t index = len - 1; //插入数据在堆中的位置
240
241 while (index >= 1)
242 {
243 if (pRoot[index] > pRoot[(index - 1) >> 1] /*&& index >= 1*/) //若插入数据大于他的父亲,则需要进行交换
244 {
245 T temp = pRoot[index];
246 pRoot[index] = pRoot[(index - 1) >> 1];
247 pRoot[(index - 1) >> 1] = temp;
248 index = (index - 1)>>1; //插入数据和父节点交换后,在比较父节点和祖父节点,是否需要交换?
249 }
250 else
251 break;
252 }
253 }
代码测试:
1 // 堆.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5 #include "MyHeap.h"
6 #include <time.h>
7 using namespace std;
8
9 int _tmain(int argc, _TCHAR* argv[])
10 {
11 MyHeap<int> heap;
12
13 int arr[12];
14 srand((unsigned)time(NULL));
15
16 for (int i = 0; i < 10; ++i)
17 arr[i] = rand() % 100;
18 cout << "堆初始化:" << endl;
19 heap.initHeap(arr, 10);
20 heap.prePrint(); cout << endl;
21 cout << "------------------------------------" << endl;
22
23 cout << "向堆中插入数据:" << endl;
24 heap.push(88); heap.prePrint(); cout << endl;
25 heap.push(666); heap.prePrint(); cout << endl;
26 arr[10] = 88; arr[11] = 666;
27 cout << "------------------------------------" << endl;
28
29 cout << "从堆中删除数据:" << endl;
30 int *deleteArr = new int[heap.size()];
31 MyHeap<int> temp(heap);
32 int len = (int)heap.size();
33 for (int i = 0; i < len; ++i)
34 {
35 heap.prePrint(); cout << endl;
36 deleteArr[i]=heap.deleteHeap();
37 }
38
39 cout << "------------------------------------" << endl;
40 cout << "对堆中的数据排序:" << endl;
41 cout << "排序前:" << endl;
42 for (int i = 0; i < 12; ++i)
43 cout << arr[i] << " ";
44 cout << endl;
45
46 cout << "排序后:" << endl;
47 for (int i = 0; i < len; ++i)
48 cout << deleteArr[i] << " ";
49 cout << endl;
50
51 delete[]deleteArr;
52 //heap.prePrint();
53 //heap.prePrint();
54 cin.get();
55 return 0;
56 }
运行结果: