目录
堆
如果有一个关键码的集合,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,则称为堆
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
堆的特点:
- 堆中某个节点的值总是不大于或不小于其父节点的值
- 堆总是一棵完全二叉树
- 二叉树中父子间下标关系
- 孩子节点找父亲节点:左孩子
leftChild = parent * 2 + 1
,右孩子rightChild = parent * 2 + 2
- 父亲节点找孩子节点:
parent = (child - 1) / 2
(不论孩子节点下标为奇数还是偶数均可以使用,因为当child
为奇数时,会进行取整操作)
堆的实现
堆的类型定义
// 定义堆中存储的数据的数据类型
typedef int HPDataType;
// 堆的结构设计
typedef struct Heap
{
HPDataType* data;// 存储数据部分
int size; // 堆中的有效数据个数
int capacity; // 堆的容量大小
}HP;
堆的功能实现(以小堆为例)
// 主要实现功能
// 堆的初始化
void HeapInit(HP* hp);
// 堆的销毁
void HeapDestroy(HP* hp);
// 堆的数据插入
void HeapPush(HP* hp, HPDataType x);
// 堆的数据删除
void HeapPop(HP* hp);
// 获取堆顶数据
HPDataType HeapTop(HP* hp);
// 判断堆是否为空
bool IsEmpty(HP* hp);
堆的初始化
// 堆的初始化
void HeapInit(HP* hp)
{
hp->data = NULL;
hp->size = hp->capacity = 0;
}
堆的销毁
// 堆的销毁
void HeapDestroy(HP* hp)
{
assert(hp);
free(hp->data);
hp->size = hp->capacity = 0;
}
堆的数据插入
对于堆的数据插入过程来说(以小堆为例),在小堆中,父亲节点的值总小于或等于孩子节点的值,故可以采用向上调整算法,但是使用向上调整算法的前提是除插入数据以外,原始的堆已经为小堆
//以下面的数组为例
int arr[6] = { 35,70,56,90,60,25};
首先插入35,因为一个节点依旧为堆,而且此时无法判断是小堆还是大堆,故不需要调整,直接插入即可
第二个数值为70,因为70>35,故不需要进行向上调整,直接插入即可
第三个数值为56,因为56>35,故不需要进行向上调整,直接插入即可
📌
注意不需要关注兄弟节点之间的关系和顺序
第四个数值为90,因为90>70,故不需要进行向上调整,直接插入即可
第五个数值为60,因为60<70,此时需要进行向上调整,当前的size
位置即为需要调整的孩子节点的位置的后一个位置
第六个数值为25,因为25<56,此时需要向上调整,并且因为25比根节点也小,所以需要向上调整两次,当前size-1
的位置即为孩子节点25的位置
📌
注意,判断循环条件为child > 0
,而不是parent >= 0
,因为当child
为0时,说明已经到了根节点,就不需要再进行向上调整了,而如果使用parent >= 0
作为循环条件,那么当child
为0是,parent = (child - 1) / 2
算出来的值依旧是0,导致出现死循环
//交换函数
void swap(HPDataType* num1, HPDataType* num2)
{
HPDataType* tmp = num1;
*num1 = *num2;
*num2 = *tmp;
}
//向上调整算法
void AdjustUP(HPDataType* data, int size, int child)
{
int parent = (child - 1) / 2;//计算父亲节点的位置
while (child > 0)
{
if (data[child] < data[parent])
{
//交换孩子节点和父亲节点的值
swap(&data[child], &data[parent]);
child = parent;//更新孩子节点为原父亲节点的位置
parent = (child - 1) / 2;// 获取下一个父亲节点的位置
}
else
{
break;// 如果有一次出现孩子节点大于父亲节点的值就直接跳出不需要进行接下来的比较,因为其他位置在执行前已经是小堆
}
}
}
// 堆的数据插入
void HeapPush(HP* hp, HPDataType x)
{
assert(hp);
//判断是否需要扩容
if (hp->size == hp->capacity)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(sizeof(HPDataType) * newCapacity);
assert(tmp);
hp->data = tmp;
hp->capacity = newCapacity;
}
hp->data[hp->size++] = x;
//插入数据
//以小堆为例,在小堆中,父亲节点的值总小于或等于孩子节点的值
//所以需要进行向上调整
//向上调整的前提是其余的内容已经是小堆
AdjustUP(hp->data, hp->size, hp->size - 1);
}
堆的数据移除
在堆中,移除的数据均为堆的第一个数据,即根节点的数据,但是不能直接使用挪动覆盖的思路,因为会打破原有的堆结构,可以采用先交换根节点和叶子节点的数据再使用向下调整算法恢复小堆结构
先交换根节点和叶子节点的数据,再改变size
的值,从而达到删除数据的效果
接着执行向下调整算法恢复堆结构
在执行向下调整算法时,需要判断是和哪一个孩子节点进行交换,找到较小的孩子的节点交换即可
//向下调整算法
void AdjustDown(HPDataType* data, int size, int parent)
{
int child = parent * 2 + 1;//假设左边的孩子节点为较小的孩子节点
while (child < size)
{
//如果假设不正确,则改变需要进行交换的孩子节点
if (child + 1 < size && data[child] > data[child + 1])//先判断child+1是否越界
{
child++;// 将child更改为右边的孩子节点
}
//执行交换
if (data[child] < data[parent])
{
swap(&data[child], &data[parent]);
parent = child;//更新parent值便于继续比较下一层
child = parent * 2 + 1;//更新child的值
}
else
{
break;
}
}
}
// 堆的数据删除
void HeapPop(HP* hp)
{
assert(hp);
//堆为空不执行删除操作
assert(!IsEmpty(hp));
//在堆中,删除的数据并不是最后一个元素,而是第一个元素
//但是不可以直接挪动数据,否则会改变节点之间的关系,从而失去堆的结构
//可以考虑使用先交换根节点和最后一个叶子节点数据再使用向下调整算法解决问题
//交换根节点数据和最后一个叶子节点数据
swap(&(hp->data[0]), &(hp->data[hp->size - 1]));
hp->size--;
//向下调整算法恢复堆结构
AdjustDown(hp->data, hp->size, 0);
}
判断堆是否为空
// 判断堆是否为空
bool IsEmpty(HP* hp)
{
assert(hp);
return hp->size == 0;
}
获取堆顶数据
//获取堆顶数据
HPDataType HeapTop(HP* hp)
{
assert(hp);
assert(!IsEmpty(hp));
return hp->data[0];
}
获取堆的有效数据个数
//获取堆的有效数据个数
int HeapSize(HP* hp)
{
assert(hp);
return hp->size;
}
项目文件
//头文件
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
// 定义堆中存储的数据的数据类型
typedef int HPDataType;
// 堆的结构设计
typedef struct Heap
{
HPDataType* data;// 存储数据部分
int size; // 堆中的有效数据个数
int capacity; // 堆的容量大小
}HP;
// 堆的初始化
void HeapInit(HP* hp);
// 堆的销毁
void HeapDestroy(HP* hp);
//交换函数
void swap(HPDataType* num1, HPDataType* num2);
//向上调整算法
void AdjustUP(HPDataType* data, int child);
// 堆的数据插入
void HeapPush(HP* hp, HPDataType x);
//向下调整算法
void AdjustDown(HPDataType* data, int size, int parent);
// 堆的数据删除
void HeapPop(HP* hp);
// 获取堆顶数据
HPDataType HeapTop(HP* hp);
// 判断堆是否为空
bool IsEmpty(HP* hp);
//获取堆的有效数据个数
int HeapSize(HP *hp);
//实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
// 堆的初始化
void HeapInit(HP* hp)
{
hp->data = NULL;
hp->size = hp->capacity = 0;
}
// 堆的销毁
void HeapDestroy(HP* hp)
{
assert(hp);
free(hp->data);
hp->size = hp->capacity = 0;
}
//交换函数
void swap(HPDataType* num1, HPDataType* num2)
{
HPDataType tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
//向上调整算法
void AdjustUP(HPDataType* data, int child)
{
int parent = (child - 1) / 2;//计算父亲节点的位置
while (child > 0)
{
if (data[child] < data[parent])
{
//交换孩子节点和父亲节点的值
swap(&data[child], &data[parent]);
child = parent;//更新孩子节点为原父亲节点的位置
parent = (child - 1) / 2;// 获取下一个父亲节点的位置
}
else
{
break;// 如果有一次出现孩子节点大于父亲节点的值就直接跳出不需要进行接下来的比较,因为其他位置在执行前已经是小堆
}
}
}
// 堆的数据插入
void HeapPush(HP* hp, HPDataType x)
{
assert(hp);
//判断是否需要扩容
if (hp->size == hp->capacity)
{
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->data, sizeof(HPDataType) * newCapacity);
assert(tmp);
hp->data = tmp;
hp->capacity = newCapacity;
}
hp->data[hp->size++] = x;
//插入数据
//以小堆为例,在小堆中,父亲节点的值总小于或等于孩子节点的值
//所以需要进行向上调整
//向上调整的前提是其余的内容已经是小堆
AdjustUP(hp->data, hp->size - 1);
}
//向下调整算法
void AdjustDown(HPDataType* data, int size, int parent)
{
int child = parent * 2 + 1;//假设左边的孩子节点为较小的孩子节点
while (child < size)
{
//如果假设不正确,则改变需要进行交换的孩子节点
if (child + 1 < size && data[child] > data[child + 1])//先判断child+1是否越界
{
child++;// 将child更改为右边的孩子节点
}
//执行交换
if (data[child] < data[parent])
{
swap(&data[child], &data[parent]);
parent = child;//更新parent值便于继续比较下一层
child = parent * 2 + 1;//更新child的值
}
else
{
break;
}
}
}
// 堆的数据删除
void HeapPop(HP* hp)
{
assert(hp);
//堆为空不执行删除操作
assert(!IsEmpty(hp));
//在堆中,删除的数据并不是最后一个元素,而是第一个元素
//但是不可以直接挪动数据,否则会改变节点之间的关系,从而失去堆的结构
//可以考虑使用先交换根节点和最后一个叶子节点数据再使用向下调整算法解决问题
//交换根节点数据和最后一个叶子节点数据
swap(&(hp->data[0]), &(hp->data[hp->size - 1]));
hp->size--;
//向下调整算法恢复堆结构
AdjustDown(hp->data, hp->size, 0);
}
// 判断堆是否为空
bool IsEmpty(HP* hp)
{
assert(hp);
return hp->size == 0;
}
// 获取堆顶数据
HPDataType HeapTop(HP* hp)
{
assert(hp);
assert(!IsEmpty(hp));
return hp->data[0];
}
//获取堆的有效数据个数
int HeapSize(HP* hp)
{
assert(hp);
return hp->size;
}
//测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
void Test()
{
HP hp = { 0 };
HeapInit(&hp);
//int a[] = { 65,100,70,32,50,60 };
int a[] = { 35,70,56,90,60,25 };
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
HeapPush(&hp, a[i]);
}
while (!IsEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDestroy(&hp);
}
int main()
{
Test();
return 0;
}