大家好,我是白晨,一个不是很能熬夜,但是也想日更的人✈。如果喜欢这篇文章,点个赞,关注一下白晨吧!你的支持就是我最大的动力!
上一篇文章,我们详细介绍了二叉树的入门知识(如果没有二叉树基础的同学建议先看一下二叉树入门,我在本篇文章也会对其中二叉树中比较重要的点再次提及),在有了二叉树的相关知识后,我们就可以着手实现一些有着更多特性的数据结构了。今天,这篇文章我们要介绍到的数据结构是一种用来排序或者找寻数据中最大、最小的若干数据的树——堆。
上一篇文章我们讲到,二叉树的顺序存储结构一般只适用于完全二叉树,并且顺序存储结构的父子结点之间还有一些数学联系,现在我们来回顾一下。
一棵二叉树中最后一层结点以上结点构成满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
上图中,第四层以上的前三层构成满二叉树结构,最后一层从左到右结点连续,所以上树为一棵完全二叉树。
完全二叉树有如下特性:
对于具有n
个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i
的结点有:
i
>0,i
位置结点的双亲序号:(i
-1)/2;i
=0,i
为根结点编号,无双亲结点i
位置结点的孩子序号:
i
+1;i
+2;i
+1=n,则无左孩子i
+2=n,则无右孩子如果有一个关键码的集合K = { k 0 k_0 k0, k 1 k_1 k1 , k 2 k_2 k2 ,…, k n − 1 k_{n-1} kn−1 },把它的所有元素按
完全二叉树
的顺序存储方式存储在一个一维数组
中,并满足: k i k_{i} ki <= k 2 i + 1 k_{2i+1} k2i+1 且 k i k_i ki<= k 2 i + 2 k_{2i+2} k2i+2 ( k i k_i ki >= k 2 i + 1 k_{2i+1} k2i+1 且 k i k_i ki >= k 2 i + 2 k_{2i+2} k2i+2 ),i
= 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。翻译一下就是:
- 如果一棵完全二叉树树上父节点都小于等于子节点,那么这棵完全二叉树就被称为
小堆
。下图为逻辑结构:
下表为存储结构:
下标 0 1 2 3 4 5 数据 1 2 3 6 5 4
下标 0 1 2 3 4 5 数据 6 4 5 1 2 3 从以上定义中我们可以得到堆的两个性质:
- 堆一定是完全二叉树
- 堆中某个节点的值总是不大于或不小于其父节点的值
特别注意
:堆只要求父节点与子节点的关系,并没有要求前一层与后一层的关系,更没有要求存储顺序必须是有序的.
前面已经提过,堆的存储结构是顺序存储,所以需要用到顺序表(如果没有顺序表基础的同学建议先去了解一下顺序表<-点击跳转),以下操作均会涉及到顺序表的相关操作。
堆的代码实现与顺序表没有任何区别,所以这里我们就不再赘述:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
这样一个堆的存储结构就搭建完毕了,现在我们来实现一下几个简单的接口函数。
// 堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的打印
void HeapPrint(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
由于堆的结构也是顺序表,所以初始化与销毁与销毁与顺序表没有任何区别,我们可以直接实现。
void HeapInit(Heap* hp)
{
assert(hp);
hp->a = NULL;
hp->size = 0;
hp->capacity = 0;
}
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->a);
hp->capacity = 0;
hp->size = 0;
}
这个操作只需要遍历一遍堆中元素就可以。
void HeapPrint(Heap* hp)
{
assert(hp);
int i = 0;
for (i = 0; i < HeapSize(hp); i++)
{
printf("%d ", hp->a[i]);
}
printf("\n");
}
堆的堆顶元素在物理上就是顺序表数组下标为0的元素,所以我们只需要保证栈不为空就可以拿到这个数据。
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->a[0];
}
size
大小size
是否为0,如为0,返回真;如不为0,返回假int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
堆的查找
int HeapSearch(Heap* hp, HPDataType x)
{
assert(hp);
for (int i = 0; i < hp->size; i++)
{
if (hp->a[i] == x)
{
return i;
}
}
return -1;
}
我们通过上面学习了解到堆对父子结点的大小关系是有一定要求的,如果现在给我们一个完全二叉树,我们该如何将其变为大堆呢?(从这里开始,我们全都使用大堆来举例子,小堆的情况只需要根据大堆的思想类比就可以)
先放置这个问题,我们先来看一种情况:
我们可以发现这个完全二叉树除了根结点以外,其余的结点都满足大堆的结构,我们如何调整这个完全二叉树可以使其成为一个大堆呢?
这时候我们发现,根结点已经是全部结点中最大的了,这就说明根结点已经调整好了,不用再换了。
0
与3
这两个结点的大小,发现0<3,不满足大堆的要求,所以让0
与3
换调整完毕,我们得到了一个小堆。
通过上面的过程,我们得到了以下经验:
如果左右子树不是大堆,那会出现什么情况呢?
这个堆的左右子树就全不为大堆,我们现在来向下调整根结点。
3
换0
与5
交换我们发现0
结点已经调整完毕,但是这个完全二叉树仍然不是大堆。
所以,我们可以得出结论,向下调整必须要求根结点的左右子树为大堆(或者小堆)。
所以,我们可以根据以上过程抽象出一个向下调整的通法(大堆):
N
的左右子树都是大堆。N
与孩子结点的大小关系。如果N
大于等于两个孩子结点,调整结束;不然就让N
与较大的孩子交换。N
调整结束或者被调整到叶子结点。小堆类比大堆思想就可以:
N
的左右子树都是小堆。N
与孩子结点的大小关系。如果N
小于等于两个孩子结点,调整结束;不然就让N
与较小的孩子交换。N
调整结束或者被调整到叶子结点。上述过程N
结点始终不变,向下调整的意思就是,向下调整N
结点到应在的位置
根据以上思想,我们可以实现向下调整:
// 大堆向下调整
// 向下调整结束条件
// 1. 被调整结点 >= 孩子
// 2. 调整到叶子节点
void AdjustDown_big(HPDataType* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
//孩子必须在堆的范围内
while (child < n)
{
//选出两个孩子中最大的进行交换,并且要保证右孩子存在
if (a[child + 1] > a[child] && child + 1 < n)
{
child++;
}
//孩子大于父亲,则交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
// 小堆向下调整
// 向下调整结束条件
// 1. 被调整结点 <= 孩子
// 2. 调整到叶子节点
void AdjustDown_little(HPDataType* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
//孩子必须在堆的范围内
while (child < n)
{
//选出两个孩子中最小的进行交换,并且要保证右孩子存在
if (a[child + 1] < a[child] && child + 1 < n)
{
child++;
}
//孩子小于父亲,则交换
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
这里我们假设我们堆就是满二叉树,向下调整一次最坏情况的时间复杂度为 O ( l o g 2 n ) O(log_2~n) O(log2 n)
我们现在回到最开始提出的问题,一个完全二叉树怎么才能调整为一个大堆?
因为向下调整要保证左右子树都是大堆,所以我们不可能从根结点开始调整,但是我们可以倒着调整。
3
结点5
3.这时根结点的左右子树就是大堆了,我们现在来调整它
调整完毕,这时我们就得到了一个大堆。
具体调整过程如下:
现在我们就可以抽象出一种向下调整建堆的通法:
最后,再补充一点向下建堆的时间复杂度(了解即可)
来看这样一种情况,现在在一个大堆后面插入一个数9
,由于9>6,所以现在构不成大堆了,要怎么调整才能将其再变为大堆呢?
6
和9
结点。8
和9
,8 < 9,所以根据大堆规则,交换8
和9
。9
已经被调整到根结点,调整结束。同样我们可以上述过程中得到一些经验:
从以上我们可以抽象出向上调整的过程(大堆):
N
以前的结点构成一个大堆。N
与其父节点比较大小,如果N
大于父节点,则交换这两个结点;如果N
小于等于父节点,则结束调整。N
被调整调整完毕或者调整到根结点。小堆的向上调整类比大堆可得:
N
以前的结点构成一个小堆。N
与其父节点比较大小,如果N
小于父节点,则交换这两个结点;如果N
大于等于父节点,则结束调整。N
被调整调整完毕或者调整到根结点。// 大堆向上调整
// 调整结束条件:
// 1.父节点 >= 被调整结点
// 2.被调整结点已经调整到根结点
void AdjustUp_big(HPDataType* a, int child)
{
assert(a);
while (child > 0)
{
int parent = (child - 1) / 2;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
child = parent;
}
}
// 小堆向上调整
// 调整结束条件:
// 1.父节点 <= 被调整结点
// 2.被调整结点已经调整到根结点
void AdjustUp_little(HPDataType* a, int child)
{
assert(a);
//当child为0时,结束循环
while (child > 0)
{
int parent = (child - 1) / 2;
//孩子小于父亲时交换,否则退出
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
child = parent;
}
}
那么向上调整可不可以将一个完全二叉树调整成堆呢?
答案是可以的,但是向上调整前提是被调整的结点以前的结点必须构成一个堆,所以必须要从上到下向上调整,具体要从第二个结点开始调整,保证前面的都是大堆,一直要调整到最后一个结点。
所以向上调整建堆所花费的时间要比向下调整建堆花费的时间长,一般调整一棵完全二叉树成堆我们使用向下调整法。
向上调整的真正用处在于在堆后面插入数据,我们就要用向上调整将这个新插入的数据调整到正确的位置上,使这棵完全二叉树再建成堆。这个用处我们后面还会提到。
上文提到了向上调整一般用来插入数据,现在我们就来具体探讨一下堆的插入。
具体代码实现:
// 大堆数据的插入
void HeapPush_big(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->capacity == hp->size)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->a, newCapacity*sizeof(HPDataType));
if (tmp == NULL)
{
printf("relloc fail");
exit(-1);
}
hp->capacity = newCapacity;
hp->a = tmp;
}
hp->a[hp->size] = x;
AdjustUp_big(hp->a, hp->size);
hp->size++;
}
// 小堆数据的插入
void HeapPush_little(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->capacity == hp->size)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = realloc(hp->a, newCapacity * sizeof(HPDataType));
if (tmp == NULL)
{
printf("relloc fail");
exit(-1);
}
hp->capacity = newCapacity;
hp->a = tmp;
}
hp->a[hp->size] = x;
AdjustUp_little(hp->a, hp->size);
hp->size++;
}
代码逻辑运行过程:
堆的删除是指删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
堆的删除指的是删除堆顶的元素,也就是说删除顺序表数组中下标为0的元素。
直接删除堆顶元素肯定行不通,因为直接删除这个元素会,将数据整体向前移动一位,这样大概率会破坏堆的结构,所以我们要利用堆的特性删除栈顶元素。
我们以大堆为例,要删除栈顶元素(也就是整个栈中最大的元素)
小堆
具体实例如下:
具体代码实现如下:
// 大堆数据的删除
void HeapPop_big(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_big(hp->a, hp->size, 0);
}
// 小堆数据的删除
void HeapPop_little(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_little(hp->a, hp->size, 0);
}
代码逻辑运行过程:
进阶
:
我们可以思考一个问题,堆如何实现任意删除?
任意删除其实思想仍是上述思想,我们知道移除一个元素会破坏大堆或者小堆属性,所以我们需要将要删除的元素和最后一个元素交换,再向下调整原最后一个元素。
具体思路:
具体代码实现如下:
// 大堆任意删除元素
void HeapDelect_big(Heap* hp,int x)
{
assert(hp);
assert(!HeapEmpty(hp));
assert(x >= 0 && x < hp->size);
Swap(&hp->a[x], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_big(hp->a, hp->size, x);
}
// 小堆任意删除元素
void HeapDelect_little(Heap* hp,int x)
{
assert(hp);
assert(!HeapEmpty(hp));
assert(x >= 0 && x < hp->size);
Swap(&hp->a[x], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_little(hp->a, hp->size, x);
}
其实上面的任意删除函数仍然有优化的空间,因为下标是随着堆的结构变化不停变动的,所以使用一次删除前就必须查找一次。
但是我们如果可以改进查找函数,使其返回对应位置的指针,在删除时,传递指针,找到此数据进行删除。此功能较好实现,大家可以自己试一试将其实现。
最后,堆的删除大部分都是删除堆顶元素,所以任意删除不是必须要掌握,大家做一了解即可。
我们已经了解了堆的结构、特性和常用的接口函数,现在就可以将其应用到日常的问题中。堆最常用的应用一般有两个——Topk问题和堆排序。
Topk问题指的是选出一株数据中最大或者最小的几个。
如,王者荣耀中英雄的国服最强,考试成绩的全校前十等
如果给我们一组数据,如果要让我们选出最大的10个数,那么我们可能有以下思路:
方式2到方式1的时间复杂度小了很多,但是这两个方法都有一个致命的缺陷——空间复杂度为 O ( N ) O(N) O(N)。
我们来假设一种场景,如果我们有10亿个数据,内存中无法存储这么多数据,那应该怎么办呢?
有机智的小伙伴可能就会说用外部排序不就可以给磁盘中的数据排序了吗?这是一种可行的方法,但是如果为了找10个数据就要排序全部的数据,时间成本和消耗都太大,得不偿失。
所以,现在就是体现我们堆结构优越性的地方了,我们可以得到第三种方式:
选出最小的数也是一个道理:
特别提醒
:这种方法只是选出最大或者最小的前k个数,并不会给这k个数排序。
具体代码实现如下:
// 找出最大的前k个数
void PrintTopK(int* a, int n, int k)
{
assert(a);
// 建小堆
Heap hp;
HeapInit(&hp);
for (int i = 0; i < k; i++)
{
HeapPush_little(&hp, a[i]);
}
// 堆顶元素与其余元素比较
for (int i = k; i < n; i++)
{
if (a[i] > HeapTop(&hp))
{
hp.a[0] = a[i];
AdjustDown_little(hp.a, hp.size, 0);
}
}
HeapPrint(&hp);
HeapDestory(&hp);
}
// 找出最小的前k个数
void PrintTopK(int* a, int n, int k)
{
assert(a);
// 建大堆
Heap hp;
HeapInit(&hp);
for (int i = 0; i < k; i++)
{
HeapPush_big(&hp, a[i]);
}
// 堆顶元素与其余元素比较
for (int i = k; i < n; i++)
{
if (a[i] > HeapTop(&hp))
{
hp.a[0] = a[i];
AdjustDown_big(hp.a, hp.size, 0);
}
}
HeapPrint(&hp);
HeapDestory(&hp);
}
堆排序
(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
所以说堆排序就是利用堆的性质完成排序的过程,堆排序主要妙就妙在它的思想上,这里我们直接来看堆排序的思想:
总共分为两个步骤:
建堆
升序:建大堆
降序:建小堆利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
具体解释(升序):
降序的思想也与升序相似:
具体实例图解如下:
堆排序动态的逻辑过程:
具体代码实现:
// 堆排序升序
void HeapSortUp(HPDataType* a, int n)
{
assert(a);
// 向下调整建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown_big(a, n, i);
}
// 将最后一个元素与堆顶元素交换,选出最大的元素,堆大小减一,然后向下调整,选出此大的元素,以此类推
for (int i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown_big(a, i, 0);
}
}
// 堆排序降序
void HeapSortDown(HPDataType* a, int n)
{
assert(a);
// 向下调整建小堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown_little(a, n, i);
}
// 将最后一个元素与堆顶元素交换,选出最小的元素,堆大小减一,然后向下调整,选出此大的元素,以此类推
for (int i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown_little(a, i, 0);
}
}
代码逻辑动态展示:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
// 堆的构建
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 向上调整
void AdjustUp_big(HPDataType* a, int child);
void AdjustUp_little(HPDataType* a, int child);
// 堆的插入
void HeapPush_big(Heap* hp, HPDataType x);
void HeapPush_little(Heap* hp, HPDataType x);
// 堆的打印
void HeapPrint(Heap* hp);
// 堆的删除
void HeapPop_big(Heap* hp);
void HeapPop_little(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
// 堆的查找
int HeapSearch(Heap* hp, HPDataType x);
// 堆的任意删除
void HeapDelect_big(Heap* hp, int x);
void HeapDelect_little(Heap* hp, int x);
// TopK问题:找出N个数里面最大/最小的前K个问题。
// 需要注意:
// 找最大的前K个,建立K个数的小堆
// 找最小的前K个,建立K个数的大堆
void PrintTopK(int* a, int n, int k);
// 对数组进行堆排序
void HeapSortUp(HPDataType* a, int n);
void HeapSortDown(HPDataType* a, int n);
void HeapInit(Heap* hp)
{
assert(hp);
hp->a = NULL;
hp->size = 0;
hp->capacity = 0;
}
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->a);
hp->capacity = 0;
hp->size = 0;
}
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->a[0];
}
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
void HeapPrint(Heap* hp)
{
assert(hp);
int i = 0;
for (i = 0; i < HeapSize(hp); i++)
{
printf("%d ", hp->a[i]);
}
printf("\n");
}
void Swap(HPDataType* x, HPDataType* y)
{
HPDataType tmp = *x;
*x = *y;
*y = tmp;
}
void AdjustUp_big(HPDataType* a, int child)
{
assert(a);
while (child > 0)
{
int parent = (child - 1) / 2;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
child = parent;
}
}
void AdjustUp_little(HPDataType* a, int child)
{
assert(a);
//当child为0时,结束循环
while (child > 0)
{
int parent = (child - 1) / 2;
//孩子小于父亲时交换,否则退出
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
child = parent;
}
}
void HeapPush_big(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->capacity == hp->size)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->a, newCapacity*sizeof(HPDataType));
if (tmp == NULL)
{
printf("relloc fail");
exit(-1);
}
hp->capacity = newCapacity;
hp->a = tmp;
}
hp->a[hp->size] = x;
AdjustUp_big(hp->a, hp->size);
hp->size++;
}
void HeapPush_little(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->capacity == hp->size)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = realloc(hp->a, newCapacity * sizeof(HPDataType));
if (tmp == NULL)
{
printf("relloc fail");
exit(-1);
}
hp->capacity = newCapacity;
hp->a = tmp;
}
hp->a[hp->size] = x;
AdjustUp_little(hp->a, hp->size);
hp->size++;
}
void AdjustDown_big(HPDataType* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
//孩子必须在堆的范围内
while (child < n)
{
//选出两个孩子中最大的进行交换,并且要保证右孩子存在
if (a[child + 1] > a[child] && child + 1 < n)
{
child++;
}
//孩子大于父亲,则交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void AdjustDown_little(HPDataType* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
//孩子必须在堆的范围内
while (child < n)
{
//选出两个孩子中最小的进行交换,并且要保证右孩子存在
if (a[child + 1] < a[child] && child + 1 < n)
{
child++;
}
//孩子小于父亲,则交换
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapPop_big(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_big(hp->a, hp->size, 0);
}
void HeapPop_little(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_little(hp->a, hp->size, 0);
}
int HeapSearch(Heap* hp, HPDataType x)
{
assert(hp);
for (int i = 0; i < hp->size; i++)
{
if (hp->a[i] == x)
{
return i;
}
}
return -1;
}
void PrintTopK(int* a, int n, int k)
{
assert(a);
Heap hp;
HeapInit(&hp);
for (int i = 0; i < k; i++)
{
HeapPush_little(&hp, a[i]);
}
for (int i = k; i < n; i++)
{
if (a[i] > HeapTop(&hp))
{
hp.a[0] = a[i];
AdjustDown_little(hp.a, hp.size, 0);
}
}
HeapPrint(&hp);
HeapDestory(&hp);
}
void HeapSortUp(HPDataType* a, int n)
{
assert(a);
// 向下调整建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown_big(a, n, i);
}
// 将最后一个元素与堆顶元素交换,选出最大的元素,堆大小减一,然后向下调整,选出此大的元素,以此类推
for (int i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown_big(a, i, 0);
}
}
void HeapSortDown(HPDataType* a, int n)
{
assert(a);
// 向下调整建小堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown_little(a, n, i);
}
// 将最后一个元素与堆顶元素交换,选出最小的元素,堆大小减一,然后向下调整,选出此大的元素,以此类推
for (int i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown_little(a, i, 0);
}
}
void HeapDelect_big(Heap* hp,int x)
{
assert(hp);
assert(!HeapEmpty(hp));
assert(x >= 0 && x < hp->size);
Swap(&hp->a[x], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_big(hp->a, hp->size, x);
}
void HeapDelect_little(Heap* hp,int x)
{
assert(hp);
assert(!HeapEmpty(hp));
assert(x >= 0 && x < hp->size);
Swap(&hp->a[x], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown_little(hp->a, hp->size, x);
}
恭喜呀!你已经掌握了堆的全部用法了!!距离大佬又近了一步!!!
写这篇文章对白晨来说也是一种挑战,特别是堆的精华部分——向下调整算法和向上调整算法,希望大家看的还算清楚明白。
白晨会不断提高自己的制图水平和语言描述能力,希望能让每个人都看得清楚明白,大家有什么想法都可以私信白晨呀,无论是提意见还是交朋友,白晨都会一一回复的。
最后如果大家喜欢我这篇文章,不如给我一个大拇指
和小星星
⭐️,支持一下白晨吧!喜欢白晨《数据结构》系列的话,不如关注
白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。