返回 LeetCode 刷题

Markdown File

堆排序等操作

堆排序等操作.md

1一、堆排序 Heap Sort
21. 前提条件
3
4堆排序用的是:二叉堆
5
6如果要升序排序:用大根堆
7
8因为大根堆堆顶是最大值。
9
102. 核心思想
11
12升序排序:
13
141. 先把数组建成大根堆
152. 堆顶就是最大值
163. 把堆顶和最后一个元素交换
174. 堆大小减 1
185. 对新的堆顶做下滤
196. 重复直到排序完成
203. 为什么用大根堆升序?
21
22例如:
23
24[4, 1, 7, 3]
25
26建大根堆后:
27
28堆顶 = 最大值 7
29
30把 7 放到数组最后:
31
32[?, ?, ?, 7]
33
34然后继续找剩下元素最大值,放到倒数第二个。
35
36所以最后就是升序。
37
384. 大根堆下滤代码
39void swap(int* a, int* b)
40{
41 int temp = *a;
42 *a = *b;
43 *b = temp;
44}
45
46void siftDownMax(int arr[], int size, int i)
47{
48 while(1)
49 {
50 int left = 2 * i + 1;
51 int right = 2 * i + 2;
52 int largest = i;
53
54 if(left < size && arr[left] > arr[largest])
55 {
56 largest = left;
57 }
58
59 if(right < size && arr[right] > arr[largest])
60 {
61 largest = right;
62 }
63
64 if(largest == i)
65 {
66 break;
67 }
68
69 swap(&arr[i], &arr[largest]);
70
71 i = largest;
72 }
73}
74
755. 建大根堆
76void buildMaxHeap(int arr[], int size)
77{
78 for(int i = size / 2 - 1; i >= 0; i--)
79 {
80 siftDownMax(arr, size, i);
81 }
82}
83
84为什么从:
85
86size / 2 - 1
87
88开始?
89
90因为这是最后一个非叶子节点。
91
92叶子节点没有孩子,天然满足堆性质。
93
946. 堆排序完整代码
95void heapSort(int arr[], int size)
96{
97 buildMaxHeap(arr, size);
98
99 for(int end = size - 1; end > 0; end--)
100 {
101 swap(&arr[0], &arr[end]);
102
103 siftDownMax(arr, end, 0);
104 }
105}
1067. 例子流程
107arr = [4, 1, 7, 3]
108
109建大根堆后,可能变成:
110
111[7, 3, 4, 1]
112
113交换堆顶和最后:
114
115[1, 3, 4, 7]
116
117现在 7 已经排好。
118
119对前 3 个元素下滤:
120
121[4, 3, 1, 7]
122
123交换堆顶和下标 2:
124
125[1, 3, 4, 7]
126
127对前 2 个元素下滤:
128
129[3, 1, 4, 7]
130
131交换:
132
133[1, 3, 4, 7]
134
135排序完成。
136
1378. 堆排序复杂度
138建堆:O(n)
139
140每次删除堆顶:O(log n)
141
142总共删除 n 次:
143
144O(n log n)
145
146空间复杂度:
147
148O(1)
149
150因为直接在原数组上交换。
151
1529. 易错点
153升序排序用大根堆
154
155降序排序用小根堆
156
157堆排序不是稳定排序
158
159siftDown 的 size 会不断变小
160
161交换到末尾的元素已经排好,不再参与堆调整
162
163二、优先队列 Priority Queue
1641. 前提条件
165
166普通队列:
167
168先进先出 FIFO
169
170优先队列:
171
172优先级高的先出
173
174例如:
175
176医院急诊
177任务调度
178Dijkstra 最短路
179Top K 问题
1802. 核心概念
181
182优先队列通常用堆实现。
183
184小根堆:
185
186每次弹出最小值
187
188大根堆:
189
190每次弹出最大值
1913. 优先队列 ADT
192
193常用操作:
194
195init 初始化
196push 插入元素
197top 查看最高优先级元素
198pop 删除最高优先级元素
199isEmpty 判空
200三、小根堆优先队列代码
2011. 结构体
202#include <stdlib.h>
203
204struct PriorityQueue
205{
206 int* data;
207 int size;
208 int capacity;
209};
2102. 创建优先队列
211struct PriorityQueue* createPriorityQueue(int capacity)
212{
213 struct PriorityQueue* pq =
214 malloc(sizeof(struct PriorityQueue));
215
216 pq->data = malloc(capacity * sizeof(int));
217 pq->size = 0;
218 pq->capacity = capacity;
219
220 return pq;
221}
2223. 判空
223int isEmpty(struct PriorityQueue* pq)
224{
225 return pq->size == 0;
226}
2274. push:插入元素
228
229小根堆,插入后上滤。
230
231int push(struct PriorityQueue* pq, int x)
232{
233 if(pq->size == pq->capacity)
234 {
235 return 0;
236 }
237
238 int i = pq->size;
239 pq->data[i] = x;
240 pq->size++;
241
242 while(i > 0)
243 {
244 int parent = (i - 1) / 2;
245
246 if(pq->data[parent] <= pq->data[i])
247 {
248 break;
249 }
250
251 swap(&pq->data[parent], &pq->data[i]);
252
253 i = parent;
254 }
255
256 return 1;
257}
2585. top:查看堆顶
259int top(struct PriorityQueue* pq, int* x)
260{
261 if(isEmpty(pq))
262 {
263 return 0;
264 }
265
266 *x = pq->data[0];
267
268 return 1;
269}
2706. pop:删除堆顶
271int pop(struct PriorityQueue* pq, int* x)
272{
273 if(isEmpty(pq))
274 {
275 return 0;
276 }
277
278 *x = pq->data[0];
279
280 pq->data[0] = pq->data[pq->size - 1];
281 pq->size--;
282
283 int i = 0;
284
285 while(1)
286 {
287 int left = 2 * i + 1;
288 int right = 2 * i + 2;
289 int smallest = i;
290
291 if(left < pq->size && pq->data[left] < pq->data[smallest])
292 {
293 smallest = left;
294 }
295
296 if(right < pq->size && pq->data[right] < pq->data[smallest])
297 {
298 smallest = right;
299 }
300
301 if(smallest == i)
302 {
303 break;
304 }
305
306 swap(&pq->data[i], &pq->data[smallest]);
307
308 i = smallest;
309 }
310
311 return 1;
312}
3137. 释放
314void destroyPriorityQueue(struct PriorityQueue* pq)
315{
316 free(pq->data);
317 free(pq);
318}
319四、优先队列完整模板
320#include <stdlib.h>
321
322struct PriorityQueue
323{
324 int* data;
325 int size;
326 int capacity;
327};
328
329void swap(int* a, int* b)
330{
331 int temp = *a;
332 *a = *b;
333 *b = temp;
334}
335
336struct PriorityQueue* createPriorityQueue(int capacity)
337{
338 struct PriorityQueue* pq =
339 malloc(sizeof(struct PriorityQueue));
340
341 pq->data = malloc(capacity * sizeof(int));
342 pq->size = 0;
343 pq->capacity = capacity;
344
345 return pq;
346}
347
348int isEmpty(struct PriorityQueue* pq)
349{
350 return pq->size == 0;
351}
352
353int push(struct PriorityQueue* pq, int x)
354{
355 if(pq->size == pq->capacity)
356 {
357 return 0;
358 }
359
360 int i = pq->size;
361 pq->data[i] = x;
362 pq->size++;
363
364 while(i > 0)
365 {
366 int parent = (i - 1) / 2;
367
368 if(pq->data[parent] <= pq->data[i])
369 {
370 break;
371 }
372
373 swap(&pq->data[parent], &pq->data[i]);
374
375 i = parent;
376 }
377
378 return 1;
379}
380
381int top(struct PriorityQueue* pq, int* x)
382{
383 if(isEmpty(pq))
384 {
385 return 0;
386 }
387
388 *x = pq->data[0];
389
390 return 1;
391}
392
393int pop(struct PriorityQueue* pq, int* x)
394{
395 if(isEmpty(pq))
396 {
397 return 0;
398 }
399
400 *x = pq->data[0];
401
402 pq->data[0] = pq->data[pq->size - 1];
403 pq->size--;
404
405 int i = 0;
406
407 while(1)
408 {
409 int left = 2 * i + 1;
410 int right = 2 * i + 2;
411 int smallest = i;
412
413 if(left < pq->size && pq->data[left] < pq->data[smallest])
414 {
415 smallest = left;
416 }
417
418 if(right < pq->size && pq->data[right] < pq->data[smallest])
419 {
420 smallest = right;
421 }
422
423 if(smallest == i)
424 {
425 break;
426 }
427
428 swap(&pq->data[i], &pq->data[smallest]);
429
430 i = smallest;
431 }
432
433 return 1;
434}
435
436void destroyPriorityQueue(struct PriorityQueue* pq)
437{
438 free(pq->data);
439 free(pq);
440}
441
442五、堆排序 vs 优先队列
443堆排序:
444
445目标是把整个数组排好序
446
447核心操作:
448 建堆
449 反复把堆顶放到末尾
450
451--------------------------------
452
453优先队列:
454
455目标是动态维护最大/最小值
456
457核心操作:
458 push
459 top
460 pop
461六、最终笔记总结
462堆排序:
463
464升序:
465 建大根堆
466
467步骤:
468 1. 建大根堆
469 2. 堆顶和末尾交换
470 3. 堆大小减一
471 4. 对堆顶下滤
472 5. 重复
473
474复杂度:
475 O(n log n)
476
477空间:
478 O(1)
479
480--------------------------------
481
482优先队列:
483
484普通队列:
485 先进先出
486
487优先队列:
488 优先级最高先出
489
490实现:
491 二叉堆
492
493小根堆:
494 top 是最小值
495
496大根堆:
497 top 是最大值
498
499操作:
500 push O(log n)
501 pop O(log n)
502 top O(1)
503
504堆排序是“用堆把数组排好”;优先队列是“用堆动态维护当前最大或最小”。
Rendered Preview

