1.树
1.1树的基本概念
空树:节点为0的树
非空树:
非空树的特性:
- 有且只有一个根结点
- 没有后继的结点称为“叶子结点”(或终端结点)
- 有后继的结点称为“分支结点”(或非终端结点)
- 除了根结点外,任何一个结点都只有一个前驱
- 每个结点可以有0个或多个后继
子树:根结点的后继结点互不相交,且自身往下又是一棵树,则把这个集合称为根结点的子树。
-
树是一种递归定义的数据结构
-
两个结点的路径:从前趋结点到后继结点的路线,只能从上往下,不能反着走
-
路径长度:即路径经过了几条边,长度就为几
-
结点的层次(深度):从上往下数的层数
-
结点的高度:从下往上数层数,有几层就有几高
-
树的高度(深度):一棵树总共有几层
-
结点的度:即有几个分支,就是度为几
-
树的度:即各结点的度的最大值
-
有序树:逻辑上看,树中结点的各子树从左至右是有次序的,不能互换
-
无序树:逻辑上看,树中结点的各子树从左至右是无次序的,可以互换
-
森林:森林是任意棵(也可以为0棵)不相交的树的集合
1.2树的性质
结点数 = 总度数 + 1
- 度为m的树第i层至多有m^i-1个结点(i>=1)
- m叉树第i层至多有m^i-1个结点(i>=1)
2.二叉树
2.1二叉树的基本定义和术语
二叉树是n(n>=0)个结点的有限集合:
- 可以为空二叉树,即n = 0。
- 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一颗二叉树。
特点:每个结点至多只有两棵子树,左右子树不能颠倒(二叉树是有序数)
二叉树是递归定义的数据结构
二叉树的五种状态:
2.2特殊的二叉树
- 满二叉树。一棵高度为h,且含有(2^h)-1个结点的二叉树
特点:
- 只有最后一层有叶子结点
- 不存在度为1的结点
- 按层序从1开始编号,结点i的左孩子为2i,右孩子2i+1;结点i的父亲节为[i/2] (如果有的话)
- 完全二叉树。当且其每个结点都与高度为h的满二叉树中编号的结点一一对应时,称为完全二叉树
特点:
-
只有最后两层可能有叶子结点,叶子结点必须先有左结点才能有右结点
-
最多只有一个度为1的结点
-
按层序从1开始编号,结点i的左孩子为2i,右孩子2i+1;结点i的父亲节为[i/2] (如果有的话)
-
当i<=|n/2|时为分支结点,当i>=|n/2|时为叶子结点
- 二叉排序树。一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
- 左子树上所有结点的关键字均小于根结点的关键字
- 右子树上所有结点的关键字均大于根结点的关键字
- 左子树和右子树又各是一棵二叉排序树
- 平衡二叉树。树上任一结点的左子树和右子树得到深度之差不超过1。
平衡二叉树也是一个二叉排序树,其拥有更高的搜索效率。
2.3二叉树的性质
- 设非空二叉树中度为0,1和2的结点个数分别为n0、n1、n2,则n0 = n2 + 1(叶子结点比二分支结点多一个)
假设树中结点总数为n,则
- n = n0 + n1 + n2
- n = n1 + 2n2 + 1
-
二叉树第i层至多有2^(i-1)个结点(i>=1),
m叉树第i层至多有m^(i-1)个结点(i>=1)
-
高度为h的二叉树至多有2h-1个结点(满二叉树),高度为h的m叉树至多有mh-1/m-1个结点
-
对于完全二叉树,可以由结点数n推出度为0、1和2的结点个数为n0、n1、n2
-
完全二叉树最多智能有一个度为1的结点,即
n1 = 0 或 1,n0 = n2 + 1 , n0 + n2 一定是奇数
-
若完全二叉树有2k个(偶数)个结点,则必有n1=1, n0=k, n2=k-1
-
若完全二叉树有2k-1个(奇数)个结点,则必有n1=0, n0=k, n2=k-1
性质总结:
二叉树:
- n0 = n2 + n1
- 第i层至多有2^(i-1)个结点(i>=1)
- 高度为h的二叉树至多有2^h-1个结点
完全二叉树:
- 对于完全二叉树,可以由结点数n推出度为0、1和2的结点个数为n0、n1、n2(突破点:完全二叉树最多只会有一个度为1的结点)
3.二叉树的存储结构
3.1顺序存储
- 在二叉树的顺序存储中,一定要把二叉树的结点编号与完全二叉树对应起来
完全二叉树:
代码:
//定义结构体
#define MaxSize 100
struct TreeNode {
ElemType value; //结点中的数据元素
bool isEmpty; //结点是否为空
};
TreeNode t[MaxSize];
定义一个长度为MaxSize的数组t,按照从上至下,从左至右的顺序依次存储完全二叉树中的各个结点
非完全二叉树时:
结论:二叉树的顺序存储结构,只适合存储完全二叉树。
- i的左孩子为2i
- i的右孩子为2i+1
- i的父结点为i/2
3.2链式存储
非完全二叉树时:
代码:
//二叉树的结点(链式存储)
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
- n个结点的二叉链表共有 n+1 个空链域
构建链式存储二叉树代码:
struct ElemType{
int value;
};
//定义结构体BiTNode
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;//定义左右孩子结点指针
struct BiTNode *parent; //定义父结点指针(当业务需要频繁寻找结点的父结点时在定义本句)
}BiTNode,*BiTree;
//定义一棵空树
BiTree root = NULL; //声明一个指向根结点的指针
//插入根结点
root = (BiTree) malloc(sizeof(BiTNode));//用malloc函数申请一个根结点root
root->data = {1}; //往根结点中存入数字1
root->lchild = NULL;//让根结点左右指针指向NULL
root->rchild = NULL;
//插入新结点
BiTNode * p = (BiTree *) malloc(sizeof(BiTNode));//用malloc函数申请一个新结点p
p->data = {2}; //往新结点中存入数字2
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p; //把根结点的左孩子指针指向结点p
4.二叉树的遍历
遍历:按照某种次序把所有结点都访问一遍
先/中/后序遍历:基于树的递归特性确定的次序规则
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
4.1先序遍历
先序遍历的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 访问根结点;
- 先序遍历左子树;
- 先序遍历右子树。
//定义二叉树结构体
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//先序遍历
void PreOrder(BiTree T){
if(T!=NULL){ //判断二叉树是否为空
visit(T); //访问根结点
PreOrder(T->lchild);//递归遍历左子树
PreOrder(T->rchild);//递归遍历右子树
}
}
4.2中序遍历
中序遍历的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 先序遍历左子树;
- 访问根结点;
- 先序遍历右子树。
//定义二叉树结构体
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//中序遍历
void InOrder(BiTree T){
if(T!=NULL){ //判断二叉树是否为空
InOrder(T->lchild);//递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild);//递归遍历右子树
}
}
4.3后序遍历
后序遍历的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 先序遍历左子树;
- 先序遍历右子树;
- 访问根结点。
//定义二叉树结构体
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//中序遍历
void PostOrder(BiTree T){
if(T!=NULL){ //判断二叉树是否为空
PostOrder(T->lchild);//递归遍历左子树
PostOrder(T->rchild);//递归遍历右子树
visit(T); //访问根结点
}
}
以上递归算法的空间复杂度为:O(h)
4.4层序遍历
算法思想:
- 初始化一个辅助队列
- 根结点入队
- 若队列非空,则队头结点出队,访问该结点,并将其左,右孩子插入队尾(如果有的话)
- 重复步骤3直到队列为空
代码实现:
//层序遍历
void LevelOrder(BiTree T){
LinkQueue Q; //定义链队列
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //将根结点入队
while(!IsEmpty(Q)){ //队列不空则循环
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
}
5.由遍历序列构造二叉树
-
若只给出一棵二叉树的 前/中/后/层 序遍历序列中的一种,不能唯一确定一棵二叉树。
-
只有同时给出以下几种遍历序列的其中两种才可以确定一棵二叉树:
5.1前序+中序遍历序列
前序遍历:根结点,前序遍历左子树,前序遍历右子树
中序遍历:中序遍历左子树,根结点,中序遍历右子树
5.2后序+中序遍历序列
后序遍历:前序遍历左子树,前序遍历右子树,根结点
中序遍历:中序遍历左子树,根结点,中序遍历右子树