️Take your time ! ️
个人主页:大魔王
代码仓库:魔王修炼之路
所属专栏:魔王的修炼之路–数据结构
如果你觉得这篇文章对你有帮助,请在文章结尾处留下你的点赞和关注,支持一下博主。同时记得收藏✨这篇文章,方便以后重新阅读。
上一篇博客详细讲述了二叉树的概念,那么接下来这篇博客将讲述二叉树的存储方式的一种:顺序存储。
堆分为大根堆和小根堆。
大根堆:所有父节点都不小于子节点。
小根堆:所有父节点都不大于子节点。
需要注意的是:兄弟节点之间没有什么联系,可以大于可以小于也可以相等。
实现堆的方法:有两种,分别为向上调整建堆和向下调整建堆。
向上调整建堆:就像刚才说的,我们进行调整时其本身必须为一个堆,因此我们只能一点一点的调整,所以从第二个开始向上调整(因为第一个没有比较的东西,所以从第二个开始调整),使每次比较都能是在原本就是堆的基础上进行。如图:从红色位置开始向上调整建堆。
//向上调整建大堆
#include
void AdjustUp(int* a,int n)
{
int child = n;
int parent = (child - 1) / 2;
while (child > 0)
//判断条件不能为parent,因为parent为整型,但是最后那个结果是-0.5,对于整型来说,浮点数小数点后的忽略,那么此时parent还是0,child也变成0了,它们的值会相等然后return,虽然这样程序照样过了,但是这种想法其实是错的,所以我们通过判断child是否>0来作为是否结束的标志。
{
if (a[child] > a[parent])
{
int temp = a[child];
a[child] = a[parent];
a[parent] = temp;
}
else
return;//如果子节点小于父亲节点那么本次循环就不用比了,因为再向上比的那些之前已经调整过了。
child = parent;
parent = (parent - 1) / 2;
}
}
int main()
{
int a[7] = { 2,5,6,7,4,9,3 };
int size = sizeof(a) / sizeof(int);
int n = 1;
while (n < size)
{
AdjustUp(a, n);
n++;
}
for (int n = 0; n < size; n++)
{
printf("%d ", a[n]);
}
return 0;
}
向下调整建堆:和向上调整很像,也必须一点一点的建,只不过是从下面开始,我们跳过最后一层,层倒数第二层开始逐个向下调整(确保除了要调整的这个,其余参与的节点本身构成一个堆)。如图:
//向下调整建大堆
#include
void AdjustDown(int* a, int n,int size)
{
int parent = n;
int child = n * 2 + 1;//先假设左子树是孩子中较大的,然后再进行判断,看是否替换为右子树(右子树需要存在)。
while (child < size)
{
if (child + 1 < size && a[child] < a[child + 1])//第一个判断的原因是首先右子树要存在才能比较。
{
child = child + 1;
}
if (a[parent] < a[child])
{
int temp = a[parent];
a[parent] = a[child];
a[child] = temp;
}
parent = child;
child = child * 2 + 1;
}
}
int main()
{
int a[7] = { 2,5,6,7,4,9,3};
int size = sizeof(a) / sizeof(int);
int n = (size - 2) / 2;
while (n >= 0)
{
AdjustDown(a, n,size);
n--;
}
n = 0;
while (n < size)
{
printf("%d ", a[n]);
n++;
}
return 0;
}
向上调整与这个证明过程类似。
先将这个元素插入到尾部,然后让这个数据进行向上调整就行了。
让根与最后一个元素交换位置,然后让数组删除最后一个元素(size–),然后让这个取代根位置的元素进行向下调整即可。
#pragma once
typedef int HeapDateType;
#include
#include
#include
typedef struct Heap
{
HeapDateType* a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapCreat(Heap* php);
//堆的销毁
void HeapDestory(Heap* php);
//堆的插入
void HeapPush(Heap* php, HeapDateType x);
//堆的删除
void HeapPop(Heap* php);
//取堆顶的数据
HeapDateType HeapTop(Heap* php);
//堆的数据个数
int HeapSize(Heap* php);
//堆的判空
int HeapEmpty(Heap* php);
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//堆的初始化
void HeapInit(Heap* php)
{
assert(php);
php->a = (HeapDateType*)malloc(sizeof(HeapDateType) * 4);
if (php->a == NULL)
{
perror("malloc error");
assert(php);
}
php->size = 0;
php->capacity = 4;
}
//堆的销毁
void HeapDestory(Heap* php)
{
assert(php);
free(php->a);
php->capacity = 0;
php->size = 0;
}
//数据交换
void Swap(HeapDateType* a, HeapDateType* b)
{
int temp = 0;
temp = *b;
*b = *a;
*a = temp;
}
void AdjustUp(HeapDateType* a, int n)
{
assert(a);
int child = n - 1;
int parent = (child - 1) / 2;
while (child > 0)
//判断条件不能为parent,因为parent为整型,但是最后那个结果是-0.5,对于整型来说,浮点数小数点后的忽略,那么此时parent还是0,child也变成0了,它们的值会相等然后return,虽然这样程序照样过了,但是这种想法其实是错的,所以我们通过判断child是否>0来作为是否结束的标志。
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);//后面还会用到交换,所以封装成一个函数。
}
else
return;
child = parent;
parent = (parent - 1) / 2;
}
}
//堆的插入
void HeapPush(Heap* php,HeapDateType x)
{
assert(php);
if (php->capacity == php->size)
{
HeapDateType* inspect = realloc(php->a, sizeof(HeapDateType) * php->capacity * 2);
if (inspect == NULL)
{
perror("realloc error");
assert(inspect);
}
php->a = inspect;
inspect = NULL;
php->capacity *= 2;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size);
}
//向上向下调整的前提是必须调整前是一个堆(大堆或者小堆)
void AdjustDown(HeapDateType* a, int n,int parent)
{
assert(a);
int child = parent*2+1;//默认向下调整是跟左孩子交换,如果下面比较左右孩子大小时右孩子大,那么让child+1即可表示与右孩子交换。
while (child <= n)
{
if (child < n&& (a[child] < a[child + 1]))//前提是要有右兄弟,不然就会越界
{
child = child + 1;
}
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
}
else
return;
parent = child;
child = child * 2 + 1;
}
}
//堆的删除(删除的是堆顶,也就是根)
void HeapPop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->a[php->size - 1], & php->a[0]);//直接让最后一个覆盖根也行,因为根是要删掉的,就算这样交换了,下面size--后,根还是没有了。
php->size--;
AdjustDown(php->a, php->size,0);
}
//取堆顶的数据
HeapDateType HeapTop(Heap* php)
{
assert(php);
return php->a[0];
}
//堆的数据个数
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
//堆的判空
int HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
int main()
{
Heap hp;
HeapInit(&hp);
HeapPush(&hp, 0);
HeapPush(&hp, 47);
HeapPush(&hp, 26);
HeapPush(&hp, 48);
HeapPush(&hp, 52);
HeapPush(&hp, 79);
HeapPush(&hp, 6);
HeapPush(&hp, 83);
HeapPush(&hp, 38);
HeapPush(&hp, 24);
HeapPop(&hp);
HeapPop(&hp);
HeapPop(&hp);
HeapPop(&hp);
return 0;
}
对于升序建大堆来说:我们采用的方法是每次将根与最后一个元素交换位置,然后让这个堆忽略掉末尾元素(size–)(也就是原来的根,因为它已经到了应该的位置了),然后一直这样,直到这个堆只剩下一个元素,那么此时就排好了。
//堆排序实现
#include
#include
#include
//数据交换
void Swap(int* a, int* b)
{
int temp = 0;
temp = *b;
*b = *a;
*a = temp;
}
void AdjustUp(int* a, int n)
{
assert(a);
int child = n - 1;
int parent = (child - 1) / 2;
while (parent >= 0)
//判断条件不能为parent,因为parent为整型,但是最后那个结果是-0.5,对于整型来说,浮点数小数点后的忽略,那么此时parent还是0,child也变成0了,它们的值会相等然后return,虽然这样程序照样过了,但是这种想法其实是错的,所以我们通过判断child是否>0来作为是否结束的标志。
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);//后面还会用到交换,所以封装成一个函数。
}
else
return;
child = parent;
parent = (parent - 1) / 2;
}
}
//向上向下调整的前提是必须调整前是一个堆(大堆或者小堆)
void AdjustDown(int* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;//默认向下调整是跟左孩子交换,如果下面比较左右孩子大小时右孩子大,那么让child+1即可表示与右孩子交换。
while (child < n)
{
if (child < n - 1 && (a[child] < a[child + 1]))//前提是要有右兄弟,不然就会越界
{
child = child + 1;
}
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
}
else
return;
parent = child;
child = child * 2 + 1;
}
}
void HeapSort(int* arr, int size)//假设要求升序排列
{
向上调整建大堆--时间复杂度为N*logN
//for (int n = 2; n < size; n++)
//{
// AdjustUp(arr, n);
//}
//向下调整建堆--时间复杂度为N
for (int n = (size - 2) / 2; n >= 0; n--)//size-1是指最后一个元素的下标,另一个-1是公式套的
{
AdjustDown(arr, size, n);
}
//排序--第一个和最后一个替换,然后让新根向下调整即可,这样就让该堆中最大的数放到了该放的位置。
while (size--)
{
Swap(&arr[0], &arr[size]);
AdjustDown(arr, size, 0);
}
}
int main()
{
int arr[] = { 45,26,91,34,82,41,67,81,47,35 };
HeapSort(arr, sizeof(arr) / sizeof(int));
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
求最大的前k个:建小堆。
求最小的前k个,建大堆。
专栏推荐
魔王的修炼之路–C语言
魔王的修炼之路–数据结构初阶
魔王的修炼之路–C++
魔王的修炼之路–Linux
更新不易,希望得到友友的三连支持一波。收藏这篇文章,意味着你将永久拥有它,无论何时何地,都可以立即找到重新阅读;关注博主,意味着无论何时何地,博主将永久和你一起学习进步,为你带来有价值的内容。