文章目录
- 前言
- 一、堆的概念及结构
- 二、堆的实现
- 1.头文件Heap.h(结构及其方法的声明)
- 2.源文件Heap.c(方法的实现)
- 总结
前言
二叉树是一种特殊的树结构,其中每个节点最多有两个子节点。堆是一种特殊的二叉树,它满足以下性质:
- 堆是一个完全二叉树:除了最后一层外,其他层节点个数都是满的,最后一层的节点都集中在左侧。
- 堆中每个节点的值大于等于(或小于等于)其子节点的值,这个性质被称为堆序性。
一、堆的概念及结构
堆是一种特殊的二叉树结构,它具有以下两个特点:
1. 堆是一棵完全二叉树:堆中的所有节点从左到右依次填入,不留下任何空洞。
2. 堆中每个节点的值都大于或等于(或小于或等于)其子节点的值:对于最大堆,父节点的值大于或等于其子节点的值;对于最小堆,父节点的值小于或等于其子节点的值。
堆有两种常见的实现方式:数组和二叉树。
1. 数组实现:堆可以使用一维数组来表示,节点的位置与数组的索引存在对应关系。具体来说,对于数组中的第i个元素,它的左子节点位于2i的位置,右子节点位于2i+1的位置,父节点位于i/2的位置。
2. 二叉树实现:堆也可以使用二叉树来表示,每个节点有左子节点和右子节点。通过比较父节点与子节点的值,可以保持堆的性质。
typedef int HPDataType;
// 逻辑结构和物理结构不一样
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
二、堆的实现
1.头文件Heap.h(结构及其方法的声明)
#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 Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);
2.源文件Heap.c(方法的实现)
2.1 初始化与销毁
#include"Heap.h"
void HPInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
2.2 交换
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
在函数调用栈区中改变元素大小记得传入地址
2.3 向上调整
/向上调整(插入时判断孩子是否大于(小堆)或小于(大堆)父亲)
void AdjustUp(HPDataType* a, int child)
{
// 循环三要素
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0) 巧合,歪打正着
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
注意循环结束条件如果使用父亲节点则会有巧合刚好不满足条件语句
2.4向下调整
//向下调整 (前提:左右孩子是小堆,或者大堆)
void AdjustDown(HPDataType* a, int n, int parent)
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // 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;
}
}
}
向上向下调整的参数都没传入结构体指针,原因是因为在堆排序中要脱离该数据结构进行排序 ,减少麻烦
2.5 插入
void HPPush(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, newcapacity * sizeof(HPDataType));
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);
}
此处的扩容类比顺序表
2.6 删除
// logN
// 从小到大依次被取出,变有序了
void HPPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
此时小堆结构变为降序,次方法为之后堆排序做铺垫
2.7 取父亲节点
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
2.8判空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
总结
堆是一种特殊的数据结构,用于存储和管理一组元素。它的主要作用包括:
1. 优先级队列:堆可以将元素按照一定的优先级进行排序,使得可以快速访问和操作优先级最高的元素。这在诸如调度算法、事件处理等场景中非常有用。
2. 堆排序:堆排序是一种高效的排序算法,它利用堆的性质将元素快速排序。堆排序的时间复杂度为O(nlogn),相比于其他排序算法具有较好的性能。
3. 动态数据结构:堆可以动态地添加和删除元素,而且能够维持堆的性质。这在需求频繁变化的场景中非常有用,例如实时系统中的任务调度。
4. 选择最大/最小元素:对于大顶堆,堆顶元素就是最大元素;对于小顶堆,堆顶元素就是最小元素。通过堆可以快速找到最大或最小的元素。
总之,堆是一种功能强大的数据结构,具有高效的操作和灵活的应用。它在算法和数据处理领域中被广泛使用。