系列文章目录
文章目录
前言
因为这篇先从简单入手【二叉树的应用之堆】的实现,需要对二叉树有所了解, 这里先大概 描述二叉树及其性质,便于理解堆。下篇会对树、二叉树等做详细讲解。
一、二叉树的概念及性质
1.二叉树的概念
一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
2.二叉树的性质
- 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
3.特殊的二叉树
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k)-1 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
通俗的说 完全二叉树前k-1层都是满的,最后一层k深度不一定满,要求从左到右是连续的。
二、堆的概念及性质
1.堆的概念
- 如果有一个关键码的集合K = { k0,k1,k2,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1且Ki <=K2i+2 (Ki >=K2i+1 且Ki >=K2i+2 ) i = 0,1,2…,则称为小堆(或大堆)。
- 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树
三、堆的实现
1.用数组定义堆结构:Struct Heap
代码如下
typedef int HDataType;
typedef struct Heap
{
HDataType* a;//数组
int size;//大小
int capacity;//容量
}HP;
2.堆的初始化:HeapInit
代码如下
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
3.交换两个数Swap函数
代码如下
void Swap(HDataType*p1, HDataType* p2)
{
HDataType* tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
4.堆向上调整函数:AdjustUp
代码如下
void Adjustup(HP* php, int child)
{
int parent = child - 1 / 2;
//while(parent>=0) 因为父亲=0 进来一次 孩子=父亲=0 父亲计算后=0-1/2 =1 又上去一次 死循环 所以使用 孩子判断
while (child>0)
{
if (php->a[child] < php->a[parent])//小根堆
{
Swap(&php->a[child], &php->a[parent]);//交换两个值
child = parent;//往上走迭代
parent = child - 1 / 2;//找父亲
}
else//已成为堆
{
break;//无需调整
}
}
}
思路:通过上调的位置(孩子位) 找到其父亲,判断两个值大小,[小堆]如果孩子小于父亲,将两个值交换,再将父亲的值给给孩子(孩子向上走) 在计算当前更新后孩子的父亲 向上迭代着走 直到孩子=0
思考:为什么不用父亲>=0做循环条件?
当父亲=0;继续时父亲的值给给孩子 孩子计算下一个父亲0-1/2,结果依然0存在死循环。
5.堆向下调整函数:AdjustDown
代码如下
void AdjustDown(HP* php, int n, int parent)//数组大小
{
int minChild = parent * 2 + 1;//默认小孩子是左孩子
while (minChild<n)//?防止越界数组
{
if (php->a[minChild]<n && php->a[minChild + 1] > php->a[minChild])//找出小的那个孩子
{//防止数组越界
minChild++;
}//小孩子右孩子
if (php->a[minChild] < php->a[parent])//目前小根堆 叛逆
{
Swap(&php->a[minChild], &php->a[parent]);//交换
parent = minChild;
minChild = parent * 2 + 1;
}
else
{
break;
}
}
}
思路:找出小的那个孩子 [小堆]如果父亲大于孩子 交换 孩子位置给父亲 向下迭代
6.堆的插入:HeapPush
代码如下
void HeapPush(HP* php, HDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newCapaCity = php->capacity == 0 ? 4 : php->capacity * 2;
HDataType* tmp = (HDataType*)realloc(php->a, newCapaCity*sizeof(HDataType));//开辟大小易错
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapaCity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size-1);//从哪个位置向上调整?
}
思路:插入元素 更新size 向上调整 保持堆的形态
7删除堆顶元素:HeapPop
代码如下
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(php->a[0], php->a[php->size - 1]);//交换第一个和最后一个位置
php->size--;删除最后一个位置
AdjustDown(php->a, php->size, 0);//调整保持堆形态
}
思路:【找次大或次小】保持堆形态 为了不破坏父子关系 头尾数据交换后删除尾巴 通过向下调整保持堆形态
8…获取堆顶元素:HeapTop
代码如下
HDataType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];//返回第一位的值
}
9.堆的判空:HeapEmpty
代码如下
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;//大小为0
}
10.堆的大小:HeapSize
代码如下
int HeapSize(HP* php)
{
assert(php);
return php->size;//size位大小
}
11.堆的打印:HeapPrint
代码如下
void HeapPrint(HP* php)
{
assert(php);
for (int i = 0; i < php->size;++i)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
12.堆的销毁:HeapDestroy
代码如下
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
总结
以上就是我对数据结构中二叉树的应用堆的一些认识,如果有问题,请大家帮忙指出。掌握堆的特点及如何保持堆形态的调整,遇到问题多画图 想明白多上手敲代码。(监督自己💪)
今日语录✍️
知不足而奋进 望远山而前行💖