一、堆排序 Heap Sort

  1. 前提条件

堆排序用的是:二叉堆

如果要升序排序:用大根堆

因为大根堆堆顶是最大值。

  1. 核心思想

升序排序:

  1. 先把数组建成大根堆
  2. 堆顶就是最大值
  3. 把堆顶和最后一个元素交换
  4. 堆大小减 1
  5. 对新的堆顶做下滤
  6. 重复直到排序完成
  7. 为什么用大根堆升序?

例如:

[4, 1, 7, 3]

建大根堆后:

堆顶 = 最大值 7

把 7 放到数组最后:

[?, ?, ?, 7]

然后继续找剩下元素最大值,放到倒数第二个。

所以最后就是升序。

  1. 大根堆下滤代码
    void swap(int* a, int* b)
    {
    int temp = *a;
    *a = *b;
    *b = temp;
    }

void siftDownMax(int arr[], int size, int i)
{
while(1)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;

    if(left < size && arr[left] > arr[largest])
    {
        largest = left;
    }

    if(right < size && arr[right] > arr[largest])
    {
        largest = right;
    }

    if(largest == i)
    {
        break;
    }

    swap(&arr[i], &arr[largest]);

    i = largest;
}

}

  1. 建大根堆
    void buildMaxHeap(int arr[], int size)
    {
    for(int i = size / 2 - 1; i >= 0; i--)
    {
    siftDownMax(arr, size, i);
    }
    }

