目录
1. 堆的概念及结构
按完全二叉树的顺序存储方式存储 在一个一维数组中,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值。
- 堆总是一棵完全二叉树。
堆的调整算法有多种,其中典型的是:
- 小堆:父亲位,比孩子位,要小。
- 大堆:父亲位,比孩子位,要大。
2.堆的基本功能实现
typedef int HeapDataType; //便于更改堆所存的数据的类型
typedef struct Heap
{
HeapDataType* a; //利用动态开辟所创建的一维数组
int size; //数据个数
int capacity; //空间大小
}HP;
//初始化堆
void HeapInit(HP* ph);
//向上调整(小根堆)
void AdjustUp(HeapDataType* a, int child);
//向下调整(小根堆)
void AdjustDwon(HeapDataType* a, int size, int parent);
//存入数据
void HeapPush(HP* ph, HeapDataType n);
//打印数据
void HeapPrint(HP* ph);
//删除数据(堆顶元素)
void HeapPop(HP* ph);
//所含数据的多少
int HeapSize(HP* ph);
//堆顶元素
HeapDataType HeapTop(HP* ph);
//销毁堆
void HeapDestroy(HP* ph);
//堆是否为空
//为空返回true,非空返回false
bool HeapEmpty(HP* ph);
2.1 初始化堆
//初始化堆
void HeapInit(HP* ph)
{
assert(ph); //防止野指针
//初始化
ph->a = NULL;
ph->capacity = ph->size = 0;
}
2.2 数据交换
//数据交换
void Swap(HeapDataType* e1, HeapDataType* e2)
{
int tmp = *e1;
*e1 = *e2;
*e2 = tmp;
}
2.3 大小堆的调整
下标计算父子间的关系:
- leftchild = parent * 2 + 1
- rightchild = parent * 2 + 2
- parent = (child - 1) / 2 (因为只会保留整数部分)
2.3.1 向上调整(小根堆) -- 多数利用于存入数据
- 如果比父亲小,则交换,然后继续向上比较并调整(最多调整到跟节点就结束了)。
- 如果比父亲大,则调整结束。
(父节点与子节点大小的比较,子对父)
//向上调整(小根堆)
void AdjustUp(HeapDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2.3.2 向下调整(小根堆) -- 多数利用于删除数据
选出左右孩子中小的那一个。
以小的这个孩子跟父亲比较:
- 如果比父亲小,则交换,然后继续向下比较并调整。(最多调整到叶子节点就结束了)
- 如果比父亲大,则调整结束。
(父节点与子节点大小的比较,父对子)
//向下调整(小根堆)
void AdjustDwon(HeapDataType* a, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child]) //在保证两个孩子都存在的同时,挑出最小的
{
++child;
}
if (a[child] < a[parent]) //孩子小于父亲即符合
{
Swap(&a[child], &a[parent]); //交换元素
parent = child;
child = parent * 2 + 1; //更改孩子的定义将原孩子变为下一个孩子
}
else
{
break; //孩子大于父亲,就没必要进行交换了。
}
}
}
2.4 存入数据
//存入数据
void HeapPush(HP* ph, HeapDataType n)
{
assert(ph);
if (ph->size == ph->capacity)
{
ph->capacity = ph->capacity == 0 ? 4 : ph->capacity * 2; //扩容所扩的大小
HeapDataType* tmp = (HeapDataType*)realloc(ph->a, sizeof(HeapDataType) * ph->capacity);
//防止realloc扩容失败
if (NULL == tmp)
{
printf("realloc fail\n");
exit(-1);
}
//扩容成功,放入结构体变量中
ph->a = tmp;
}
//将数据存入
ph->a[ph->size] = n;
//数据已存入,个数即+1
ph->size++;
//向上调整(小根堆)
AdjustUp(ph->a, ph->size - 1);
}
2.5 打印数据
//打印数据
void HeapPrint(HP* ph)
{
assert(ph);
int i = 0;
for (i = 0; i < ph->size; i++)
{
printf("%d ", ph->a[i]);
}
printf("\n");
}
2.6 所含数据的多少
//所含数据的多少
int HeapSize(HP* ph)
{
assert(ph);
return ph->size;
}
2.7 删除数据(堆顶元素)
这里需要考虑到删除头数据的问题,(不用考虑其余位置删除的问题,因为根本没有意义,因为只有顶元素代表最大值(大根堆)或最小值(小根堆))。
而在删除数据中,如果我们运用平时的删除习惯的话:
而我们如果不移动顺序的话,我们可以发现:
如此,利用向下调整,有利于降低时间复杂度。
//删除数据(堆顶元素)
void HeapPop(HP* ph)
{
assert(ph);
assert(ph->size > 0);
ph->a[0] = ph->a[ph->size - 1];
ph->size--;
//向下调整(小根堆)
AdjustDwon(ph->a, ph->size, 0);
}
2.8 堆顶元素
//堆顶元素
HeapDataType HeapTop(HP* ph)
{
assert(ph);
assert(ph->size > 0);
return ph->a[0];
}
2.9 销毁堆
堆此处是利用动态内存开辟的,所以以free归还,是必要的。
//销毁堆
void HeapDestroy(HP* ph)
{
assert(ph);
free(ph->a);
ph->a = NULL;
ph->capacity = ph->size = 0;
}
2.10 判断是否为空
//堆是否为空
//为空返回true,非空返回false
bool HeapEmpty(HP* ph)
{
assert(ph);
return ph->size == 0;
}