什么是树
树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继。因此,树是递归定义的(后面会体验到树递归的暴力)。子树是不相交的,相交就是图了,⼀棵N个结点的树有N-1条边。
![](https://i-blog.csdnimg.cn/direct/b4c8edf6af5448a39c3ff7bedf7081b0.png)
![](https://i-blog.csdnimg.cn/direct/fc806c97c53a4d988a3665d8cac7f232.png)
树的相关术语
父结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点。
子结点/孩子结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点。
树的度:⼀棵树中,最⼤的结点的度称为树的度。
叶子结点/终端结点:度为 0 的结点称为叶结点。
结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推。
树的高度或深度:树中结点的最⼤层次。
结点的祖先:从根到该结点所经分⽀上的所有结点。
路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列。
森林:由 m(m>0) 棵互不相交的树的集合称为森林。
树的表示方法代码展示
struct TreeNode
{
struct Node* child; // 左边开始的第⼀个孩⼦结点
struct Node* brother; // 指向其右边的下⼀个兄弟结点
int data; // 结点中的数据域
}
二叉树满二叉树完全二叉树的区别
二叉树
二叉树是树形结构的一种,⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空。
二叉树
![](https://i-blog.csdnimg.cn/direct/95667b304174429b84c8a9ae6882a9c7.png)
二叉树具有以下几个特点
1. ⼆叉树不存在度⼤于 2 的结点
2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树
![](https://i-blog.csdnimg.cn/direct/866374c9648649cfb6a0994de60e3702.png)
满二叉树
⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为 K ,且结点总数是 2k - 1 ,则它就是满⼆叉树。
满二叉树性质
一个非空的满二叉树第i层上最多有个结点。
深度为h的二叉树的最大节点数为。
具有n结点的满二叉树的深度为h=log2(n+1)。
完全二叉树
完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度为 K 的,有 n 个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从 1 ⾄ n 的结点⼀⼀对应时称之为完全⼆叉树。要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
二叉树的存储结构
具有顺序结构和链式结构,顺序结构存储是使用数组来存储,一般使用数组来表示完全二叉树,因为不完全二叉树会有空间的浪费。链式存储是指,用链表来表示一颗二叉树,用链表来指元素的逻辑关系。
顺序二叉树的实现
实现的方法
一般使用顺序结构的数组来存储,堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他的特性。
大小堆
小根堆:堆中的父节点小于子节点的值,小堆的堆顶元素是堆中最小的。
大根堆:堆中的父节点大于子节点的值,小堆的堆顶元素是堆中最大的。
堆的向上调整
堆的向上排序是,是从孩子节点和父节点进行比较然后从底部向上进行排序,这里要找到孩子节点和父节点的位置,左孩子节点的位置=父节点位置*2+1。
堆的向下调整
堆的向上排序是,是从孩子节点和父节点进行比较然后从栈顶向下进行排序
堆的插入
堆的插入是从队尾进行插入,然后对堆进行调整(按照大根堆或者小根堆进行排序的)
void HeapPush(Heap* hp, Hedatatype x)
{
assert(hp);
//检查空间是否满足
if (hp->size == hp->capacity)
{
int Newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
Hedatatype *temp = (Hedatatype *)realloc(hp->arr, Newcapacity * sizeof(Hedatatype));
if (temp == NULL)
{
perror("fail!!!");
exit(1);
}
hp->arr = temp;
hp->capacity = Newcapacity;
}
hp->arr[hp->size]=x;
Heapup(hp->arr, hp->size);
//遗漏
++hp->size;
}
堆得删除
删除出的是堆顶的元素,因此先对栈顶元素和尾部进行替换,然后进行向下排序,这个排序和向下排序其实还有一点区别,因为要比较左右子树的大小。
void HeadDown(Hedatatype* arr, int prient, int n)
{
int child = prient * 2 + 1;//左面
//比较左右子节点
while (child<n)
{
//注意此处一定要限制最后的孩子节点不越界
if ((child + 1) < n && (arr[child] > arr[child + 1]))
{
//保证了找到最小的
child++;
}
if (arr[child] < arr[prient])
{
swap(&arr[child], &arr[prient]);
prient = child;
child = 2 * prient + 1;
}
else
{
break;
}
}
}
堆得实现原码
head.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int Hedatatype;
typedef struct heap {
Hedatatype* arr;
int size;
int capacity;
}Heap;
void heinit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, Hedatatype x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
Hedatatype HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
head.c文件
#include"heap.h"
//初始化
void heinit(Heap* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
if (hp->arr)
{
free(hp->arr);
}
hp->arr = NULL;
hp->capacity = hp->size = 0;
}
void swap(int *x,int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
//向上置换
void Heapup(Hedatatype *arr, int child)
{
int prient = (child - 1) / 2;
while (child>0)
{
if (arr[child] < arr[prient])
{
swap(&arr[prient],&arr[child]);
child = prient;
prient = (child - 1) / 2;
}
else {
break;
}
}
}
void HeapPush(Heap* hp, Hedatatype x)
{
assert(hp);
//检查空间是否满足
if (hp->size == hp->capacity)
{
int Newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
Hedatatype *temp = (Hedatatype *)realloc(hp->arr, Newcapacity * sizeof(Hedatatype));
if (temp == NULL)
{
perror("fail!!!");
exit(1);
}
hp->arr = temp;
hp->capacity = Newcapacity;
}
hp->arr[hp->size]=x;
Heapup(hp->arr, hp->size);
//遗漏
++hp->size;
}
// 堆的删除
//堆删除的是头顶元素
void HeadDown(Hedatatype* arr, int prient, int n)
{
int child = prient * 2 + 1;//左面
//比较左右子节点
while (child<n)
{
if ((child + 1) < n && (arr[child] > arr[child + 1]))
{
//保证了左侧小
child++;
}
if (arr[child] < arr[prient])
{
swap(&arr[child], &arr[prient]);
prient = child;
child = 2 * prient + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp && hp->size);
swap(&hp->arr[0],&hp->arr[hp->size-1]);
--hp->size;
//堆向下调整
HeadDown(hp->arr, 0, hp->size);
}
// 取堆顶的数据
Hedatatype HeapTop(Heap* hp)
{
assert(hp && hp->size);
return hp->arr[0];
}
// 堆的判空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
test.c文件
#include"heap.h"
void test01()
{
Heap hl;
heinit(&hl);
int arr[] = {17,20,10,13,19,15};
for (int i = 0; i < 6; i++)
{
HeapPush(&hl, arr[i]);
}
while (!HeapEmpty(&hl))
{
printf("%d ", HeapTop(&hl));
//删除操作
HeapPop(&hl);
}
HeapDestory(&hl);
}
int main()
{
test01();
return 0;
}
![](https://i-blog.csdnimg.cn/direct/423cd0c0ca1e48fd94acbd06da9bb52d.png)