二叉树的存储结构
-
顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们下一篇就来讲解堆的实现。 -
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。后面的红黑数可能会用到三叉链。
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* pLeft; // 指向当前节点左孩子
struct BinTreeNode* pRight; // 指向当前节点右孩子
BTDataType data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* pParent; // 指向当前节点的双亲
struct BinTreeNode* pLeft; // 指向当前节点左孩子
struct BinTreeNode* pRight; // 指向当前节点右孩子
BTDataType data; // 当前节点值域
};
二叉树实现堆
堆的定义
我们认为堆是一个完全二叉树,并且堆中的父子关系 有且只有一种,要么是父亲结点大于等于孩子结点,要么是父亲节点小于等于孩子结点。
我们根据根节点在堆中的大小,来确定是大堆还是小堆,如果根节点在堆中最大,则是大根堆;如果根节点在堆中最小,则是小根堆。
堆的存储结构
我们知道一般的二叉树使用链式存储,但是完全二叉树可以使用数组进行存储。
一般的二叉树使用数组存储,会造成大量的空间浪费,而完全二叉树,就不会哦!
又因为堆刚好是完全二叉树,所以我们使用数组存储。
上述示意图中,二叉树的部分是我们想象中的逻辑结构,下面数组部分是我们实际在数组中的存储结构。
堆的代码实现:
头文件
首先我们和顺序表一样写出,头文件,存储结构,函数接口。
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capaticy;
}HP;
void Swap(HPDataType* p1, HPDataType* p2);//交换
void AdjustDwon(HPDataType* a, int size, int parent);//向下调整 (时间复杂度O(N))
void AdjustUp(HPDataType* a, int child);//向上调整(时间复杂度O(N))
void HeapPrint(HP* php);//打印
void HeapInit(HP* php);//初始化
void HeapDestroy(HP* php);//销毁
void HeapPush(HP* php, HPDataType x);//插入
void HeapPop(HP* php);//删除
HPDataType HeapTop(HP* php);//返回堆顶元素
bool HeapEmpty(HP* php);//判空
int HeapSize(HP* php);//返回堆的元素个数
堆的初始化:
void HeapInit(HP* php)//初始化
{
assert(php);
php->a = NULL;
php->size = php->capaticy = 0;
}
堆的打印:
void HeapPrint(HP* php)//打印
{
assert(php);
for (int i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
堆的销毁:
void HeapDestroy(HP* php)//销毁
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capaticy = 0;
}
返回堆顶元素:
HPDataType HeapTop(HP* php)//返回堆顶元素
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
判断堆元素是否为空:
bool HeapEmpty(HP* php)//判空
{
assert(php);
return php->size == php->capaticy;
}
返回堆的元素个数:
int HeapSize(HP* php)//返回堆的元素个数。
{
assert(php);
return php->size;
}
堆的插入和向上调整
我们建堆插入时,一般会插入在动态数组的最后一个元素处插入数据,然后在进行调整,这个调整叫向上调整算法。
我们在调整时,满足父亲节点的下标和孩子节点的下标满足一个公式,P = (C-1)/ 2,
利用这一公式我们可以进一步找到父亲节点,然后调整,代码如下:
void AdjustUp(HPDataType* a, int child)//向上调整
{
int parent = (child - 1) / 2;//找到父亲节点
while (child > 0)//如果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->capaticy)
{
int NewCapaticy = php->capaticy == 0 ? 4 : 2 * php->capaticy;
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*NewCapaticy);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->a = tmp;
php->capaticy = NewCapaticy;
}
php->a[php->size] = x;
php->size++;
//最后要给的是孩子的下标,是size-1.
AdjustUp(php->a, php->size - 1);
}
堆的删除和向下调整
:
同样在堆删除数据时,会先交换最后一个数据和根,然后让根进行向下调整。
我们也使用了公式: C(左孩子)= P*2 +1。
我们依据上图实现下代码吧:
void AdjustDwon(HPDataType* a, int size, int parent)//向下调整
{
int child = parent * 2 + 1;//左孩子
while (child < size)//孩子节点下,到size结束
{
//要在确定有右孩子的情况下,右孩子大于左孩子,就使用右孩子(大堆)
if (child + 1 < size && a[child] < a[child + 1])
{
child = child + 1;
}
//如果孩子大就交换(大堆)
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)//删除
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDwon(php->a, php->size, 0);
}
验证代码
我们使用一个小例子来验证下代码的正确性,我们使用一些随机值,然后把他们插入堆中,接着我们打印下,判断是否和我们想象中的逻辑结构相符合:
void HeapTest1()
{
HP hp;//建堆
HeapInit(&hp);//初始化
int a[] = { 10, 15, 18, 41, 56, 89, 66, 23, 37, 75 };//随机值
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
HeapPush(&hp, a[i]);//插入堆中
}
HeapPrint(&hp);//打印
HeapPop(&hp);//删除根节点
HeapPrint(&hp);//打印
HeapDestroy(&hp);//销毁
}
int main()
{
HeapTest1();//测试函数
return 0;
}
结果如上,我们验证下逻辑结构是否正确。
第一次打印,符合大根堆性质。
第二次打印,符合大根堆性质。