为什么从:

size / 2 - 1

开始?

因为这是最后一个非叶子节点。

叶子节点没有孩子,天然满足堆性质。

  1. 堆排序完整代码
    void heapSort(int arr[], int size)
    {
    buildMaxHeap(arr, size);

    for(int end = size - 1; end > 0; end--)
    {
    swap(&arr[0], &arr[end]);

     siftDownMax(arr, end, 0);
    

    }
    }

  2. 例子流程
    arr = [4, 1, 7, 3]

建大根堆后,可能变成:

[7, 3, 4, 1]

交换堆顶和最后:

[1, 3, 4, 7]

现在 7 已经排好。

对前 3 个元素下滤:

[4, 3, 1, 7]

交换堆顶和下标 2:

[1, 3, 4, 7]

对前 2 个元素下滤:

[3, 1, 4, 7]

交换:

[1, 3, 4, 7]

排序完成。

  1. 堆排序复杂度
    建堆:O(n)

每次删除堆顶:O(log n)

总共删除 n 次:

O(n log n)

空间复杂度:

O(1)

因为直接在原数组上交换。

  1. 易错点
    升序排序用大根堆

降序排序用小根堆

堆排序不是稳定排序

siftDown 的 size 会不断变小

交换到末尾的元素已经排好,不再参与堆调整

二、优先队列 Priority Queue

  1. 前提条件

普通队列:

先进先出 FIFO

优先队列:

优先级高的先出

例如:

医院急诊
任务调度
Dijkstra 最短路
Top K 问题
2. 核心概念

优先队列通常用堆实现。

小根堆:

每次弹出最小值

大根堆:

每次弹出最大值
3. 优先队列 ADT

常用操作:

init 初始化
push 插入元素
top 查看最高优先级元素
pop 删除最高优先级元素
isEmpty 判空
三、小根堆优先队列代码

  1. 结构体
    #include <stdlib.h>

