目录
一、树的类型定义
树是 n 个结点的有限集。在任意一棵非空树中:
(1)有且仅有一个特定的称为根的结点;
(2)当 n > 1时,其余结点可分为 m 个互不相交的有限集。
其中每一个集合本身又是一棵树,并且称为根的子树。
树形结构的特点:在数据元素的非空有限集中,(1)存在唯一的一个被称做“根”的结点;(2)存在一个或多个被称做“叶子”的结点;(3)除根之外,集合中的每个结点均只有一个双亲;(4)除叶子之外,集合中每个结点有一个或多个孩子。
基本术语:
空树:没有结点的树
结点:包含一个数据元素及若干指向其子树的分支 结点的度:结点拥有的子树数
叶子:度为 0 的结点 分支结点:度不为 0 的结点 树的度:树内各结点的度的最大值
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲,同一个双亲的孩子之间互称为兄弟
结点的祖先是从根到该结点所经分支上的所有结点
结点的子孙是以某结点为根的子树中的任意结点
结点的层次从根开始定义,根为第一层,双亲在同一层的结点互为堂兄弟
树的深度:树中结点的最大层次
有序树:将树中结点的各子树看成从左至右是有次序的 无序树:树中结点没有次序
森林:若干棵(可以为 0 )互不相交的树的集合
二、二叉树
二叉树是每个结点至多只有两棵子树(即二叉树中不存在度大于 2 的结点),且子树的左右次序不能任意交换的树。
满二叉树:深度为 k 且有 个结点的二叉树。
完全二叉树:对满二叉树的结点进行从上到下、从左到右进行编号,若有 n 个结点,从 1 至 n 每个结点都与满二叉树的编号对应的二叉树称为完全二叉树。
正则二叉树:每个结点的度为 0 或 2 的二叉树。
性质:1.在二叉树的第 i 层上至多有个结点
;
2.深度为 k 的二叉树至多有个结点
;
3.对任何一棵二叉树 T,如果其叶子数为,度为 2 的结点数为
,则
;
4.具有 n 个结点的完全二叉树的深度为;
5.如果对一棵有 n 个结点的完全二叉树的结点按层序编号(每层从左到右编号),则对任意结点 ,有:
(1)如果,则结点 i 是二叉树的根,无双亲;如果
,则其双亲是结点
;
(2)如果,则结点 i 无左孩子(结点 i 为叶子结点);否则其左孩子是结点
;
(3)如果,则结点 i 无右孩子;否则其右孩子是结点
。
含有 n 个结点的不相似的二叉树有棵(卡特兰数)。
二叉树的基本操作:
CreateBiTree(Bitree &T) // 构造二叉链表表示的二叉树T
PreOrderTraverse(BiTree T) // 先序遍历二叉树T
InOrderTraverse(BiTree T) // 中序遍历二叉树T
PostOrderTraverse(BiTree T) // 后序遍历二叉树T
LevelOrderTraverse(BiTree T) // 层次遍历二叉树T
1.顺序存储结构(仅适用于完全二叉树)
用一组地址连续的存储单元自上而下、自左而右存储完全二叉树的结点元素,即编号为 i 的结点元素存储在定义的一维数组的下标 i - 1 处。
存储结构:
#define MAX_TREE_SIZE 100 // 二叉树的最大结点数
typedef TElemType SqBiTree[MAX_TREE_SIZE]; // 0号单元存储根结点
2.链式存储结构
1)二叉链表存储结构:
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode, *BiTree;
2)三叉链表存储结构:
typedef struct TriTNode{
TElemType data;
struct TriTNode *lchild, *parent, *rchild; // 左右孩子指针和双亲指针
}TriTNode, *TriTree;
3.二叉树的遍历
1)先序遍历:访问根结点(D);先序遍历左子树(L);先序遍历右子树(R)
递归算法:
Status PreOrderTraverse(BiTree T){
// 先序遍历二叉树T
if(T == NULL) return OK; // 空二叉树
else{
visit(T); // 访问根结点(最简单的visit函数为输出根结点的值)
PreOrderTraverse(T->lchild); // 递归遍历左子树
PreOrderTraverse(T->rchild); // 递归遍历右子树
}
}// PreOrderTraverse
非递归算法:
Status PreOrderTraverse(BiTree T){
// 先序遍历二叉树T(非递归)
InitStack(S); p = T;
while(p || !IsEmpty(S)){
if(p){
visit(p->data); // 访问根结点的值
push(S, p); // 根指针进栈
p = p->lchild; // 遍历左子树
}
else{
Pop(S, p); // 根指针出栈
p = p->rchild; // 遍历右子树
}
}
return OK;
}// PreOrderTraverse
2)中序遍历:中序遍历左子树(L);访问根结点(D);中序遍历右子树(R)
递归算法:
Status InOrderTraverse(BiTree T){
// 中序遍历二叉树T
if(T == NULL) return OK; // 空二叉树
else{
InOrderTraverse(T->lchild); // 递归遍历左子树
visit(T); // 访问根结点(最简单的visit函数为输出根结点的值)
InOrderTraverse(T->rchild); // 递归遍历右子树
}
}// InOrderTraverse
非递归算法:
Status InOrderTraverse(BiTree T){
// 中序遍历二叉树T(非递归)
InitStack(S); p = T;
while(p || !IsEmpty(S)){
if(p){
push(S, p); // 根指针进栈
p = p->lchild; // 遍历左子树
}
else{
pop(S, p); // 根指针出栈
visit(p->data); // 访问根结点的值
p = p->rchild; // 遍历右子树
}
}
return OK;
}// InOrderTraverse
3)后序遍历:后序遍历左子树(L);后序遍历右子树(R);访问根结点(D)
递归算法:
Status PostOrderTraverse(BiTree T){
// 后序遍历二叉树T
if(T == NULL) return OK; // 空二叉树
else{
PostOrderTraverse(T->lchild); // 递归遍历左子树
PostOrderTraverse(T->rchild); // 递归遍历右子树
visit(T); // 访问根结点(最简单的visit函数为输出根结点的值)
}
}// PostOrderTraverse
非递归算法:
Status PostOrderTraverse(BiTree T){
// 后序遍历二叉树T(非递归)
InitStack(S); p = T; PrePop = NULL; // PrePop记录上一个Pop出来的结点
while(p || !IsEmpty(S)){
while(p){ // 一直向左将结点压入堆栈
Push(S, p);
p = p->lchild;
}
// 将Pop的过程改为循环
while(!IsEmpty(S)){ // 后序遍历有两种情况可以Pop该结点
Pop(S, p);
if(p->rchild == PrePop || p->rchild == NULL){ // 该结点的右结点为空或者上一次Pop的是该结点的右结点
visit(p->data); // 访问根结点的值
PrePop = p;
}
else{ // 若不满足以上情况,则说明该结点右侧结点还未被Pop
Push(S, p); // 则将该结点重新压回堆栈
p = p->rchild; // 然后指向该结点的右结点
break; // 退出Pop循环
}
}
}
return OK;
}// PostOrderTraverse
4)层次遍历:从根结点开始按顺序遍历二叉树的每一层
Status LevelOrderTraverse(BiTree T){
// 层次遍历二叉树T
InitQueue(q); p = T;
EnQueue(q, p); // 根结点指针入队
while(!IsEmpty(q)){
DeQueue(q, p); // 根结点指针出队
visit(p->data); // 访问根结点
if(p->lchild) EnQueue(q, p->lchild); // 有左孩子将其入队
if(p->rchild) EnQueue(q, p->rchild); // 有右孩子将其入队
}
return OK;
}// LevelOrderTraverse
4.创建二叉树(二叉链表存储)
Status CreateBiTree(BiTree &T){
// 构造二叉链表表示的二叉树T
// 按先序次序输入二叉树中结点的值(字符),'.'字符表示空树
scanf(&ch);
if(ch == '.') T = NULL;
else{
if(!(T = (BiTNode *)malloc(sizeof(BiTNode)))) exit(OVERFLOW);
T->data = ch; // 生成根结点
CreateBiTree(T->lchild); // 构造左子树
CreateBiTree(T->rchild); // 构造右子树
}
return OK;
}// CreatrBiTree
5.线索二叉树
结点结构:
其中:,
以上述结构构成的二叉链表称为线索链表。
指向结点前驱和后继的指针称为线索(前驱和后继根据遍历序列不同而不同)。
加上线索的二叉树称为线索二叉树。
对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。
先序线索二叉树:
中序线索二叉树:
后序线索二叉树:
三、树和森林
1.树的存储结构
1)双亲表示法:在每个结点中附设一个指示器指示其双亲结点在链表中的位置
#define MAX_TREE_SIZE 100
typedef struct PTNode{ // 结点结构
TElemType data;
int parent; // 双亲位置域
}PTNode;
typedef struct{ // 树结构
PTNode nodes[MAX_TREE_SIZE];
int r, n; // 根的位置和结点数
}PTree;
2)孩子表示法:将每个结点的孩子结点排列起来看成一个线性表,且以单链表存储
typedef struct CTNode{ // 孩子结点
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct{
TElemType data;
ChildPtr firstchild; // 孩子链表头指针
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n, r; // 结点数和根的位置
}CTree;
3)孩子兄弟表示法:以二叉链表作为树的存储结构,链表中结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点(左孩子右兄弟)
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
2.森林与二叉树的转换
1)森林转换成二叉树:如果是森林,则可按如下规则转换成一棵二叉树
。
若 F 为空,即 ,则 B 为空树;
若 F 非空,即 ,则 B 的根 root 即为森林中的第一棵树的根
;B 的左子树 LB 是从
中根结点的子树森林
转换而成的二叉树;其右子树 RB 是从森林
转换而成的二叉树。
口诀:兄弟相连留长子,树变二叉根相连
2)二叉树转换成森林:如果是一棵二叉树,则可按如下规则转换成森林
。
若 B 为空,则 F 为空;
若 B 非空,则 F 中的第一棵树 的根
即为二叉树 B 的根 root ;
中根结点 的子树森林
是由 B 的左子树 LB 转换而成的森林;F 中除
之外其余树组成的森林
是由 B 的右子树 RB 转换而成的森林。
口诀:去掉全部右孩线,孤立二叉再还原
左孩右右连双亲,去掉原来右孩线
3.树和森林的遍历
1)树的先根遍历:先访问根结点,再依次先根遍历根的每棵子树
2)树的后根遍历:先依次后根遍历根的每棵子树,再访问根结点
3)森林的先序遍历:访问第一棵树的根结点;先序遍历第一棵树中根结点的子树森林;先序遍历除去第一棵树之后剩余的树构成的森林
4)森林的中序遍历:中序遍历森林中第一棵树的根结点的子树森林;访问第一棵树的根结点;中序遍历除去第一棵树之后剩余的树构成的森林
四、Huffman树
路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:两个结点间的路径上的分支数目。
树的路径长度:从树根到每一结点的路径长度之和。
树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作。
Huffman树:带权路径长度 WPL 最小的二叉树。
构造Huffman树的方法见:基础算法思想之贪心法_Wmiracle的博客-CSDN博客