一 . 概念与结构
在树的概念与结构中树的概念与结构-CSDN博客, 我们发现子结点可以为 0 或者是更多 , 结构较为复杂 , 然后把树的结点个数 加个限制条件 --> 不能超过 2 --> 我们引出了二叉树,在实际运用广 且高效 ;
在树形结构中 , 我们最常用的就是二叉树 , 一棵二叉树是结点的一个有限集合 , 该结点由一个根结点加上两棵 称 为左子树 和 右子树的二叉树 或者为空;
二叉树的特点 :
1 . 不存在大于 2 的结点
2 . 二叉树的子树有左右之分 , 次序不能颠倒 , 因此二叉树是有序树 ;
注意 : 对于任意的二叉树都是由以下几种情况复合而成的 ! ! !
二 . 特殊的二叉树
2.1 满二叉树
一个二叉树 , 如果每一层的结点数都达到最大值, 则这个二叉树就是满二叉树 。
也就是说 , 如果一个二叉树的层数为K,且结点的总数是 -1 , 则它就是满二叉树。
2.2 完全二叉树
完全二叉树是效率很高的数据结构 , 完全二叉树是由满二叉树而引出来的 。 对于深度为 K 的 , 有 n 个结点的二叉树 , 当且仅当其每个结点都与深度为 K 的满二叉树从 1 到 n 的结点一 一对应时称之为完全二叉树 ;
注意 : 满二叉树是一种特殊的完全二叉树 。
通俗点说 : 最后一层结点的个数不一定达到最大 , 但是最后一层的结点必须从左往右依次排列 。
满二叉树是特殊的完全二叉树 !!!
二叉树的特质 :
- 若规定根结点的层数为 1 ,则一棵非空二叉树的第 i 层最多有 个结点
- 若规定根结点的层数为 1 , 则深度为 h 的二叉树的最大结点树是
- 若规定根结点的层数为 1 ,具有 n 个结点的满二叉树的深度 h =
三 . 二叉树存储结构
二叉树一般可以使用两种结构存储 ,一种顺序结构 , 一种链式结构。
3.1 顺序存储结构
顺序结构存储就是使用数组来存储 ,一般使用数组只适合表示完全二叉树 , 因为不是完全二叉树会有空间的浪费 , 完全二叉树更适合使用顺序结构存储 。
**现实生活中 , 我们通常把****堆 (一种二叉树 )**使用顺序结构的数组来存储 , 需要注意的是这里的堆和 操作系统虚拟进程地址空间中的堆 是两回事 , 一个是数据结构 , 一个是操作系统中管理内存的一块区域分段 。
3.2 链式结构
二叉树的链式存储结构是指 , 用链表来表示一棵二叉树 ,即用链来指示元素的逻辑关系 。 通常的方法是链表中每个结点由三个域组成 , 数据域 和左右指针域 , 左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链 和 三叉链,接下来介绍的是二叉链 , 在后续更新的数据结构 --> 如红黑树 , 会使用到三叉链 。
四 . 实现顺序结构二叉树
一般堆使用 顺序结构的数组 来存储数据 , 堆是一种特殊的二叉树 , 具有二叉树的特性的同时 , 还具备其他的特性 。
4.1 树的概念与结构
如果由一个关键码的集合 K = { } , 它把所有元素按完全二叉树的顺序存储方式存储 , 一个一维数组中 , 并满足 : ( 且 ) , i = 0,1,2...) , 称为小堆(或大堆) 。将根结点最大的堆叫做最大堆或大根堆 , 根结点最小的堆叫最小堆或小根堆 。
堆具有以下性质 :
1 ) 堆中某个结点的值总是 不大于 或者 不小于 其父结点的值 ;
2 ) 堆总是一棵完全二叉树 ;
二叉树的性质 :
对于具有 n 个节点的完全二叉树 , 如果按照 从上到下 , 从左到右 的数组顺序对所有结点 从 0 开始编号 , 则对于序号为 i 的结点有 :
1 ) 若 i > 0 ,i 位置 结点的双亲序号 : ( i - 2 ) / 2 ;
i = 0 , i 为根结点编号 , 无双亲结点
2 )若 2 i + 1< n ,左孩子序号 : 2i + 1 , 2i+1>= n 否则无左孩子
3 ) 若 2 i + 2 < n , 右孩子序号 : 2i + 2 , 2i+2>= n 否则无右孩子
4.2 堆的常见接口/函数的实现
堆 一般使用顺序结构的数组来存储数据 :
初始化
堆 的底层结构是数组 。
typedef int HPDataType;
//定义堆的结构
typedef struct Heap
{
HPDataType* arr;
int size; // 有效数据
int capacity; //空间容量
}HP;
//堆的初始化
void HPInit(HP* php);
//堆的初始化
void HPInit(HP* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
销毁
当数组不为空的时候 , 进行空间释放
//堆的销毁
void HPDeatory(HP* php)
{
if (php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
向上调整
主要运用于 堆的插入后进行数据顺序的调整 :
将新数据插入到数组的尾上,即最后一个孩子之后 , 插入之后如果堆的性质遭到破坏 , 将新插入的结点顺着其双亲往上调整到合适的位置即可 -----> 向上调整算法 , 直到满足堆( 大堆 / 小堆) !
1 ) 先找插入数据的父结点 ( child - 1 ) / 2
2 ) 然后子结点与父结点进行判断大小,是否需要交换
3 ) 交换后,仍需继续比较,直到满足了堆的需求
//向上调整
void AdjustUP(HPDataType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
// > : 大堆
// < : 小堆
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
插入数据
1 ) 判断空间是否足够 , 不足够时进行增容 ( realloc)
2 ) 插入数据 , 让后让有效数据size往后走 (size++)
3 ) 进行向上调整!
//插入数据
void HPPush(HP* php, HPDataType x)
{
assert(php);
//空间不足时 -- 申请空间 -- realloc
if (php->size == php->capacity)
{
int newCapacity = php->capacity == 0 ? sizeof(HPDataType) : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
php->arr = tmp;
php->capacity = newCapacity;
}
//空间足够 - 插入数据
php->arr[php->size] = x;
php->size++;
AdjustUP(php->arr, php->size - 1);
}
判断是否为空
如果有效数据为 0 ,即为空
//判断堆是否为空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
求有效数字Size
直接返回size
//判断size
int HPSize(HP* php)
{
assert(php);
return php->size;
}
向下调整
一般用于堆的删除后 , 进行顺序的调整 ;
注意 : 删除堆是删除堆顶的数据 !
前提 : 左右子树必须是一个堆才能调整 !
1 )先让堆顶的数据与 最后一个有效数据交换
2 ) 然后删除最后一个数据
3 ) 让交换完之后的堆顶数据与 子结点 进行对比 , 直到满足需求!
然后一直往下调整
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;
while (child < n)
{
//先找最小孩子
if (child+1 < n && arr[child] > arr[child + 1])
{
child++;
}
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
删除数据
1 ) 删除数据之前需要判断堆是否为空!
2 ) 将堆顶的数据与最后一个有效数据进行交换
3 ) size--;
4 ) 从堆顶开始向下调整
//删除堆顶数据
void HPPop(HP* php)
{
//堆不为空 -- 才可以进行数据的删除
assert(!HPEmpty(php));
// 从堆顶开始比较
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
//向下调整
AdjustDown(php->arr, 0 ,php->size);
}
总代码
test.c
#include "Heap.h"
void test()
{
HP hp;
HPInit(&hp);
HPPush(&hp, 1);
HPPush(&hp, 2);
HPPush(&hp, 3);
HPPush(&hp, 4);
HPPop(&hp);
HPPop(&hp);
HPPop(&hp);
HPPop(&hp);
HPDeatory(&hp);
}
int main()
{
test();
return 0;
}
Heap.c
#include "Heap.h"
//堆的初始化
void HPInit(HP* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向上调整
void AdjustUP(HPDataType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
// > : 大堆
// < : 小堆
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//插入数据
void HPPush(HP* php, HPDataType x)
{
assert(php);
//空间不足时 -- 申请空间 -- realloc
if (php->size == php->capacity)
{
int newCapacity = php->capacity == 0 ? sizeof(HPDataType) : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
php->arr = tmp;
php->capacity = newCapacity;
}
//空间足够 - 插入数据
php->arr[php->size] = x;
php->size++;
AdjustUP(php->arr, php->size - 1);
}
//判断堆是否为空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//判断size
int HPSize(HP* php)
{
assert(php);
return php->size;
}
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;
while (child < n)
{
//先找最小孩子
if (child+1 < n && arr[child] > arr[child + 1])
{
child++;
}
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
//删除堆顶数据
void HPPop(HP* php)
{
//堆不为空 -- 才可以进行数据的删除
assert(!HPEmpty(php));
// 从堆顶开始比较
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
//向下调整
AdjustDown(php->arr, 0 ,php->size);
}
//堆的销毁
void HPDeatory(HP* php)
{
if (php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HPDataType;
//定义堆的结构
typedef struct Heap
{
HPDataType* arr;
int size; // 有效数据
int capacity; //空间容量
}HP;
//堆的初始化
void HPInit(HP* php);
//向上调整
void AdjustUP(HPDataType* arr, int child);
//插入数据
void HPPush(HP* php , HPDataType x);
//判断堆是否为空
bool HPEmpty(HP* php);
//判断size
int HPSize(HP* php);
//向下调整
void AdjustDown(HPDataType* arr,int parent, int n);
//删除堆顶数据
void HPPop(HP* php);
//堆的销毁
void HPDeatory(HP* php);