n
)
O(\log n)
O(logn),其中
n
n
n 是堆中元素的数量。
堆的应用
堆在计算机科学中有广泛的应用,其中一些主要应用包括:
-
堆排序
堆排序是一种高效的排序算法,它利用堆的性质进行排序。它的时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn),并且具有原地排序的特性。
2. 优先队列
堆可以实现高效的优先级队列,允许以常数时间复杂度找到具有最高优先级的元素,并支持快速的插入和删除操作。
3. Top K 问题
在一组元素中,查找前 K 个最大(或最小)的元素是一个常见的问题。使用堆可以高效地解决这个问题,通过维护一个大小为 K 的最小堆或最大堆,可以快速地找到前 K 个元素。
4. 图算法
在图算法中,堆常用于实现最短路径算法(如Dijkstra算法)和最小生成树算法(如Prim和Kruskal算法)。
5. 数据流中的中位数
对于一个不断变化的数据流,查找其中的中位数也是一个常见的问题。使用两个堆(一个最大堆和一个最小堆),可以高效地实现对数据流中的中位数的查找。
为什么使用数组实现堆
用数组来实现树相关的数据结构也许看起来有点古怪,但是它在时间和空间上都是很高效的。
我们准备将上面图中的大根堆这样存储:
[ 50, 45, 40, 20, 25, 35, 30, 10, 15 ]
就这么多!我们除了一个简单的数组以外,不需要任何额外的空间。
如果我们不允许使用指针,那么我们怎么知道哪一个节点是父节点,哪一个节点是它的子节点呢?问得好!节点在数组中的位置 index 和它的父节点以及子节点的索引之间有一个映射关系。
如果 i 是节点的索引,那么下面的公式就给出了它的父节点和子节点在数组中的位置:
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
注意:right(i) 就是简单的 left(i) + 1。左右节点总是处于相邻的位置。
我们将这些公式放到前面的例子中验证一下。
| Node | Array index (i) | Parent index | Left child | Right child |
|---|---|---|---|---|
| 50 | 0 | -1 | 1 | 2 |
| 45 | 1 | 0 | 3 | 4 |
| 40 | 2 | 0 | 5 | 6 |
| 20 | 3 | 1 | 7 | 8 |
| 25 | 4 | 1 | 9 | 10 |
| 35 | 5 | 2 | 11 | 12 |
| 30 | 6 | 2 | 13 | 14 |
| 10 | 7 | 3 | 15 | 16 |
| 15 | 8 | 3 | 17 | 18 |
注意:根节点(50)没有父节点,因为 -1 不是一个有效的数组索引。同样,节点(25),(35),(30),(10)和(15)没有子节点,因为这些索引已经超过了数组的大小,所以我们在使用这些索引值的时候需要保证是有效的索引值。
复习一下,在最大堆中,父节点的值总是要大于(或者等于)其子节点的值。这意味下面的公式对数组中任意一个索引 i 都成立:
array[parent(i)] >= array[i]
可以用上面的例子来验证一下这个堆属性。
如你所见,这些公式允许我们不使用指针就可以找到任何一个节点的父节点或者子节点。
堆的基本操作
以下是堆的一些基本操作:
- 插入:将一个元素插入到堆中,并保持堆的特性。
- 删除根节点:删除堆的根节点,并保持堆的特性。
- 获取根节点:获取堆的根节点的值,通常是堆中最大或最小的值。
- 堆化(Heapify):对一个无序的数组进行堆化操作,将其转换为一个堆。
C语言
以下是使用C语言实现堆(包括创建堆、插入数据、删除根结点、获取根节点和堆化等基础操作)的示例代码:
#include <stdio.h>
#define MAX_HEAP_SIZE 100
typedef struct {
int heap[MAX_HEAP_SIZE];
int size;
} Heap;
void initializeHeap(Heap *h) {
h->size = 0;
}
void insert(Heap *h, int value) {
if (h->size >= MAX_HEAP_SIZE) {
printf("Heap is full.\n");
return;
}
int i = h->size;
h->heap[i] = value;
h->size++;
// 调整堆的结构
while (i > 0 && h->heap[(i - 1) / 2] < h->heap[i]) {
int temp = h->heap[i];
h->heap[i] = h->heap[(i - 1) / 2];
h->heap[(i - 1) / 2] = temp;
i = (i - 1) / 2;
}
}
int removeRoot(Heap *h) {
if (h->size <= 0) {
printf("Heap is empty.\n");
return -1;
}
int root = h->heap[0];
h->size--;
h->heap[0] = h->heap[h->size];
// 调整堆的结构
int i = 0;
while (2 * i + 1 < h->size) {
int leftChild = 2 * i + 1;
int rightChild = 2 * i + 2;
int largerChild = leftChild;
if (rightChild < h->size && h->heap[rightChild] > h->heap[leftChild]) {
largerChild = rightChild;
}
if (h->heap[i] >= h->heap[largerChild]) {
break;
}
int temp = h->heap[i];
h->heap[i] = h->heap[largerChild];
h->heap[largerChild] = temp;
i = largerChild;
}
return root;
}
void heapify(Heap *h, int arr[], int n) {
initializeHeap(h);
// 将数组元素逐个插入堆中
for (int i = 0; i < n; i++) {
做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。
别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。
我先来介绍一下这些东西怎么用,文末抱走。
* * *
**(1)Python所有方向的学习路线(新版)**
这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
最近我才对这些路线做了一下新的更新,知识体系更全面了。

**(2)Python学习视频**
包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

**(3)100多个练手项目**
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

**(4)200多本电子书**
这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。
基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。
**(5)Python知识点汇总**
知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。

**(6)其他资料**
还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

**这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。**
**了解详情:https://docs.qq.com/doc/DSnl3ZGlhT1RDaVhV**