数据结构--堆的基本操作(C语言描述)
程序员文章站
2024-02-11 21:49:16
...
首先说明一下,这里的堆是一种数据结构,不是内存里的堆。
一、堆的概念
堆是一个所有元素按完全二叉树的顺序存储方式存储在一个一维数组的数组,如果满足双亲节点的值小于(或大于)左孩子节点的值且双亲节点的值小于(或大于)右孩子节点的值,我们称这个堆为小堆(大堆)。
小堆中:任一节点的值均小于等于它左、右孩子的值,位于堆顶节点的元素的值最小,从根节点到每个节点的路径上元素组成的序列都是递增的。
堆的数据类型:
typedef int DataType;
typedef struct Heap
{
DataType* _array; // 数据块指针
int _capacity; // 容量
int _size; // 数组内已存数据元素个数
}Heap;
typedef Heap* PHeap;
堆存储在下标为0开始的数组中,因此在堆中给定下标为i的节点时:
- 如果i = 0, 节点i是根节点,没有双亲;否则节点i的双亲节点为节点 (i - 1) / 2
- 如果 2 * i + 1 <= n - 1,则节点 i 的左孩子为节点 2 * i + 1, 否则节点 i 无左孩子
- 如果 2 * i + 1 <= n - 1,则节点 i 的右孩子为节点 2 * i + 2, 否则节点 i 无右孩子
二、堆的基本操作
// Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
typedef int DataType;
typedef struct Heap
{
DataType* _array; // 数据块指针
int _capacity; // 容量
int _size; // 数组内已存数据元素个数
}Heap;
typedef Heap* PHeap;
// 创建堆
void CreateHeap(PHeap hp, DataType* array, int size, int(*cmp)(const void *a, const void *b));
// 比较函数 创建小堆
int CompareAscending_order(const void *a, const void *b);
// 比较函数 创建大堆
int CompareDescending_order(const void *a, const void *b);
// 在堆中插入值为data的元素
void InsertHeap(PHeap hp, DataType data, int(*cmp)(const void *a, const void *b));
// 获取堆顶元素
DataType TopHeap(PHeap hp);
// 检测一个堆是否为空堆
int EmptyHeap(PHeap hp);
// 获取堆中元素的个数
int SizeHeap(PHeap hp);
// 删除堆顶元素
void DeleteHeap(PHeap hp, int(*cmp)(const void *a, const void *b));
// 销毁堆
void DestroyHeap(PHeap hp);
void Print(PHeap hp);
#include "Heap.h"
// 交换函数
void _swap(DataType *a, DataType *b)
{
DataType temp = *a;
*a = *b;
*b = temp;
}
// 比较函数 创建小堆
int CompareAscending_order(const void *a, const void *b)
{
return *(DataType*)a - *(DataType*)b;
}
// 比较函数 创建大堆
int CompareDescending_order(const void *a, const void *b)
{
return *(DataType*)b - *(DataType*)a;
}
// 向下调整法
// 算法思想:以创建小堆为例
// 从最后一个非叶子节点开始调整,一直到根节点为止,将每个节点及其子树调整到满足小堆的性质
// 最后一个非叶子节点的查找方法:
// 完全二叉树的性质,假设数组共有size个元素,那么最后一个非叶子节点
// 下标为:((size - 1) - 1)/ 2
// 具体调整方法:
// 1.假设最后一个非叶子节点的下标为parent;
// 2.找到该节点的左孩子left = parent * 2 + 1;
// 3.如果右孩子right = parent * 2 + 2存在,标记左、右孩子中最小的孩子;
// 4.如果parent比child还小,交换parent和child的值;
// 如果p和c交换可能导致孩子节点的子树不满足小堆性质,继续调整子树使其满足小堆性质
void _AdjustDown(PHeap hp, int size, int(*cmp)(const void *a, const void *b))
{
int parent = ((size - 1) - 1) / 2; // 找到最后一个非叶子节点
int child = parent * 2 + 1; // parent的左孩子
while (parent >= 0) // 调整小堆一步可能得不到想要的结果
{
// 默认左孩子最小,如果右孩子存在,找出左右孩子中最小的
if ((child + 1) < size && cmp(&hp->_array[child], &hp->_array[child + 1]) > 0)
{ // 需要保证右孩子的合法性,否则程序会崩掉
child += 1;
}
if ((child + 1) < size && cmp(&hp->_array[parent], &hp->_array[child]) > 0)
{ // 这里也要保证右孩子的合法性
_swap(&(hp->_array[parent]), &(hp->_array[child]));
if ((parent * 2 + 1) * 2 + 1 < size)
{ // 如果双亲与孩子交换 可能导致子树不满足小堆性质
parent = child; // 调整子树
child = parent * 2 + 1;
}
}
else
{ // 如果不需要双亲和孩子节点交换 就调整前一个非叶子节点
// 由完全二叉树性质 只需parent-1即可找到前一个非叶子节点
parent = parent - 1;
child = parent * 2 + 1;
}
}
}
// 创建堆
void CreateHeap(PHeap hp, DataType* array, int size, int(*cmp)(const void *a, const void *b))
{
int i = 0;
// 参数检验
assert(hp);
// 给堆申请空间
hp->_array = (DataType*)malloc(sizeof(DataType)*size);
if (hp->_array == NULL)
{
printf("内存申请失败!!!\n");
assert(0);
return;
}
else
{
hp->_capacity = size;
hp->_size = 0;
}
// 给堆放置元素
for (i = 0; i < size; i++)
{
hp->_array[i] = array[i];
hp->size++;
}
// 用向下调整法
_AdjustDown(hp, size, cmp);
}
// 为了直观的观测数组元素的变化
void Print(PHeap hp)
{
int i = 0;
for (i = 0; i < hp->_size; i++)
{
printf("%d ", hp->_array[i]);
}
printf("\n");
}
// 向上调整法
// 在一个最小堆之后插入新元素后可能破坏堆的结构
// 对新堆自下向上重新调整 将其调整为最小堆
void _AdjustUp(PHeap hp, int size, int(*cmp)(const void *a, const void *b))
{
int parent = ((size - 1) - 1) / 2;
int child = size - 1;
while (parent >= 0)
{
if ((child < size) && cmp(&(hp->_array[parent]), &(hp->_array[child])) > 0)
{
_swap(&(hp->_array[parent]), &(hp->_array[child]));
child = parent;
parent = (child - 1) / 2;
}
else
{
return;
}
}
}
// 在堆中插入值为data的元素
// 在已经建成的最小堆的后面插入新元素
void InsertHeap(PHeap hp, DataType data, int (*cmp)(const void *a, const void *b))
{
assert(hp);
hp->_array = (DataType*)realloc(hp->_array, sizeof(DataType) *( hp->_capacity +1));
if (hp->_array == NULL)
{
assert(0);
return;
}
else
{
hp->_capacity++;
hp->_array[hp->_capacity - 1] = data;
hp->_size++;
_AdjustUp(hp, hp->_size, cmp);
}
}
// 获取堆顶元素
DataType TopHeap(PHeap hp)
{
assert(hp);
return hp->_size > 0 ? hp->_array[0] : 0;
}
// 检测一个堆是否为空堆
int EmptyHeap(PHeap hp)
{
assert(hp);
return hp->_size > 0 ? 0 : 1;
}
// 获取堆中元素的个数
int SizeHeap(PHeap hp)
{
assert(hp);
return hp->_size;
}
// 删除堆顶元素
// 删除方法:
// 1.将堆中最后一个元素替代堆顶元素
// 2.将堆中元素个数减少一个,相当于将堆中最后一个元素删除
// 3.此时堆结构可能被破坏,再向下调整使其满足堆的性质
void DeleteHeap(PHeap hp, int (*cmp)(const void *a, const void *b))
{
assert(hp);
_swap(&hp->_array[0], &hp->_array[hp->_size - 1]);
hp->_size--;
_AdjustDown(hp, hp->_size, cmp);
}
// 销毁堆
void DestroyHeap(PHeap hp)
{
assert(hp);
free(hp->_array);
hp->_capacity = 0;
hp->_size = 0;
}
三、测试
#include "Heap.h"
void TestHeap()
{
Heap hp; // 创建堆
DataType array[] = {53, 17, 78, 9, 45, 65, 87, 23, 31}; // 外部数组
int size = sizeof(array) / sizeof(array[0]); // 求外部数组元素个数
DataType ret_Top = 0; // 接收取对顶元素函数返回值
int ret_Empty = 0; // 接收判断堆是否为空函数的返回值
int ret_size = 0; // 接收求堆元素个数函数的返回值
CreateHeap(&hp, array, size, CompareAscending_order); // 创建小堆
Print(&hp); // 遍历堆数组元素
ret_Top = TopHeap(&hp); // 调用求取堆顶元素函数
printf("TopHeap = %d\n", ret_Top);
ret_Empty = EmptyHeap(&hp); // 调用判断堆是否为空函数
if (ret_Empty == 0)
{
printf("不是空堆\n");
}
else
{
printf("空堆!\n");
}
ret_size = SizeHeap(&hp); // 调用求堆数组元素个数函数
printf("Size = %d\n", ret_size);
InsertHeap(&hp, 8, CompareAscending_order); // 给堆插入一个元素
Print(&hp);
ret_Top = TopHeap(&hp);
printf("TopHeap = %d\n", ret_Top);
ret_Empty = EmptyHeap(&hp);
if (ret_Empty == 0)
{
printf("不是空堆\n");
}
else
{
printf("空堆!\n");
}
ret_size = SizeHeap(&hp);
printf("Size = %d\n", ret_size);
DeleteHeap(&hp, CompareAscending_order); // 删除一个堆顶元素
Print(&hp);
DestroyHeap(&hp); // 销毁堆
}
// 主函数
int main()
{
TestHeap(); // 调用测试函数
system("pause");
return 0;
}
测试结果:
如有不正还请指出,有劳!
上一篇: JDBCTM 指南:入门
下一篇: IO-NIO 你真的理解IO吗?