struct PriorityQueue
{
int* data;
int size;
int capacity;
};
2. 创建优先队列
struct PriorityQueue* createPriorityQueue(int capacity)
{
struct PriorityQueue* pq =
malloc(sizeof(struct PriorityQueue));

pq->data = malloc(capacity * sizeof(int));
pq->size = 0;
pq->capacity = capacity;

return pq;

}
3. 判空
int isEmpty(struct PriorityQueue* pq)
{
return pq->size == 0;
}
4. push:插入元素

小根堆,插入后上滤。

int push(struct PriorityQueue* pq, int x)
{
if(pq->size == pq->capacity)
{
return 0;
}

int i = pq->size;
pq->data[i] = x;
pq->size++;

while(i > 0)
{
    int parent = (i - 1) / 2;

    if(pq->data[parent] <= pq->data[i])
    {
        break;
    }

    swap(&pq->data[parent], &pq->data[i]);

    i = parent;
}

return 1;

}
5. top:查看堆顶
int top(struct PriorityQueue* pq, int* x)
{
if(isEmpty(pq))
{
return 0;
}

*x = pq->data[0];

return 1;

}
6. pop:删除堆顶
int pop(struct PriorityQueue* pq, int* x)
{
if(isEmpty(pq))
{
return 0;
}

*x = pq->data[0];

pq->data[0] = pq->data[pq->size - 1];
pq->size--;

int i = 0;

while(1)
{
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int smallest = i;

    if(left < pq->size && pq->data[left] < pq->data[smallest])
    {
        smallest = left;
    }

    if(right < pq->size && pq->data[right] < pq->data[smallest])
    {
        smallest = right;
    }

    if(smallest == i)
    {
        break;
    }

    swap(&pq->data[i], &pq->data[smallest]);

    i = smallest;
}

return 1;

}
7. 释放
void destroyPriorityQueue(struct PriorityQueue* pq)
{
free(pq->data);
free(pq);
}
四、优先队列完整模板
#include <stdlib.h>

struct PriorityQueue
{
int* data;
int size;
int capacity;
};

void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}

struct PriorityQueue* createPriorityQueue(int capacity)
{
struct PriorityQueue* pq =
malloc(sizeof(struct PriorityQueue));

pq->data = malloc(capacity * sizeof(int));
pq->size = 0;
pq->capacity = capacity;

return pq;

}

int isEmpty(struct PriorityQueue* pq)
{
return pq->size == 0;
}

int push(struct PriorityQueue* pq, int x)
{
if(pq->size == pq->capacity)
{
return 0;
}

int i = pq->size;
pq->data[i] = x;
pq->size++;

while(i > 0)
{
    int parent = (i - 1) / 2;

    if(pq->data[parent] <= pq->data[i])
    {
        break;
    }

    swap(&pq->data[parent], &pq->data[i]);

    i = parent;
}

return 1;

}

int top(struct PriorityQueue* pq, int* x)
{
if(isEmpty(pq))
{
return 0;
}

*x = pq->data[0];

return 1;

}

int pop(struct PriorityQueue* pq, int* x)
{
if(isEmpty(pq))
{
return 0;
}

*x = pq->data[0];

pq->data[0] = pq->data[pq->size - 1];
pq->size--;

int i = 0;

while(1)
{
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int smallest = i;

    if(left < pq->size && pq->data[left] < pq->data[smallest])
    {
        smallest = left;
    }

    if(right < pq->size && pq->data[right] < pq->data[smallest])
    {
        smallest = right;
    }

    if(smallest == i)
    {
        break;
    }

    swap(&pq->data[i], &pq->data[smallest]);

    i = smallest;
}

return 1;

}

void destroyPriorityQueue(struct PriorityQueue* pq)
{
free(pq->data);
free(pq);
}

五、堆排序 vs 优先队列
堆排序:

目标是把整个数组排好序

核心操作:
建堆
反复把堆顶放到末尾


优先队列:

目标是动态维护最大/最小值

核心操作:
push
top
pop
六、最终笔记总结
堆排序:

升序:
建大根堆

步骤:
1. 建大根堆
2. 堆顶和末尾交换
3. 堆大小减一
4. 对堆顶下滤
5. 重复

复杂度:
O(n log n)

空间:
O(1)


优先队列:

普通队列:
先进先出

优先队列:
优先级最高先出

实现:
二叉堆

小根堆:
top 是最小值

大根堆:
top 是最大值

操作:
push O(log n)
pop O(log n)
top O(1)

堆排序是“用堆把数组排好”;优先队列是“用堆动态维护当前最大或最小”。