目录
一、二叉树简介:
这里只是简单介绍一下完全二叉树和非完全二叉树:
1.完全二叉树:
完全二叉树是连续的,像数组一样:(可以看图了解)
2.非完全二叉树:
而非完全二叉树是不连续的:(可以看图了解)
二、此帖的堆是用动态数组来实现:
用动态数组的原因是,因为静态数组无法扩容。
parent---父亲(或者母亲)
child---子女
1、先拟定好堆的功能有哪些:
Heap.h(头文件):
这里以小堆为例子,如何实现大堆,请看后续:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap
{
int* val; // 这里采用动态数组来存储数据
int size; // 记录堆的有效元素
int capacity; // 记录堆的空间大小
}Heap;
void HeapInit(Heap* hp);// 堆的初始化
void HeapCreate(Heap* hp, HPDataType* a, int n);// 此函数是把静态数组中的数据移到动态数组中,详细请看后面Heap.c文件描述
void HeapDestory(Heap* hp);// 堆的销毁
void HeapPush(Heap* hp, HPDataType x);// 堆的插入
void HeapPop(Heap* hp);// 堆的删除
HPDataType HeapTop(Heap* hp);// 取堆顶的数据
int HeapSize(Heap* hp);// 堆的数据个数
int HeapEmpty(Heap* hp);// 堆的判空
void AdjustUp(HPDataType* hp, int child);// 堆元素的向上调整
void AdjustDown(HPDataType* hp, int parent, int n);// 堆元素的向下调整
void Swap(HPDataType* p1, HPDataType* p2);// 元素交换
Heap.c(函数文件):
#include "Heap.h" // 别忘了头文件的引用
void HeapInit(Heap* hp) //初始化
{
assert(hp);
hp->val = NULL;
hp->capacity = 0;
hp->size = 0;
}
void HeapCreate(Heap* hp, HPDataType* a, int n) // 此函数是把静态数组中的数据移到动态数组中
{
//Heap* hp -> 堆指针
// HPDataType* a -> 是静态数组的指针
// int n-> 是静态数组的空间大小
HPDataType* tmp = (HPDataType*)realloc(hp->val, sizeof(HPDataType) * n);
// 静态数组不支持realloc函数操作,故此必须重开动态空间
hp->val = tmp;
for (int i = 0; i < n; i++)
{
tmp[i] = a[i];
AdjustUp(hp->val, i);
hp->size++;
}
hp->capacity = n;
}
void HeapDestory(Heap* hp) // 销毁掉堆
{
assert(hp);
free(hp->val);
hp->val = NULL;
hp->capacity = 0;
hp->size = 0;
}
void HeapPush(Heap* hp, HPDataType x) // 向堆中插入元素
{
if (hp->size == hp->capacity) //堆的有效元素和最大空间是否相等,来判断堆是否满
{
int Newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->val, sizeof(HPDataType) * Newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
hp->val = tmp;
hp->capacity = Newcapacity;
}
hp->val[hp->size] = x;
hp->size++;
AdjustUp(hp->val, hp->size - 1);
}
void HeapPop(Heap* hp) // 删除堆顶的元素
{
assert(hp);
assert(hp->size > 0);
Swap(&hp->val[0], &hp->val[hp->size - 1]);
hp->size--;
AdjustDown(hp->val, 0, hp->size);
}
HPDataType HeapTop(Heap* hp) // 访问堆顶的元素,但不删除
{
assert(hp);
assert(hp->size > 0);
return hp->val[0];
}
int HeapSize(Heap* hp) // 获取堆的有效元素个数
{
assert(hp);
assert(hp->size > 0);
return hp->size;
}
int HeapEmpty(Heap* hp) // 判断堆是否为空
{
assert(hp);
return hp->size == 0;
}
void AdjustUp(HPDataType* hp, int child) // 让子节点与父结点比较,从而到达理想位置
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (hp[parent] > hp[child])
{
Swap(&hp[child], &hp[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(HPDataType* hp, int parent, int n) // 让父节点与子结点比较,从而到达理想位置
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && hp[child] > hp[child + 1])
{
++child;
}
if (hp[parent] > hp[child])
{
Swap(&hp[parent], &hp[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void Swap(HPDataType* p1, HPDataType* p2) // 两个结点的数据交换
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
2、部分函数的详细介绍
1.AdjustUp函数的介绍:
void AdjustUp(HPDataType* hp, int child)
{
int parent = (child - 1) / 2; // 该写法是依据子节点的下标反向推断出父节点的下标
while (child > 0) // 当child等于parent时就说明已经到根结点了
{
if (hp[parent] > hp[child])
{
Swap(&hp[child], &hp[parent]); // 交换数据函数
child = parent;
parent = (child - 1) / 2;
// 当交换成功时,将再往上走一层,继续判断交换后的数据(此时从父节点更变为子节点)与其父节点比大小
}
else
{
break;
}
}
}
2.AdjustDown函数的介绍:
可以参考上图理解AdjustDown函数的思路
void AdjustDown(HPDataType* hp, int parent, int n) // 让父节点与子结点比较,从而到达理想位置
{
// 该写法是依据父节点的下标推断出子节点的下标
// 这里假设左子节点是满足交换条件的.
int child = parent * 2 + 1;
while (child < n)
{
// 再让左子节点和右子节点比较,筛选出满足条件的那个子节点
// child + 1 < n 是为了防止越界
if (child + 1 < n && hp[child] > hp[child + 1])
{
++child;
}
if (hp[parent] > hp[child])
{
Swap(&hp[parent], &hp[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
如果想要大堆,那就把上述两个函数中 if 语句中的元素数据之间比大小反一下就好了