个人主页
⭐个人专栏——数据结构学习⭐
点击关注一起学习C语言
我们在前面学习了单链表和顺序表,以及栈和队列。
今天我们来学习小堆。
关注博主或是订阅专栏,掌握第一消息。
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
堆是一种特殊的数据结构,它可以看做是一个完全二叉树(或者近似二叉树),其中每个节点的值都大于等于(或小于等于)其子节点的值。在一个最大堆中,根节点的值是最大的;在一个最小堆中,根节点的值是最小的。
堆的主要特点是:每个节点的值都大于等于(或小于等于)其子节点的值。这种特点使得堆可以快速找到最大(或最小)的元素。另外,堆还可以用于排序和优先队列等应用。
堆中兄弟节点的值之间没有关联。在堆中,节点之间的关系仅由其在树中的位置决定。
堆通常使用数组来实现,数组的下标代表节点在堆中的位置。根据节点在数组中的位置,可以通过简单的计算得到其父节点、左子节点和右子节点的位置。这样,在堆中插入一个新元素、删除堆顶的元素或者调整堆的结构时,只需要对数组进行简单的操作,而不需要改变整个堆的结构。
我们需要创建两个 C文件: study.c 和 Heap.c,以及一个 头文件: Heap.h。
头文件来声明函数,一个C文件来定义函数,另外一个C文件来用于主函数main()进行测试。
堆的常见操作包括插入元素、删除堆顶元素、堆化(调整堆的结构使其满足堆的特点)等。其中,插入元素和删除堆顶元素的时间复杂度为O(logn),堆化的时间复杂度为O(nlogn)。
Heap.h:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size; //记录数组内的有效数据
int capacity; //记录数组空间大小
}HP;
Heap.h:
//堆的初始化
void HeapInit(HP* php);
Heap.c:
//堆的初始化
void HeapInit(HP* php)
{
//各值初始化为0
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
我们的数组空间是用malloc函数开辟的,使用完之后需要进行释放。
Heap.h:
// 堆的销毁
void HeapDestroy(HP* php);
Heap.c:
// 堆的销毁
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
在出入数组后,我们需要对数组进行调整,以实现堆的结构特点。
在数组中,下标*2+1就是他的子节点,同样的下标-1/2就是他的父节点。
Heap.h:
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child);
Heap.c:
//交换父节点和子节点的值
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child)
{
assert(a);
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;
}
}
}
Heap.h:
// 堆的插入
void HeapPush(HP* php, HPDataType x);
Heap.c:
// 堆的插入
// 堆的插入
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");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
//在插入数值后需要检查是否要进行调整
AdjustUp(php->a, php->size);
php->size++;
}
删除堆是删除堆顶的数据,但是我们无法直接删除第一个元素,这有极大的可能会使我们的堆崩溃,不再具有堆的特点,而在删除之后把其它数值都往前移动,再进行调整是一项很大的工作量。
所以我们可以将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。将当前的根数值调整到符合堆特点的位置去。
Heap.h:
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent);
Heap.c:
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;//找到孩子节点
while (child < size)
{
//找孩子中较小的一个
if ((a[child] > a[child + 1]) && (child + 1) < size)
{
child += 1;
}
//判断两个大小进行交换
if (a[parent] > a[child])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
Heap.h:
// 堆的删除
void HeapPop(HP* php);
Heap.c:
// 堆的删除
void HeapPop(HP* php)
{
assert(php);
//先检查数组是否有可删除的数据
assert(php->size > 0);
//交换首尾元素
Swap(&php->a[php->size - 1], &php->a[0]);
php->size--;
//向下进行调整
AdjustDown(php->a, php->size, 0);
}
返回数组首元素即可
Heap.h:
// 取堆顶的数据
HPDataType HeapTop(HP* php);
Heap.c:
// 取堆顶的数据
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
直接返回size的值即可
Heap.h:
// 堆的数据个数
size_t HeapSize(HP* php);
Heap.c:
// 堆的数据个数
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
只需判断size的值是否为0,如果是,返回true,反之返回false。
Heap.h:
// 堆的判空
bool HeapEmpty(HP* php);
Heap.c:
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include
#include
#include
#include
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);
// 堆的数据个数
size_t HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child);
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent);
//交换父节点和子节点的值
void Swap(int* child, int* parent);
#include "Heap.h"
//堆的初始化
void HeapInit(HP* php)
{
//各值初始化为0
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 = 0;
php->capacity = 0;
}
//交换父节点和子节点的值
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child)
{
assert(a);
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 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");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
//在插入数值后需要检查是否要进行调整
AdjustUp(php->a, php->size);
php->size++;
}
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;//找到孩子节点
while (child < size)
{
//找孩子中较小的一个
if ((a[child] > a[child + 1]) && (child + 1) < size)
{
child += 1;
}
//判断两个大小进行交换
if (a[parent] > a[child])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆的删除
void HeapPop(HP* php)
{
assert(php);
//先检查数组是否有可删除的数据
assert(php->size > 0);
//交换首尾元素
Swap(&php->a[php->size - 1], &php->a[0]);
php->size--;
//向下进行调整
AdjustDown(php->a, php->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
// 堆的数据个数
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
void Test1()
{
int array[] = { 27,15,19,18,28,34,65,49,25,37 };
HP hp;
HeapInit(&hp);
for (int i = 0; i < sizeof(array) / sizeof(int); i++)
{
HeapPush(&hp, array[i]);//插入数据
}
int k = HeapSize(&hp);
while (k--)
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDestroy(&hp);
}
int main()
{;
Test1();
return 0;
}