【数据结构】堆的实现以及建堆算法和堆排序
🔥个人主页:大白的编程日记
🔥专栏:数据结构
文章目录
- 【数据结构】堆的实现以及建堆算法和堆排序
- 前言
- 一.堆的实现
- 1.1 堆数据的插入
- 1.2堆数据的删除
- 二.建堆算法和堆排序
- 2.1思路分析
- 2.2向上建堆算法
- 2.3向下调整建堆
- 2.4堆排序
- 后言
前言
哈喽,各位小伙伴大家好!上期给大家讲了树,二叉树以及堆。今天带着大家实现堆这个数据结构,以及堆排序。话不多说,咱们进入正题!向大厂冲锋!
一.堆的实现
- 堆的定义
我们用数组控制堆,在物理上是一个数组。逻辑上想象成堆。然后用size记录堆的节点个数。后面涉及扩容,所以用capacity记录数组空间大小。这里我们实现的是小堆
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
- 堆的初始化
初始化时我们可以先给堆开好空间,也可以不开。我们不开就先给NULL,然后size和capacity都先给0。
void HPInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}//初始化
- 堆的销毁
我们先free销毁数组,在把size和capacity置为0.
void HPDestroy(HP* php)
{
assert(php);
free(php->a);//销毁数组
php->a = NULL;
php->capacity = php->size = 0;
}//销毁
1.1 堆数据的插入
- 思路分析
想要实现堆的插入,我们需要在数组插入数据后进行向上调整。
- 堆数据插入
插入数据前我们需要检查一下是否需要扩容。
第一次没开空间,我们就给4个数据的空间。
否则我们就realloc扩容为2倍。
最后在赋值。然后size++更新节点个数。
再用向上调整。
void HPPush(HP* php, HPDataType x)
{
assert(php);//断言
if (php->size == php->capacity)//空间满
{
int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail~");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size++] = x;//插入
AdjustUp(php->a, php->size);//向上调整
}
- 向上调整算法
我们size是数据个数,所以插入的child节点下标为size-1.那我们要找到他的父亲节点就是(child - 1) / 2。
然后判断插入数据和父亲节点的大小关系是否满足堆。
不满足就交换父子节点。然后更新child节点的下标。
满足则停止。或直到child节点到根节点时停止。
void AdjustUp(HPDataType* a, int size)
{
int child = size - 1;//最后的节点
while (child > 0)
{
int parent = (child - 1) / 2;//父亲节点
if (a[child] < a[parent])//判断
{
Swap(&a[child], &a[parent]);//交换
child = parent;
}
else
{
break;//调整完成
}
}
}
1.2堆数据的删除
- 思路分析
所以我们需要把最后一个节点和堆的根节点交换后,删除最后一个节点,然后向下调整。
- 堆数据的删除
我们先Swap交换根节点和最后一个节点,然后size–删除最后一个节点。再进行向下调整。
void HPPop(HP* php)//删除根节点
{
assert(php);
assert(php->size );//判断是否为空
Swap(&php->a[0], &php->a[php->size-1]);//交换根节点和最后一个节点
php->size--;//删除堆最后一个数据
AdjustDown(php->a,php->size,0);//向下调整
}
- 向下调整算法
我们先算parent的左孩子child的下标。
然后用假设法找出左右孩子最大或最小的那个孩子,注意有可能只有左孩子没有右孩子。所以我们用min+1<size判断是否存在右孩子。然后再将找出的最大或最小孩子与parent节点进行比较。如果不满足堆的大小关系就交换,然后更新parent的下标和新的child的下标。
满足就break停止循环,或当child<=size说明此时parent为叶子节点停止循环。
void AdjustDown(HPDataType* a, int size,int parent)
{
int child = parent * 2 + 1;//孩子节点
while (child<size)
{
int min = child;//左右孩子中最小的孩子
if (min+1<size&&a[min] > a[min + 1])//防止没有右孩子
{
min = child + 1;
}//假设法
if (a[parent] > a[min])//判断
{
Swap(&a[parent], &a[min]);//交换
parent = min;
child = parent * 2 + 1;
}
else
{
break;//调整完毕
}
}
}
- 堆的判空
直接判断是否size为0
bool HPEmpyt(HP* php)
{
assert(php);
return php->size == 0;//判空
}
- 堆的数据个数
直接返回size即可
int HPSize(HP* php)
{
assert(php);
return php->size;//返回数据个数
}
- 堆顶元素
直接返回下标为0的位置数据即可。
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size);//判断是否为空
return php->a[0];//返回根节点
}
二.建堆算法和堆排序
现在我们要实现堆排序,那我们需要建堆。
那升序建大堆还是小堆,降序建大堆还是小堆呢?还是都可以呢?
升序建大堆 降序建小堆 为什么呢?
2.1思路分析
那如何建堆呢?
有两种建堆算法。
2.2向上建堆算法
我们堆插入的过程其实就是建堆的过程。
for (int i = 1; i < n; i++)
{
AdjustUp(p,i);
}
2.3向下调整建堆
我们从最后一个父亲节点依次往后向下调整
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(p, n, i);
}
2.4堆排序
实现堆排序,我们升序建大堆,降序建小堆。
两种建堆算法都可以。
然后我们用end表示最后一个堆元素
每次循环交换堆顶元素和最后一个堆元素。
然后堆顶元素向下调整。更新最后一个堆元素即可。
void HeapSort(HPDataType* p, int n)
{
for (int i = 1; i < n; i++)//向上建堆
{
AdjustUp(p,i);
}
int end = n - 1;//最后一个堆元素
while (end > 0)//
{
Swap(&p[0], &p[end]);//交换堆顶元素和最后一个堆元素
AdjustDown(p,end,0);//向下调整
end--;//更新最后一个堆元素
}
}
- 验证
void HeapSort(HPDataType* p, int n)
{
for (int i = 1; i < n; i++)//向上建堆
{
AdjustUp(p,i);
}
int end = n - 1;//最后一个堆元素
while (end > 0)//
{
Swap(&p[0], &p[end]);//交换堆顶元素和最后一个堆元素
AdjustDown(p,end,0);//向下调整
end--;//更新最后一个堆元素
}
}
void test2()
{
int a[] = { 9,6,4,7,10,5,3,1,2,8};
HeapSort(a, sizeof(a) / sizeof(a[0]));
for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
}
int main()
{
test2();
return 0;
}
后言
这就堆的实现以及建堆算法和堆排序。这都是数据结构的重中之重。
大家一定要多加掌握。今天就分享到这,感谢各位大佬的耐心垂阅!咱们下期见!拜拜~