堆的概念与实现
目录
一、堆的介绍
1.堆的概念
2.堆的性质:
3.堆的结构
二、堆的实现
1.堆的定义
2.接口函数
三、堆的实现
1.堆的初始化
2.堆的销毁
3.获取堆顶数据
4.判断堆是否为空
5. 堆的插入
向上调整算法(重点)
向下调整算法(重点)
6.删除堆顶元素
7.堆的大小
四、完整代码
heap.h
heap.c
一、堆的介绍
1.堆的概念
堆:如果有一个关键码的集合K={k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足ki<=k2i+1且ki<=k2i+2(或满足ki>=k2i+1且ki>=k2i+2),其中i=0,1,2,…,则称该集合为堆。
小堆:将根结点最小的堆叫做小堆,也叫最小堆或小根堆。
大堆:将根结点最大的堆叫做大堆,也叫最大堆或大根堆。
2.堆的性质:
堆中某个结点的值总是不大于或不小于其父结点的值。
堆总是一棵完全二叉树。
3.堆的结构
二、堆的实现
1.堆的定义
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
注:这里实现的是数组堆
2.接口函数
void HeapInit(HP* php);
void HeapDestroy(HP* php);
//插入x继续保持堆形态
void HeapPush(HP* php, HPDataType x);
//删除堆顶元素
void HeapPop(HP* php);
HeapTop(HP* php);
//返回堆顶元素
HPDataType HeapTop(HP* php);
//判空
bool HeapEmpty(HP* php);
//堆的大小
int HeapSize(HP* php);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);
//向上调整
void AdjustUp(HPDataType* a, int child);
void Swap(HPDataType* p1, HPDataType* p2);
三、堆的实现
1.堆的初始化
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
2.堆的销毁
void HeapDestroy(HP* php)
{
free(php->a);
free(php);
php = NULL;
}
3.获取堆顶数据
//返回堆顶元素
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
4.判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
5. 堆的插入
插入的核心思路
① 先将元素插入到堆的末尾,即最后一个孩子之后。
② 插入之后如果堆的性质遭到破坏,就将新插入的节点顺着其的父亲往上调整到合适位置。直到调到符合堆的性质为止。
根据堆的性质,如果不满足大堆和小堆,出现子大于父或父大于子的情况,为了保证插入之后堆还是堆,我们就需要进行自下往上的调整。堆插入数据对其他节点没有影响,只是可能会影响从它到根节点路径上节点的关系。
向上调整算法(重点)
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType* tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
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;
}
}
}
父亲的下标=(孩子的下标-1)/2,知道了这个,就很好操作了,最坏的情况就是要调到根的位置,即parent=child,这里之所以是chile>0,而不是parent<0,是因为当child=0时,parent依然等于0,不可能小于0的。
向下调整算法(重点)
现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆
向下调整算法的基本思想(小堆):
1.从根结点处开始,选出左右孩子中值较小的孩子。
2.让小的孩子与其父亲进行比较。
若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。
我们首先定义一个minChild,这个是左孩子和右孩子中较小的孩子,parent和它进行交换。
这里调整中有两个结束条件,第一个是父亲小于孩子就停止,第二个是调整到叶子节点了,也就是minChild>n就调整完毕。
void AdjustDown(HPDataType* a, int n, int parent)
{
int minChild = parent * 2 + 1;
while (minChild < n)
{
if ((minChild+1)<n&&a[minChild] > a[minChild + 1])
{
minChild++;
}
if (a[parent] > a[minChild])
{
Swap(&a[parent], &a[minChild]);
parent = minChild;
minChild = parent * 2 + 1;
}
else
{
break;
}
}
}
使用堆的向下调整算法,最坏的情况下(即一直需要交换结点),需要循环的次数为:h - 1次(h为树的高度)。而h = (N为树的总结点数)。所以堆的向下调整算法的时间复杂度为:O(logN) 。
6.删除堆顶元素
//删除堆顶元素 -- 找次打或者次小
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);
}
直接删除堆顶元素,会破坏堆的结构,所以这里我们让堆顶元素和最后一个元素进行交换,然后size--,重新向下调整,构建堆。
7.堆的大小
int HeapSize(HP* php)
{
return php->size;
}
四、完整代码
heap.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#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);
//插入x继续保持堆形态
void HeapPush(HP* php, HPDataType x);
//删除堆顶元素
void HeapPop(HP* php);
HeapTop(HP* php);
//返回堆顶元素
HPDataType HeapTop(HP* php);
//判空
bool HeapEmpty(HP* php);
//堆的大小
int HeapSize(HP* php);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);
//向上调整
void AdjustUp(HPDataType* a, int child);
void Swap(HPDataType* p1, HPDataType* p2);
heap.c
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include "heap.h"
void HeapPrint(HP* php)
{
for (int i = 0; i < php->size; ++i)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HeapDestroy(HP* php)
{
free(php->a);
free(php);
php = NULL;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType* tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
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(HPDataType* a, int n, int parent)
{
int minChild = parent * 2 + 1;
while (minChild < n)
{
if ((minChild+1)<n&&a[minChild] > a[minChild + 1])
{
minChild++;
}
if (a[parent] > a[minChild])
{
Swap(&a[parent], &a[minChild]);
parent = minChild;
minChild = parent * 2 + 1;
}
else
{
break;
}
}
}
//插入x继续保持堆形态
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
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);
}
//删除堆顶元素 -- 找次打或者次小
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);
return php->a[0];
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
int HeapSize(HP* php)
{
return php->size;
}