堆的实现
在上节课,我们学到了,完全二叉树更适合使用顺序结构存储。因此,现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。下面我们先来回顾一下完全二叉树的规律:
leftchild = parent * 2 + 1
rightchild = parent * 2 + 2
parent = ( child - 1 ) / 2
接下来,简单复习一下堆:
堆:非线性结构,完全二叉树
小堆:树中任意一个父亲都<=孩子
大堆:树中任意一个父亲都>=孩子
接下来,即使代码的实现了
1.定义一个结构体
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
2.堆的初始化
void HeapInot(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
3.堆的销毁
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
4.堆的插入
由于是存放在数组里面的,所以插入数据时,直接选择尾插。
void HeapPush(HP* php, HPDataType x)
{
assert(php);
php->a[php->size] = x;
php->size++;
}
这时候,我们可能考虑到直接尾插,可能空间不够,需要扩容。
//扩容
//如果数组里面元素个数 == 空间大小,就说明没有地方尾插新的元素
if (php->size == php->capacity)
{
//使用条件操作符,因为前面初始化全是0
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
//使用 realloc 来扩容
HPDataType* tmp = (HPDataType *)realloc(php->a, sizeof(HPDataType) * newCapacity);
//检查是否扩容
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
我们接下来进行一个简单的图解:
我们把上面这个过程叫做向上调整。我们来进行向上调整的代码实现:
void AdjustUp(HPDataType* a,int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
插入的整体代码如下:
void HeapPush(HP* php, HPDataType x)
{
assert(php);
//扩容
//如果数组里面元素个数 == 空间大小,就说明没有地方尾插新的元素
if (php->size == php->capacity)
{
//使用条件操作符,因为前面初始化全是0
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
//使用 realloc 来扩容
HPDataType* tmp = (HPDataType *)realloc(php->a, sizeof(HPDataType) * newCapacity);
//检查是否扩容
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
5.堆的打印
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; i++)
{
printf("%d", php->a[i]);
}
printf("\n");
}
6.堆的删除
为了让堆的删除更有意义,我们选择删除根。
为了形成小堆,我们开始向下调整。
接下来,我们先来用代码实现向下调整:
void AdjustDown(HPDataType* a,int n,int parent)
{
//默认child指向左孩子
int child = parent * 2 + 1;
while (child<n)
{
//帮助找出较小的孩子
if (child + 1 < n && a[child] > a[child + 1])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
//继续向下调整
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
删除的整体代码如下:
void HeapPop(HP* php)
{
assert(php);
//确保有元素可以删
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;
AdjustDown(php->a,php->size, 0);
}
7.取堆顶
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
8.判断是否为空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
9.简单排序(但不等同于堆排序)
由于HeapPop函数是删除根,因此每次会将最小的数字再放在根的位置。排序函数就可以借此完成:
//排序:
while (!HeapEmpty(&hp))
{
//取堆顶数据
printf("%d ", HeapTop(&hp));
//取次小的数据
HeapPop(&hp);
}
不等同于堆排序的原因是,堆排序是对数组内的元素进行排序,现在的这个简单排序是通过删除根来实现的,因此不等同于堆排序。
写了这么多了,我们来检验一下:
#include"Heap.h"
int main()
{
int a[] = { 2,3,5,7,4,6,8 };
HP hp;
HeapInit(&hp);
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
HeapPush(&hp, a[i]);
}
HeapPrint(&hp);
//排序:
while (!HeapEmpty(&hp))
{
//取堆顶数据
printf("%d ", HeapTop(&hp));
//取次小的数据
HeapPop(&hp);
}
HeapDestory(&hp);
return 0;
}
本届内容就分享到这里了,下次再见!