目录
堆的概念及结构
堆:1 完全二叉树
任何一个数组都可以看成一个完全二叉树,所以堆的物理结构就是数组,逻辑结构是一颗完全二叉树
2 大堆:树中任何一个父亲都大于或者等于孩子
小堆:树中任何一个父亲都小于或者等于孩子
3 父子下标关系 leftchild = parent*2+1 rightchild = parent*2+2
parent = (child-1)/2
堆的实现
堆的向上调整算法
代码实现:
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整算法--建小堆
void AdjustUp(int* 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;
}
}
}
思路详解:
向上调整建堆其实就是一个插入的过程,向上调整算法的前提是插入之前,已经是堆
下面我以建小堆的思路来介绍:
每插入一个元素,可能需要多次向上调整,即可能会与自己所有的祖先节点比较,首先和其父亲 (下标是:(child-1)/2 )比较,若是小于父亲则二者交换,此元素作为孩子向上调到父亲的位置,然后再和这个位置上的父亲比较,过程一样……,若是大于父亲则无需向上调堆,位置合适
孩子节点何时停止向上调整 ?
1 孩子节点处在合适的位置 即孩子节点大于父亲节点
or:孩子节点调整至堆顶,调无可调
插入的孩子60比父亲56大,位置合适,插入后还是小堆,无需向上调整
插入的孩子50小于其父亲56,需要向上调整:
孩子50和父亲56交换位置,现在孩子50处在父亲56的位置,然后求出孩子50现在的下标,即child=parent,继而求出孩子50现在的父亲下标 parent = (child-1)/2
孩子50小于现在的父亲10,位置合适,结构保持为堆,无需继续向上调整
孩子8比父亲56小,向上调整,调到父亲56的位置,继续与现在位置上的父亲10比较,小于,向上调整, 调到父亲10的位置,此时已经处在堆顶,下标为0,无需继续调堆
堆的向下调整算法
代码实现:
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向下调整算法--建小堆
void AdjustDown(int* a, int n,int parent)
{
int child = parent * 2 + 1;//假设左右孩子中最小的孩子是左孩子
while (child < n)//只要一个父亲节点还有左孩子,则有继续向下调整的可能
{
if (child + 1 < n && a[child + 1] < a[child])//右孩子存在且右孩子<左孩子,假设不成立,更换有孩子为最小的孩子,child更新为右孩子的下标,右孩子的下标为左孩子的下标+1
{
child++;
}
if (a[child] < a[parent])//左右孩子中最小的孩子比父亲小,交换二者位置
{
Swap(&a[child], &a[parent]);
parent = child;//父亲节点现在处在最小孩子的位置
child = parent * 2 + 1;//父亲节点现在最小孩子(依然先做假设)
}
else//左右孩子中最小的孩子都比父亲节点大,父亲节点无需向下调整,位置合适,保持小堆
{
break;
}
}
}
思路详解:
向下调整算法有一个前提:左右子树必须是一个堆,才能调整
1 从父节点开始向下调整
先选出左右孩子中最小/最大的一个,然后与其比较:下面我按小堆的形式介绍
若是父亲小于孩子中最小的一个,则无需向下调整
若是父亲大于孩子中最小的一个,则二者交换,父亲调至最小孩子的位置
再求出它现在最小孩子的下标,二者比较,重复上述过程
2 父节点何时停止向下调整:
父节点处在合适的位置,父节点小于最小的孩子
or:父节点调至叶子节点,调无可调,此时的父节点是没有左孩子的,故而当父亲的要与之交换的孩子下标越界后,就无需再向下调整了
堆的创建:
堆结构的定义及相关函数的声明:
堆是一颗完全二叉树,任何一个数组都可以看成一颗完全二叉树,所以堆结构就可以用数组来实现
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(int* a, int n, int parent);
堆的初始化:
void HeapInit(HP* php)
{
assert(php);//断言,确保堆指针不为空
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
堆的销毁:
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
堆的插入:
void HeapPush(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(int) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);//每插入一个元素需要向上调整,以保持堆结构
}
堆的删除:
删除的是堆顶的元素,方法是:
1 将堆顶元素与最后一个元素交换
2 删除最后一个元素,即实现删除堆顶元素的效果
3 从现在的堆顶位置开始向下调整,以维持堆结构不变
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));//为空不能删除
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
取得堆顶元素:
HPDataType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));//为空不能取数据
return php->a[0];
}
堆的判空:
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
堆中元素的个数:
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
向上调整算法:
void AdjustUp(HPDataType* 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;
}
}
}
向下调整算法:
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] < a[child])
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
交换:
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
现在我们来使用这个堆结构吧,将数组建成一个小堆,不断打印堆顶元素我们会得到一个递增序列,但注意!!建成小堆后数组任然无序,因为堆只规定了父子间的关系,没有规定兄弟间的关系
得到最小的堆顶元素后,删除堆顶元素,新交换到堆顶的元素向下调整,会得到第二小的元素,然后是第三小,第四小……所以不断打印堆顶元素会是有序序列
int main()
{
HP heap;
HeapInit(&heap);
int arr[] = { 9,8,7,6,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
HeapPush(&heap, arr[i]);
}
while (!HeapEmpty(&heap))
{
int Top = HeapTop(&heap);
HeapPop(&heap);
printf("%d ", Top);
}
HeapDestroy(&heap);
return 0;
}
堆中的元素:物理结构如上
可以看到数组成为小堆后仍然不是有序的
逻辑结构图:
不断打印堆顶元素,得到一个递增序列:
完结撒花~