1. 树
1.1 树的定义
树结构是一种非线性结构,在这种结构中一个数据元素可以有两个或以上的直接后继元素。
一般用来描述客观世界中存在的层次结构关系。
![](https://img-blog.csdnimg.cn/direct/abefbbb1701d46268f43f2bbdefb390c.png)
树是
N (N >= 0)
个结点的有限集。 在任意一个非空树中
:
有且只有一个特定的结点称之为
根结点
(root)
当
N > 1
时, 其余结点可分
m
个
(m > 0)
个互不相交的有限集
T1, T2, T3 ..... Tn,
其中每个有限集本
身又是一颗树,并称之为根的子树。
1.2 树的基本概念
树的结点包含一个数据元素以及若干个指向其子树的分支
(
指针
)
双亲、孩子、兄弟
结点的子树的根称之为该结点的孩子
对应的,该结点称之为它的盖子结点的双亲结点
(
父结点
)
具有相同父结点的结点,互为兄弟
![](https://img-blog.csdnimg.cn/direct/1004ee73276449fba215d8b6b05149f5.png)
结点的度
结点拥有的子树的数量称之为结点的度
度为
0
的结点称之为
叶子结点
或
终端结点
度不为
0
的结点称之为
分支结点
结点的层次
从根结点定义起,根结点成为第一层,根结点的孩子称为第二层,依次类推
....
树中结点的最大层次称之为
树的深度
或
树的高度
2. 二叉树
二叉树是一种树状结构,它的特点是每个结点最多只有两颗子树,即二叉树不存在度大于
2
的结点,并且
二叉树的子树有左、右之分,其次序不能颠倒。
二叉树与一般树的区别
:
二叉树中的结点的度最大为
2
,一般的树不限制结点数
二叉树中的子树要区分左子树和右子树,即使只有一颗子树,也要明确说明它是左子树还是右子
树。
2.1 二叉树的五种形态
(1)
空二叉树
(2)
只有一个根结点的二叉树
(3)
只有左子树
(4)
只有右子树
(5)
有左子树和右子树
![](https://img-blog.csdnimg.cn/direct/aeefd554cec5461d9253f0b0a76c9eb4.png)
2.2 二叉树的性质
1
、在二叉树的第
i
层上最多有
2^(i-1)
个结点
(i >= 1)
2
、深度为
k
的二叉树最多有
2^k - 1
个结点
3
、对任何一颗二叉树,如果其叶子结点数为
n0,
度为
2
的结点为
n2
则它们之间的关系
:
n2 = n0-1
4
、满二叉树
一颗深度为
k
且具有
2^k - 1
个结点的二叉树
(
如果不改变树的深度,无法再挂载新的结点
)
5
、完全二叉树
除去最后一层就是一颗满二叉树
最后一层的结点必须是依次从左往右排
6
、具有
n
个结点的完全二叉树,如果从上往下,从左往右,从
1
到
n
进行编号,那么编号为
i
的结点,它
的左子结点(如果存在)的编号为
2*i
,
右子节点
(
如果存在
)
的编号为
2*i + 1
, 它的父结点
(
如果存在
)
的编号为
i / 2
问?
结点数为699的完全二叉树的叶子结点的数量是多少?
结点数为奇数的完全二叉树不存在度为1的结点
叶子结点数为n0, 度为2的结点为n2
则它们之间的关系: n2 = n0 - 1
n0 + n2 = 699;
则: 2*n0 - 1 = 699
可以得到 n0 = 350
2.3 二叉树的存储
2.3.1 顺序存储结构
顺序存储结构就是用一组地址连续的存储单元来存储树的结点
必须把结点排成一个适当的线性序列,并且结点在这个序列中的位置能反映出结点之间的逻辑关系
对于深度为
k
的完全二叉树,除了第
k
层,其余各层中含有最大结点数,由此,从一个结点的编号
可以推导出它的双亲,左孩子,右孩子的编号。
假设编号为
i
的结点
如果
i == 1,
则它是根结点,无双亲结点,如果
i > 1 ,
那么它的双亲结点
i/2 (
向下取整
)
如果
2*i <= n (
结点数
)
, 则该结点的左孩子的编号为
2*i,
否则没有左孩子
如果
2*i+1 <= n (
结点数
)
, 则该结点的右孩子的编号为
2*i+1,
否则没有右孩子
![](https://img-blog.csdnimg.cn/direct/0cc8d609ee734c948016ef58183de55d.png)
使用完全二叉树采用顺序结构存储简单且节省空间。
但是对于一般的二叉树,则不太适用
因为一般的二叉树要用顺序结构存储也必须要按照完全二叉树的形式来存,也就是需要添加一些实际上
并不存在的
“
虚结点
”
, 这将造成空间浪费。
2.3.2 链式存储结构
二叉树的结点中需要包括
:
结点的数据
,
左子树的根, 右子树的根
因此可以采用二叉链表来存储二叉树。
二叉链表
:
即一个结点包含两个指针来分别指向左子树和右子树
假设结点中存储的数据元素类型是整型,则二叉树的结点类型定义如下
:
typedef int ElemType;
typedef struct BiTnode //binary tree
{
ElemType data; //数据域
struct BiTnode* left; //左孩子
struct BiTnode* right; //右孩子
}BiTNode;
2.4 二叉树的遍历
如何按某种搜索路径去访问树中的每一个结点,使得每个结点均被访问一次,而且仅访问一次
2.4.1 先序遍历
遍历顺序:
根
左
右
访问方法
:
1
、先访问根结点
2
、再按照先序遍历顺序去访问根结点的左子树
3
、再按照先序遍历顺序去访问根结点的右子树
2.4.2 中序遍历
遍历顺序:
左
根
右
访问方法
:
1
、按照中序遍历顺序去访问根结点的左子树
2
、访问根结点
3
、按照中序遍历顺序去访问根结点的右子树
2.4.3 后序遍历
遍历顺序
:
左
右
根
访问方法
:
1
、按照后序遍历顺序去访问根结点的左子树
2
、按照后序遍历顺序去访问根结点的右子树
3
、访问根结点
2.4.4 层序遍历
需要借助一个队列
step1:
把树的根结点入队
step2:
获取队头元素,并进行打印
step3:
判断当前队头元素是否存在左、右孩子。如果存在左、右孩子,则将其入队
step4:
出队
重复上面的过程
2
、
3
、
4
步
8 3 10 1 6 14 5 7 0
先序遍历
: 8 3 1 6 5 7 10 14
中序遍历
: 1 3 5 6 7 8 10 14
后序遍历
: 1 5 7 6 3 14 10 8
层序遍历
: 8 3 10 1 6 14 5 7
2.5 二叉排序树
Binary sort Tree, BST
二叉排序树,也叫二叉查找树或者二叉搜索树
二叉排序树要么是一颗空树,要么是具有以下性质的二叉树
1
、如果它的左子树不为空,则左子树上的所有结点的值
小于
它的根结点的值
2
、如果它的右子树不为空,则右子树上的所有结点的值
大于
它的根结点的值
3
、它的左子树,右子树也是二叉排序树
与一般的二叉树相比,
BST
最大的优势,在于
"
排序
"
二字
查找值为
6
的结点:
1
、访问根结点
8
2
、根据
BST
的特性,左子树中结点的值都比根结点要小,右子树中结点的值都比根结点要大,
6 <
8
,因此值为
6
的结点应该在根结点
8
的左子树中
3
、查看左子树的根节点
3
,
6>3,
因此值为
6
的根结点应该在以
3
为根结点的右子树中
4
、查看右子树的根结点
6
,
6==6
, 找到了,返回该结点的地址
![](https://img-blog.csdnimg.cn/direct/cbf2ef6342a747b39f077b32aca31f4e.png)
搜索数据
BitNode* search(BitNode* root, int value)
{
//找不到或者找到了都可以结束
if(root == NULL || root->data == value)
return root;
if(value > root->data)
return search(root->right, value);
return search(root->left, value);
}
删除结点
注意
:
结点删除之后不能影响到树的结构
![](https://img-blog.csdnimg.cn/direct/ab6f29da55f1404da490f7c764032fc6.png)
1
、被删除的结点是叶子结点,直接删除即可,不会影响到树的结构
![](https://img-blog.csdnimg.cn/direct/4ad89cbb274047b78dfc8b9429370096.png)
2
、被删除的结点
(p),
只有一个孩子结点
只有右孩子,没有左孩子,则只需要把需要删除的结点
(p)
的右孩子链接到
p
的父结点,之后删除
p
即可
只有左孩子,没有右孩子,则只需要把需要删除的结点
(p)
的左孩子链接到
p
的父结点,之后删除
p
即可
![](https://img-blog.csdnimg.cn/direct/9318638e285b4a27a50b99f3c4b9e624.png)
3
、 被删除的结点
(p),
有左孩子和右孩子
a.
从待删除的结点的左子树中查找一个最大值或者从右子树中查找一个最小值
b.
将查找到的结点
(p2)
与需要删除的结点
(p)
进行数据的交换
c.
删除那个查到到的最大值或者最小值的结点
(p2)
即可
![](https://img-blog.csdnimg.cn/direct/dd489a0ac35040b8bacfcd9222990b2c.png)
2.6 平衡二叉树
Balance Binary Tree
或
Height-Balance tree
但是一般称之为
AVL
树
(AVL,
平衡二叉树的提出者的名字缩写
)
AVL
树可以解决
BST
的不平衡问题
(
一般认为平衡二叉树就是一种特殊的二叉排序树
)
AVL
树那么是一颗空树,要么是具有以下性质的二叉树:
左、右子树都是
AVL
树
左子树高度与右子树高度之差的绝对值不会超过
1
根据
AVL
树的性质,计算得到平衡因子会出现如下情况
:
平衡因子: 该结点的左子树的高度减去它的右子树的高度
如果平衡因子为
1
、
0
、
-1
这三个值,则认为该结点是符合平衡二叉树的定义了
否则,认为该结点不平衡,需要重新平衡
一个新的结点接入,可能导致多个结点不平衡,此时需要从最小的失衡子树的根结点
(
失衡结点
)
开
始进行处
typedef int ElemType;
typedef struct AVLnode
{
ElemType data;
struct AVLnode* left;
struct AVLnode* right;
int height; //树的高度,空树为0
}AVLNode;
树的四种不平衡情况
:
重新平衡
如果发现某个结点不平衡,那么就需要对该结点重新平衡,实现结点重新平衡的方法,称为旋转
旋转分为两种情况
:
一种称为左旋
(
逆时针
)
一种称为右旋
(
顺时针
)
四种失衡的处理
(1)
单向右旋平衡处理
(SIngleRotateWithRight)
![](https://img-blog.csdnimg.cn/direct/a2891b746ae7428f870478d43d6675d8.png)
(2)
单向左旋平衡处理
(SingleRotateWithLeft)
(3)
双向旋转
(
先左旋再右旋平衡处理
)(DoubleRotateLeftRight)
![](https://img-blog.csdnimg.cn/direct/165192f427da4d4c88f5d4a31ee1fbed.png)
(4)
双向旋转
(
先右旋再左旋平衡处理
)(DoubleRotateRightLeft)
![](https://img-blog.csdnimg.cn/direct/ee0aaa26c8374ba5a5de52001b2bd43e.png)
2.7 哈夫曼树
哈夫曼树又称为最优二叉树
它是
n
个带权叶子结点构成的所有二叉树中,带权路径长度
(WPL)
最小的二叉树
一颗树的带权路径长度定义: 树中所有叶子结点的带权路径长度之和
结点的带权路径长度
:
从根结点到该结点之间的路径长度与结点上的权的乘积
![](https://img-blog.csdnimg.cn/direct/8918d3419e2242e0aec332961742f5a9.png)
哈夫曼编码
(
前缀编码
)
在电报通信,电文是以二进制
1/0
序列传送的,每个字符对应一个二进制编码。
为了缩短电文的总长度,采用的是不等长编码方式,把使用频率高的字符用短编码,使用频率低的字符
用长编码。
我们把使用频率作为权值,每个字符作为叶子结点来构建哈夫曼树,然后每个分支结点的左右分支分别
用
0
和
1
编码,这样就得到了每个叶子结点的二进制编码
(
哈夫曼编码
)
![](https://img-blog.csdnimg.cn/direct/b9417d0479314cb9a3dc3c6dbf5b6f72.png)
构建哈夫曼树
假设有
n
个权值为
w1, w2, w3....... wn
的叶子结点,则构建哈夫曼树的规则
step1:
构建森林
我们把每一个叶子结点,都当做是单独的树
(
只有根结点
)
,这样就形成了一个森林
我们把这片森林存放到一个优先队列中,方便取出最小的结点
step2:
从优先队列中取出权值最小的两个结点,然后再创建一个新的结点,作为它们的父结点。
父结点的权值就是这两个结点的权值之和。
step3:
把新创建的父结点加入到优先队列
重复第二、第三直到优先队列中只剩下一个结点为止,该结点就是哈夫曼树的根结点。
2.8 森林与二叉树的转化(了解)
2.8.1 森林转化为二叉树
a.
将森林中的每棵树都转化为二叉树
![](https://img-blog.csdnimg.cn/direct/75882a840567417597c015b79b9855b9.png)
b.
所有的二叉树合并为一颗二叉树
![](https://img-blog.csdnimg.cn/direct/2ec7298a240149609d07d2ccc31a49a7.png)
2.8.2 二叉树转化为森林
2.9 二叉树转化表达式(了解)
如果有一个中缀表达式
100 + 200 * 3 / 2
将该表达式用二叉树来描述
:
(1)
叶子结点都是操作数
(2)
非叶子结点都是运算符
(3)
根结点的运算符优先级最低
![](https://img-blog.csdnimg.cn/direct/d82c87729a294223a7e05b80e04e1548.png)
二叉排序树代码:
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct BiTNode //binary tree
{
ElemType data; //数据域
struct BiTNode* left; //左孩子
struct BiTNode* right; //右孩子
}BiTNode;
/*
功能: 向二叉树中插入新的结点
参数:
root: 根结点的地址
value: 需要插入的结点的数据
返回值:
根结点的地址
*/
BiTNode* insert_node(BiTNode* root, ElemType value)
{
//step1: 创建一个新的结点
BiTNode* new_node = malloc(sizeof(BiTNode));
new_node->data = value;
new_node->left = NULL;
new_node->right = NULL;
BiTNode* p = root; //遍历指针
//step2: 找到正确的位置,并进行插入
//从无到有
if(root == NULL)
{
root = new_node;
}
//从少到多
else
{
while(1)
{
//需要插入的数据比当前根结点的数据小
//则往当前根节点的左子树中搜索空位进行插入
if(value < p->data)
{
if(p->left == NULL) //找到空位了
{
p->left = new_node;
break;
}
else //如果该结点的左孩子已经存在了,则继续往下找
{
p = p->left;
}
}
//需要插入的数据比当前根结点的数据大
//则往当前根节点的右子树中搜索空位进行插入
else if(value > p->data)
{
if(p->right == NULL) //找到空位了
{
p->right = new_node;
break;
}
else//如果该结点的右孩子已经存在了,则继续往下找
{
p = p->right;
}
}
else //相等
{
printf("需要进行插入的数据有误,已存在该数据,无法再次插入\n");
break;
}
}
}
return root;
}
/*
功能: 递归实现结点的插入
参数:
root: 根结点的地址
value: 需要插入的结点的数据
返回值:
返回根结点的地址
*/
BiTNode* digui_insert_node(BiTNode* root, ElemType value)
{
//找到空位,进行插入
if(root == NULL)
{
//创建一个新的结点
BiTNode* new_node = malloc(sizeof(BiTNode));
new_node->data = value;
new_node->left = NULL;
new_node->right = NULL;
root = new_node;
return root;
}
//从少到多
if(value < root->data)
root->left = digui_insert_node(root->left, value);
if(value > root->data)
root->right = digui_insert_node(root->right, value);
return root;
}
/*
功能: 创建一个二叉树
返回值:
返回二叉树的根结点的地址
*/
BiTNode* create_sort_tree()
{
BiTNode* root = NULL;
ElemType value;
//从终端接收数据,并将数据存储到结点中
//用于构建一个二叉排序树
while(1)
{
scanf("%d", &value);
if(value == 0)
break;
//往二叉树中添加结点
//root = insert_node(root, value);
root = digui_insert_node(root, value);
}
return root;
}
/*
功能: 递归实现二叉树的先序遍历: 根 、左、右
参数:
root: 根结点的地址
*/
void pre_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、先访问根结点
printf("%d ", root->data);
//2、再按照先序遍历顺序去访问根结点的左子树
pre_order(root->left);
//3、再按照先序遍历顺序去访问根结点的右子树
pre_order(root->right);
}
/*
功能: 递归实现二叉树的中序遍历: 左、根、右
参数:
root: 根结点的地址
*/
void mid_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、按照中序遍历顺序去访问根结点的左子树
mid_order(root->left);
//2、访问根结点
printf("%d ", root->data);
//3、按照中序遍历顺序去访问根结点的右子树
mid_order(root->right);
}
/*
功能: 递归实现二叉树的后序遍历: 左、右、根
参数:
root: 根结点的地址
*/
void post_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、按照后序遍历顺序去访问根结点的左子树
post_order(root->left);
//2、按照后序遍历顺序去访问根结点的右子树
post_order(root->right);
//3、访问根结点
printf("%d ", root->data);
}
/*
功能: 在二叉树中查找数据
参数:
root: 二叉树的根结点的地址
value: 需要查找的数据
返回值:
如果成功查找到指定的数据,则返回该数据所在的结点的地址
如果失败返回NULL
*/
BiTNode* search(BiTNode* root, int value)
{
//找不到或者找到了都可以结束
if(root == NULL || root->data == value)
return root;
if(value > root->data)
return search(root->right, value);
return search(root->left, value);
}
/*
功能: 删除二叉树中的一个结点
参数:
root: 二叉树的根结点的地址
value: 需要删除的数据
返回值:
根结点的地址
*/
BiTNode* delete_node(BiTNode* root, ElemType value)
{
BiTNode* p = root; //遍历指针,用于搜索二叉树中value对应的结点
BiTNode* pre = NULL; //保存p的父结点的地址
//step1: 查找需要删除的结点的位置
while(p != NULL)
{
if(p->data == value)//找到了
{
break;
}
else if(value > p->data) //往右子树找
{
pre = p;
p = p->right;
}
else if(value < p->data) //往左子树找
{
pre = p;
p = p->left;
}
}
//step2: 进行删除
//1. 没有找到
if(p == NULL)
{
printf("没有在二叉树中找到值为%d的结点\n", value);
return root;
}
//2. 找到了
//case1: 被删除的结点是叶子结点,直接删除即可,不会影响到树的结构
if(p->left == NULL && p->right == NULL) //p没有孩子
{
if(p == root) //p是根结点
{
free(p);
return NULL;
}
if(p == pre->right) //p是pre的右孩子
{
pre->right = NULL; //断开与需要删除的结点的链接
free(p);
}
else if(p == pre->left) //p是pre的左孩子
{
pre->left = NULL; //断开与需要删除的结点的链接
free(p);
}
}
//case2: 被删除的结点(p), 只有一个孩子结点
//只有右孩子,没有左孩子,则只需要把需要删除的结点(p)的右孩子链接到p的父结点,之后删除p即可
//只有左孩子,没有右孩子,则只需要把需要删除的结点(p)的左孩子链接到p的父结点,之后删除p即可
else if(p->left == NULL || p->right == NULL)
{
if(p->right != NULL) //p有一个右孩子
{
if(p == root) //p是根节点
{
root = p->right; //p的右孩子成为新的根节点
p->right = NULL;
free(p);
}
else if(p == pre->right) //p是pre的右孩子
{
pre->right = p->right;
p->right = NULL;
free(p);
}
else if(p == pre->left) //p是pre的左孩子
{
pre->left = p->right;
p->right = NULL;
free(p);
}
}
else if(p->left != NULL) //p只有左孩子
{
if(p == root) //p是根结点
{
root = p->left;
p->left = NULL;
free(p);
}
else if(p == pre->left) //p是pre的左孩子
{
pre->left = p->left;
p->left = NULL;
free(p);
}
else if(p == pre->right) //p是pre的右孩子
{
pre->right = p->left;
p->left = NULL;
free(p);
}
}
}
//case3: 被删除的结点(p), 有左孩子和右孩子
else if(p->left != NULL && p->right != NULL)
{
//a. 从待删除的结点的左子树中查找一个最大值或者从右子树中查找一个最小值
//搜索p的左子树中的最大值
BiTNode* p2 = p->left; //p2来进行搜索
BiTNode* pre2 = NULL; //保存p2的父结点
//当p2的右孩子不存在,则认为找到了左子树中的最大值
while(p2->right != NULL)
{
pre2 = p2;
p2 = p2->right;
}
//b. 将查找到的结点(p2)与需要删除的结点(p)进行数据的交换
//p2没有右子树,即p的左子树中最大值所在的结点就是p2
if(p2 == p->left)
{
ElemType temp = p2->data;
p2->data = p->data;
p->data = temp;
//c. 删除那个查到到的最大值或者最小值的结点(p2)即可
p->left = p2->left;
p2->left = NULL;
free(p2);
}
else //p的左子树中包含右子树
{
ElemType temp = p2->data;
p2->data = p->data;
p->data = temp;
//c. 删除那个查到到的最大值或者最小值的结点(p2)即可
pre2->right = p2->left;
p2->left = NULL;
free(p2);
}
}
return root;
}
int main()
{
//创建一颗二叉排序树
BiTNode* root = create_sort_tree();
//删除结点
root = delete_node(root, 3);
printf("先序遍历: ");
pre_order(root);
printf("\n");
printf("中序遍历: ");
mid_order(root);
printf("\n");
printf("后序遍历: ");
post_order(root);
printf("\n");
/*
BiTNode* search_data = search(root, 8);
if(search_data == NULL)
printf("没有搜索到该数据\n");
else
printf("搜索到了数据: %d\n", search_data->data);
*/
return 0;
}
层次遍历代码:
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
/*
功能: 向二叉树中插入新的结点
参数:
root: 根结点的地址
value: 需要插入的结点的数据
返回值:
根结点的地址
*/
BiTNode* insert_node(BiTNode* root, ElemType value)
{
//step1: 创建一个新的结点
BiTNode* new_node = malloc(sizeof(BiTNode));
new_node->data = value;
new_node->left = NULL;
new_node->right = NULL;
BiTNode* p = root; //遍历指针
//step2: 找到正确的位置,并进行插入
//从无到有
if(root == NULL)
{
root = new_node;
}
//从少到多
else
{
while(1)
{
//需要插入的数据比当前根结点的数据小
//则往当前根节点的左子树中搜索空位进行插入
if(value < p->data)
{
if(p->left == NULL) //找到空位了
{
p->left = new_node;
break;
}
else //如果该结点的左孩子已经存在了,则继续往下找
{
p = p->left;
}
}
//需要插入的数据比当前根结点的数据大
//则往当前根节点的右子树中搜索空位进行插入
else if(value > p->data)
{
if(p->right == NULL) //找到空位了
{
p->right = new_node;
break;
}
else//如果该结点的右孩子已经存在了,则继续往下找
{
p = p->right;
}
}
else //相等
{
printf("需要进行插入的数据有误,已存在该数据,无法再次插入\n");
break;
}
}
}
return root;
}
/*
功能: 创建一个二叉树
返回值:
返回二叉树的根结点的地址
*/
BiTNode* create_sort_tree()
{
BiTNode* root = NULL;
ElemType value;
//从终端接收数据,并将数据存储到结点中
//用于构建一个二叉排序树
while(1)
{
scanf("%d", &value);
if(value == 0)
break;
//往二叉树中添加结点
root = insert_node(root, value);
}
return root;
}
/*
功能: 递归实现二叉树的先序遍历: 根 、左、右
参数:
root: 根结点的地址
*/
void pre_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、先访问根结点
printf("%d ", root->data);
//2、再按照先序遍历顺序去访问根结点的左子树
pre_order(root->left);
//3、再按照先序遍历顺序去访问根结点的右子树
pre_order(root->right);
}
/*
功能: 递归实现二叉树的中序遍历: 左、根、右
参数:
root: 根结点的地址
*/
void mid_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、按照中序遍历顺序去访问根结点的左子树
mid_order(root->left);
//2、访问根结点
printf("%d ", root->data);
//3、按照中序遍历顺序去访问根结点的右子树
mid_order(root->right);
}
/*
功能: 递归实现二叉树的后序遍历: 左、右、根
参数:
root: 根结点的地址
*/
void post_order(BiTNode* root)
{
if(root == NULL)
return ;
//1、按照后序遍历顺序去访问根结点的左子树
post_order(root->left);
//2、按照后序遍历顺序去访问根结点的右子树
post_order(root->right);
//3、访问根结点
printf("%d ", root->data);
}
/*
功能: 二叉树的层序遍历
参数:
root: 二叉树的根结点的地址
*/
void level_order(BiTNode* root)
{
//初始化一个空的队列
Queue* queue = queue_init();
//step1: 把树的根结点入队
push_queue(queue, root);
BiTNode* p = NULL; //遍历指针
//只要队列不为空
while(queue_empty(queue) == false)
{
//step2: 获取队头元素,并进行打印
p = get_front(queue);
printf("%d ", p->data);
//step3: 判断当前队头元素是否存在左、右孩子。如果存在左、右孩子,则将其入队
if(p->left != NULL)
push_queue(queue, p->left);
if(p->right != NULL)
push_queue(queue, p->right);
//step4: 出队
pop_queue(queue);
}
}
int main()
{
//创建一颗二叉排序树
BiTNode* root = create_sort_tree();
printf("先序遍历: ");
pre_order(root);
printf("\n");
printf("中序遍历: ");
mid_order(root);
printf("\n");
printf("后序遍历: ");
post_order(root);
printf("\n");
printf("层序遍历: ");
level_order(root);
printf("\n");
return 0;
}
平衡二叉树代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
typedef int ElemType;
typedef struct AVLNode
{
ElemType data; //数据域
struct AVLNode* left; //左孩子
struct AVLNode* right; //右孩子
int height; //树的高度,空树为0
}AVLNode;
/*
功能: 获取指定树(root)的高度,如果root为NULL, 返回0
参数:
root: 指定进行高度获取的树的根结点的地址
返回值:
树的高度
*/
int get_height(AVLNode* root)
{
if(root == NULL)
{
return 0;
}
else
{
return root->height;
}
}
/*
功能: 对K2实施单向右旋平衡处理
参数:
K2, 最小的失衡子树的根结点
返回值:
返回平衡之后的新的根结点K1
*/
AVLNode* SingleRotateWithRight(AVLNode* K2)
{
AVLNode* K1 = K2->left;
K2->left = K1->right;
K1->right = K2;
//更新K1和K2的高度
K2->height = MAX(get_height(K2->left), get_height(K2->right))+1;
K1->height = MAX(get_height(K1->left), get_height(K1->right))+1;
return K1;
}
/*
功能: 对K2实施单向左旋平衡处理
参数:
K2, 最小的失衡子树的根结点
返回值:
返回平衡之后的新的根结点K1
*/
AVLNode* SingleRotateWithLeft(AVLNode * K2)
{
AVLNode* K1 = K2->right;
K2->right = K1->left;
K1->left = K2;
//更新K1和K2的高度
K2->height = MAX(get_height(K2->left), get_height(K2->right))+1;
K1->height = MAX(get_height(K1->left), get_height(K1->right))+1;
return K1;
}
/*
功能: 对K3实施双向平衡处理(先左后右)
参数:
K3, 最小的失衡子树的根结点
返回值:
返回平衡之后的新的根结点
*/
AVLNode* DoubleRotateLeftRight(AVLNode* K3)
{
K3->left = SingleRotateWithLeft(K3->left);
return SingleRotateWithRight(K3);
}
/*
功能: 对K3实施双向平衡处理(先右后左)
参数:
K3, 最小的失衡子树的根结点
返回值:
返回平衡之后的新的根结点
*/
AVLNode* DoubleRotateRightLeft(AVLNode* K3)
{
K3->right = SingleRotateWithRight(K3->right);
return SingleRotateWithLeft(K3);
}
/*
功能: 平衡二叉树的结点插入
参数:
root: 根结点的地址
value: 需要插入的结点的数据
返回值:
返回根结点的地址
*/
AVLNode* insert_node(AVLNode* root, ElemType value)
{
//找到空位,进行插入
if(root == NULL)
{
//创建新的结点
AVLNode* new_node = malloc(sizeof(AVLNode));
new_node->data = value;
new_node->left = NULL;
new_node->right = NULL;
new_node->height = 1;
root = new_node;
return root;
}
//从少到多
if(value < root->data)
{
//新的结点插入在左子树上
root->left = insert_node(root->left, value);
//更新结点的深度
root->height = MAX(get_height(root->left), get_height(root->right))+1;
//判断新的结点插入之后左子树是否平衡
if(get_height(root->left) - get_height(root->right) > 1) //造成了不平衡
{
if(value < root->left->data) //左深左插 LL型
{
root = SingleRotateWithRight(root); //单向右旋平衡处理
}
else //左深右插 LR型
{
root = DoubleRotateLeftRight(root); //实施双向平衡处理(先左后右)
}
}
}
else if(value > root->data) //新的结点插入在右子树上
{
//新的结点插入在右子树上
root->right = insert_node(root->right, value);
//更新结点的深度
root->height = MAX(get_height(root->left), get_height(root->right))+1;
//判断新的结点插入之后左子树是否平衡
if(get_height(root->right) - get_height(root->left) > 1) //造成了不平衡
{
if(value > root->right->data) //右深右插 RR型
{
root = SingleRotateWithLeft(root); //单向左旋平衡处理
}
else //右深左插 RL型
{
root = DoubleRotateRightLeft(root); //实施双向平衡处理(先右后左)
}
}
}
return root;
}
/*
功能: 创建一颗平衡二叉树
返回值:
返回平衡二叉树的根结点
*/
AVLNode* create_tree()
{
AVLNode* root = NULL;
ElemType value;
while(1)
{
scanf("%d", &value);
if(value == 0)
break;
root = insert_node(root, value);
}
return root;
}
/*
功能: 递归实现二叉树的先序遍历: 根 、左、右
参数:
root: 根结点的地址
*/
void pre_order(AVLNode* root)
{
if(root == NULL)
return ;
//1、先访问根结点
printf("%d ", root->data);
//2、再按照先序遍历顺序去访问根结点的左子树
pre_order(root->left);
//3、再按照先序遍历顺序去访问根结点的右子树
pre_order(root->right);
}
/*
功能: 递归实现二叉树的中序遍历: 左、根、右
参数:
root: 根结点的地址
*/
void mid_order(AVLNode* root)
{
if(root == NULL)
return ;
//1、按照中序遍历顺序去访问根结点的左子树
mid_order(root->left);
//2、访问根结点
printf("%d ", root->data);
//3、按照中序遍历顺序去访问根结点的右子树
mid_order(root->right);
}
/*
功能: 递归实现二叉树的后序遍历: 左、右、根
参数:
root: 根结点的地址
*/
void post_order(AVLNode* root)
{
if(root == NULL)
return ;
//1、按照后序遍历顺序去访问根结点的左子树
post_order(root->left);
//2、按照后序遍历顺序去访问根结点的右子树
post_order(root->right);
//3、访问根结点
printf("%d ", root->data);
}
int main()
{
//创建一颗平衡二叉树
AVLNode* root = create_tree();
printf("先序遍历: ");
pre_order(root);
printf("\n");
printf("中序遍历: ");
mid_order(root);
printf("\n");
printf("后序遍历: ");
post_order(root);
printf("\n");
return 0;
}
哈夫曼树代码:
#include "queue.h"
/*
功能: 构建一颗哈夫曼树
参数:
结点的权值-----数组保存
数组的长度
返回值:
返回构建好的哈夫曼树的根结点的地址
*/
HTNode* create_tree(int* weight, int len)
{
HTNode* root = NULL; //一颗空的哈夫曼树
//初始化一个空的优先队列
Queue* queue = queue_init();
//step1: 构建森林
HTNode* nodes = malloc(sizeof(HTNode) * len);
for(int i = 0; i < len; i++)
{
nodes[i].weight = weight[i];
nodes[i].left = NULL;
nodes[i].right = NULL;
//我们把这片森林存放到一个优先队列中,方便取出最小的结点
push_queue(queue, &nodes[i]);
}
//step2: 从优先队列中取出权值最小的两个结点,然后再创建一个新的结点,
//作为它们的父结点。父结点的权值就是这两个结点的权值之和。
while(queue->num >= 2) //直到优先队列中只剩下一个结点为止,该结点就是哈夫曼树的根结点
{
//获取权值最小的两个结点
HTNode* pleft = get_front(queue);
pop_queue(queue);
HTNode* pright = get_front(queue);
pop_queue(queue);
//为这两个取出的结点创建一个新的父结点
HTNode* parent = malloc(sizeof(HTNode));
parent->left = pleft;
parent->right = pright;
parent->weight = pleft->weight + pright->weight;
//step3: 把新创建的父结点加入到优先队列
push_queue(queue, parent);
}
root = get_front(queue);
return root;
}
/*
功能: 递归实现二叉树的先序遍历: 根 、左、右
参数:
root: 根结点的地址
*/
void pre_order(HTNode* root)
{
if(root == NULL)
return ;
//1、先访问根结点
printf("%d ", root->weight);
//2、再按照先序遍历顺序去访问根结点的左子树
pre_order(root->left);
//3、再按照先序遍历顺序去访问根结点的右子树
pre_order(root->right);
}
int main()
{
int weight[] = {9, 12, 6, 3, 5, 15};
HTNode* root = create_tree(weight, 6);
pre_order(root);
printf("\n");
return 0;
}