Skip to content

Commit 1c998c9

Browse files
committed
[U] 更新堆和优先队列
1 parent 6783dad commit 1c998c9

10 files changed

Lines changed: 434 additions & 2 deletions

File tree

notes/datastructures/堆和优先队列.md

Lines changed: 232 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
## 1. 二叉堆
77

88
二叉堆是一棵完全二叉树,如下图:
9-
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heapandpriorityqueue01.png)
9+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap01.png)
10+
11+
完全二叉树性质如下:
12+
- 元素顺序排列成树的形状
13+
- 堆中某节点的值总是不大于其父节点的值
14+
- 上图这种父亲节点值最大,则表示的是最大堆
15+
16+
同理,可以让父亲节点值最小,则可以被称为最小堆。
1017

1118
这里,给出一个最大堆和最小堆的定义:
1219

@@ -27,25 +34,248 @@
2734
0 1 2 3 4 5 6 7 8 9
2835
```
2936
将数组转化为二叉堆:
30-
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heapandpriorityqueue02.png)
37+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap02.png)
3138

3239
对于上面的二叉堆,有以下三个比较重要的公式:
3340

3441
```
42+
(i从0开始计算)
3543
parent(i) = (i - 1) / 2 求当前节点的父节点的索引
3644
left child(i) = 2 * i + 1 求当前节点的左子节点的索引
3745
right child(i) = 2 * i + 2 求当前节点的右子节点的索引
3846
```
3947

48+
构建代码:
49+
```
50+
public class MaxHeap {
51+
52+
/**
53+
* 注意,由于数组元素默认值为0,所以堆中元素不可为0
54+
*/
55+
private int[] data;
56+
57+
/**
58+
* 记录数组中元素个数
59+
*/
60+
private int size;
61+
62+
/**
63+
* 数组容量(初始化大小)
64+
*/
65+
private int capacity;
66+
67+
public MaxHeap(int capacity) {
68+
data = new int[capacity];
69+
this.capacity = capacity;
70+
size = 0;
71+
}
72+
public MaxHeap() {
73+
data = new int[10];
74+
capacity = 10;
75+
size = 0;
76+
}
77+
78+
// ================================ heapify(二叉堆化) ========================
79+
80+
public MaxHeap(int[] data) {
81+
int n = data.length;
82+
this.data = new int[n+1];
83+
capacity = n;
84+
85+
for (int i = 0; i < n; i++) {
86+
this.data[i+1] = data[i];
87+
}
88+
size = n;
89+
90+
// 这里需要注意,size / 2 得到的索引是二叉堆中最后一个非叶子节点。
91+
for (int i = size / 2; i > 0; i++) {
92+
siftDown(i);
93+
}
94+
}
95+
96+
/**
97+
* 返回堆中真实存在元素个数
98+
* @return
99+
*/
100+
public int size() {
101+
return size;
102+
}
103+
104+
/**
105+
* 返回堆中是否为空
106+
* @return
107+
*/
108+
public boolean isEmpty() {
109+
return size == 0;
110+
}
111+
112+
/**
113+
* 返回index索引其父亲节点
114+
* @param index
115+
* @return
116+
*/
117+
private int parent(int index) {
118+
if (index == 0) {
119+
throw new IllegalArgumentException("index-0 doesn't have parent");
120+
}
121+
return (index - 1) / 2;
122+
}
123+
124+
/**
125+
* 返回index索引的左子节点
126+
* @param index
127+
* @return
128+
*/
129+
private int leftChild(int index) {
130+
return index * 2 + 1;
131+
}
132+
133+
/**
134+
* 返回index索引的右子节点
135+
* @param index
136+
* @return
137+
*/
138+
private int rightChild(int index) {
139+
return index * 2 + 2;
140+
}
141+
142+
// ================================ 上浮 siftUp ========================
143+
144+
/**
145+
* 向堆中添加元素
146+
*/
147+
public void add(int i) {
148+
// 注意size和capacity的关系,判断是否容量已经满了
149+
// 向数组最后一位添加新元素
150+
data[size] = i;
151+
siftUp(size++);
152+
}
153+
154+
/**
155+
* 上浮过程
156+
* @param k
157+
*/
158+
private void siftUp(int k) {
159+
// k 索引大于0,并且k索引元素值大于k父亲节点元素值
160+
while (k > 0 && data[parent(k)] < data[k]) {
161+
// k和parent(k)互换元素
162+
swap(data, k, parent(k));
163+
// 向上移动,让k为parent(k)再进行判断
164+
k = parent(k);
165+
}
166+
}
167+
168+
// ================================ 下浮 siftDown ========================
169+
170+
/**
171+
* 获取堆中最大元素
172+
* @return
173+
*/
174+
public int findMax() {
175+
if (size == 0) {
176+
throw new IllegalArgumentException("Can't find Max value!");
177+
}
178+
return data[0];
179+
}
180+
181+
/**
182+
* 取出堆中最大元素
183+
* @return
184+
*/
185+
public int extractMax() {
186+
int max = findMax();
187+
swap(data, 0, size - 1);
188+
data[size - 1] = 0;
189+
siftDown(0);
190+
return max;
191+
}
192+
193+
/**
194+
* 下沉操作
195+
* @param k
196+
*/
197+
private void siftDown(int k) {
198+
// 左子节点比数组元素小,则表示有子节点
199+
while (leftChild(k) < size()) {
200+
int j = leftChild(k);
201+
// 如果k的有右子节点
202+
if (j + 1 < size() && data[j + 1] > data[j]) {
203+
// 所以让j为右子节点
204+
// j = rightChild(k); 同
205+
j++;
206+
}
207+
// 此时data[k] 比leftChild和rightChild中的最大值都要大
208+
if (data[k] > data[j]) {
209+
break;
210+
}
211+
// leftChild和rightChild都比data[k]大,在互换之后,继续下一轮判断
212+
swap(data, k, j);
213+
// 互换位置,继续下一轮判断
214+
k = j;
215+
}
216+
}
217+
218+
// ================================ 替换操作 ========================
219+
220+
private void swap(int[] arr, int i, int j) {
221+
int tmp = arr[i];
222+
arr[i] = arr[j];
223+
arr[j] = tmp;
224+
}
225+
226+
public static void main(String[] args) {
227+
int n = 100;
228+
MaxHeap maxHeap = new MaxHeap(n);
229+
Random random = new Random();
230+
for (int i = 0; i < n; i++) {
231+
maxHeap.add(random.nextInt(1000));
232+
}
233+
int[] result = new int[n];
234+
for (int i = 0; i < n; i++) {
235+
result[i] = maxHeap.extractMax();
236+
}
237+
// 测试是否是顺序的
238+
for (int j = 1; j < n; j++) {
239+
if (result[j-1] < result[j]) {
240+
throw new IllegalArgumentException("Error!");
241+
}
242+
}
243+
}
244+
}
245+
```
246+
247+
上浮Sift Up过程如下图:
248+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap03.png)
249+
250+
插入的新节点与父亲节点比较大小,如果比父亲节点大,则进行swap交换,直至到符合完全二叉树性质。
251+
252+
取出最大值以及下沉过程:
253+
先取出最大值,让数组最后一位元素替换堆顶元素,然后往下沉。
254+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap04.png)
255+
256+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap05.png)
257+
258+
heapify二叉堆化,即从最后一个非叶子节点开始siftDown,即图中蓝色图表示的节点元素。
259+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap06.png)
260+
261+
40262
## 2. 优先队列
41263

264+
> 普通队列:先进先出,后进后出;
265+
266+
> 优先队列:出队顺序和入队顺序无关;和优先级有关;
267+
268+
在Windows操作系统中,任务管理器的任务就是根据优先级别来进行管理的,**动态选择优先级最高的任务执行。**
269+
42270
普通线性结构和顺序线性结构对比
43271
| | 入队 | 出队(拿出最大元素) |
44272
| :----: | :-----: | :--------: |
45273
| 普通线性结构 | O(1) | O(n) |
46274
| 顺序线性结构 | O(n) | O(1) |
47275
|| O(logn) | O(logn) |
48276

277+
对于线性结构出队时(数组),需要扫描整个队列;对于顺序线性结构(链表),入队时由于需要维持顺序性则需要扫描整个队列;
278+
49279
**普通线性结构**入队O(1)分析,因为入队不考虑顺序性,所以排在队尾即可。
50280

51281
**顺序线性结构**入队O(n)分析,入队需要考虑顺序性,需要进行调度。

notes/pictures/heap01.png

66.7 KB
Loading

notes/pictures/heap02.png

71.7 KB
Loading

notes/pictures/heap03.png

83.8 KB
Loading

notes/pictures/heap04.png

76.1 KB
Loading

notes/pictures/heap05.png

77.4 KB
Loading

notes/pictures/heap06.png

72.1 KB
Loading
-11.8 KB
Binary file not shown.
-11.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)