树的应用
树
树的性质
总结树的度、树高、结点数等属性之间的联系
1.树的结点总数等于所有结点的度数之和加1。
2.T的第
i
i
i(
i
≥
1
i\ge1
i≥1)层最多有
k
i
−
1
k^{i-1}
ki−1个结点。
3.高度为h的T中最多有(
k
h
−
1
)
k^{h}-1)
kh−1)/(
k
−
1
k-1
k−1)个结点(
k
k
k为树的度)。
4.T的最小高度为:
h
h
h=
⌈
l
o
g
k
(
n
(
k
−
1
)
+
1
)
⌉
\left \lceil log_{k}(n(k-1)+1)\right \rceil
⌈logk(n(k−1)+1)⌉=
⌊
l
o
g
k
n
(
k
−
1
)
⌋
+
1
\left \lfloor log_{k}n(k-1)\right \rfloor+1
⌊logkn(k−1)⌋+1
5.T的最大高度为
n
−
k
+
1
n-k+1
n−k+1(
k
k
k为树的度)。
树(森林)的定义和画图
写代码:使用“双亲表示法”定义顺序存储的树(以及森林)
#define Max_Tree_Size 150 //树的最多结点数
typedef int Elemtype; //树结点的数据类型
typedef struct PTNode //树的结点定义(结点结构)
{
Elemtype data; //结点数据元素
int parent; //双亲位置
}PTNode;
typedef struct PTree //树的类型表示(树结构)
{
PTNode nodes[Max_Tree_Size]; //结点数组
int r,n; //根的位置和结点数
}PTree;
写代码:使用“孩子表示法”定义链式存储的树(以及森林)
#define Max_Tree_Size 150 //树的最多结点数
typedef int Elemtype; //树结点的数据类型
typedef struct CTNode //孩子结点定义
{
int Child;
struct CTNode* next;
}*Childptr;
typedef struct CTBox //表头结构
{
Elemtype data;
Childptr firstchild; //孩子链表的表头指针
}CTBox;
typedef struct CTree //树的类型表示
{
CTBox nodes[Max_Tree_Size]; //结点数组
int n,r; //结点数和根的位置
}CTree;
写代码:使用“孩子兄弟表示法”定义链式存储的树(以及森林)
typedef int Elemtype; //树结点的数据类型
typedef struct CSNode //孩子结点定义
{
Elemtype data; //数据域
struct CSNode* firstchild,*rightsibling; //指针域(存储第一个孩子结点的地址和该结点右兄弟的地址)
}CSNode,*CSTree;
写代码:求以孩子兄弟表示法存储的森林的叶结点数
type struct CSNode //孩子结点定义
{
Elemtype data; //数据域
struct CSNode *firstchild,*rightsibling; //指针域
}CSNode,*CSTree;
int Leaves(CSTree t) //计算以孩子兄弟表示法存储的森林的叶子结点数
{
if(t == NULL)
return 0; //树空返回0
if(t->firstchild == NULL) //若结点无左孩子,则该结点必是叶子结点
return 1+Leaves(t->rightsibling); //返回叶子结点和其右兄弟子树中的叶子结点树
else
return Leaves(t->firstchild) + Leaves(t->rightsibling); //孩子子树和兄弟子树中叶子子树之和
}
写代码:以孩子兄弟表示法为存储结构设计递归算法求数的深度
int Height(CSTree bt) //递归求以孩子兄弟表示法的树的深度
{
int hc,hs;
if(bt == NULL)
return 0;
else //高度取子女高度+1与兄弟子树高度更大者
{
hc = Height(bt->firstchild); //第一子女树高
hs = Height(bt->rightsibling); //兄弟树高
if(hc + 1 >hs)
return hc+1;
else
return hs;
}
}
对比“树的孩子表示法存储”vs“图的邻接矩阵存储”vs“散列表的拉链法”vs“基数排序”
自己动手创造,画一个结点总数不少于10的树,并画出对应“双亲表示法”、“孩子表示法”、“孩子兄弟表示法”三种数据结构示意图(至少包含三棵树的森林)
树:
双亲表示法:
孩子表示法:
孩子兄弟表示法:
将树表示成二叉树:
孩子兄弟表示法示意图:
二叉树
二叉树的性质
总结二叉树的度、树高、结点数等属性之间的联系
1.二叉树的第k(k>=1)层最多有
2
k
−
1
2^{k-1}
2k−1个结点。
2.高度为h的二叉树中最多有
2
h
−
1
2^{h}-1
2h−1个结点。
3.对任意一棵二叉树T,其叶子结点数等于度为二的结点数加1,即
n
0
=
n
2
+
1
n_{0}=n_{2}+1
n0=n2+1。
4.具有
n
(
n
≥
1
)
n(n\ge1)
n(n≥1)个结点的完全二叉树的高度
h
h
h满足
h
h
h=
⌈
l
o
g
2
(
n
+
1
)
⌉
\left\lceil log_{2}(n+1)\right\rceil
⌈log2(n+1)⌉=
⌊
l
o
g
2
n
⌋
+
1
\left \lfloor log_{2}n \right \rfloor +1
⌊log2n⌋+1。
5.完全二叉树中度为1的结点的数目只能是0或1。
6.完全二叉树中叶子结点只可能在最下面两层出现。
7.二叉树的最大高度为
n
n
n。
二叉树的顺序存储和基本操作实现
写代码:定义顺序存储的二叉树(数组实现,树的结点从数组下标0开始存储)
#define Max_Tree_Size 150 //二叉树的最大结点数
typedef int TElemtype;
typedef int Statues;
typedef TElemtype SqBiTree[Max_Tree_Size]; //0号单元存储根结点
void InitSqBiTree(SqBiTree& T) { //创建一个空的二叉树
for (int i = 0; i < Max_Tree_Size; i++) {
T[i] = 0;
}
}
Statues SetNodes(SqBiTree& T,int i ,TElemtype e) { //给结点i赋值
if (T == NULL)return 0;
if (i >= Max_Tree_Size)return 0;
T[i] = e;
return 1;
}
Statues SetLeftchild(SqBiTree& T, int i, TElemtype e) { //给结点i的左孩子赋值
if (T == NULL)return 0;
if (i * 2 >= Max_Tree_Size)return 0;
T[i * 2] = e;
return 1;
}
Statues SerRightchild(SqBiTree& T, int i, TElemtype e) { //给结点i的右孩子赋值
if (T == NULL)return 0;
if (i * 2 + 1 >= Max_Tree_Size)return 0;
T[i * 2 + 1] = e;
return 1;
}
写代码:定义顺序存储的二叉树(数组实现,树的结点从数组下标1开始存储)
#define Max_Size_Tree 150
typedef int TElemtype;
typedef struct SqBiTree{
TElemtype *elem; //元素存储空间基址,建表时按实际长度分配,0号单元留空
int LSqBiTree; //表示树的数组最大长度
}SqBiTree;
写代码:运用非递归算法实现先/中/后序遍历
先序遍历:
void PreOrder(BiTNode *T){
if(T == NULL) return; //特殊情况处理
Stack STK = initStack(); //等价于递归栈
push(STK,T);
while(!stackEmpty(STK)){
T = pop(STK);
visit(T); //访问当前结点<==>本层递归处理
if(T->rchild != NULL)
push(STK,T->rchild); //右孩子先入栈<==>后递归处理右子树
if(T->lchild != NULL)
push(STK,T->lchild); //左孩子后入栈<==>先递归处理左子树
}
}
中序遍历:
void GoAlonglchild(BiTNode *T,Stack &STK){
while(T != NULL){
STK.push(T);
T = T->lchild;
}
void InOrder(BiTNode *T){
Stack STK = initStack();
GoAlonglchild(T,STK);
while(!stackEmpty(STK)){
T = pop(STK);
visit(T);
GoAlonglchild(T->rchild);
}
后序遍历:
void PostOrder(){
if(T ==NULL) return;
Stack STK = initStack(); //主栈:实现N-R-L递归遍历
push(STK,T);
Stack auxSTK = initStack(); //辅助栈用来保存调整顺序后的结点
while(!stackEmpty(STK)){
T = pop(STK);
// 将N-R-L变成L-R-N
push(auxSTK,T); //等价于逆置递归顺序
if(T->lchild !=NULL)
push(STK,T->lchild); //左孩子先入栈<==>后递归处理左子树
if(T->rchild !=NULL)
push(STK,T->rchild); //右孩子后入栈<==>先递归处理右子树
}
while (!stackEmpty(auxSTK)){
T = pop(auxSTK);
visit(T); //依次出辅助栈并访问
}
}
哈夫曼树的应用
哈夫曼树的性质
总结哈夫曼树的度、树高、结点数等属性之间的联系
基础哈夫曼树的性质
1.若哈夫曼树中有
n
0
n_{0}
n0个叶子结点,则总共有
2
n
0
−
1
2n_{0}-1
2n0−1个结点,且度为2(构造过程中新建立)的结点数为
n
0
−
1
n_{0}-1
n0−1。
2.哈夫曼树是一棵二叉树,结点的度只有0或2,没有度为1的结点。
3.上一层所有结点的权值均大于下一层。
m叉哈夫曼树的性质
1.若
m
m
m叉哈夫曼树中有
n
0
n_{0}
n0个叶子结点,则总共有
(
m
n
0
−
1
)
/
(
m
−
1
)
(mn_{0}-1)/(m-1)
(mn0−1)/(m−1)个结点,且度为
m
m
m(构造过程中新建立)的结点数为
(
n
0
−
1
)
/
(
m
−
1
)
(n_{0}-1)/(m-1)
(n0−1)/(m−1)。
2.
m
m
m叉哈夫曼树结点的度只有
0
0
0或
m
m
m。
自己动手创造,写10个字符,并给每个字符设置权值,画出构造哈夫曼树的过程
用文字描述构造该哈夫曼树的过程
1.首先,根据给定的字符集合和对应的权重,创建一个包含所有字符的叶子结点集合。
2.从叶子结点集合中选择权重最小的两个结点作为左右子节点,创建一个新的父结点,并将父结点的权重设置为左右子节点的权重之和。
3.将新创建的父结点加入到结点集合中,并从结点集合中移除原来的两个子节点
4.重复步骤2、3直到集合中只剩下一个结点,即根结点。该根结点就是哈夫曼树的根结点。
选择结点4、5作为左右结点得到父结点9加入结点集合中,删除结点4、5。
选择结点6、8作为左右结点得到父结点14加入结点集合中,删除结点6、8。
选择结点9、9作为左右结点得到父结点18加入结点集合中,删除结点9、9。
选择结点10、14作为左右结点得到父结点24加入结点集合中,删除结点10、14。
选择结点18、18作为左右结点得到父结点36加入结点集合中,删除结点18、18。
选择结点24、29作为左右结点得到父结点53加入结点集合中,删除结点24、29。
选择结点36、36作为左右结点得到父结点72加入结点集合中,删除结点36、36。
选择结点45、53作为左右结点得到父结点98加入结点集合中,删除结点45、53。
选择结点72、98作为左右结点得到父结点170加入结点集合中,删除结点72、98。
结点集合只剩下结点170,该结点为此哈夫曼树的根结点。
基于你所构造的哈夫曼树,写出10个字符的哈夫曼编码
A:00000;
B:00001;
C:11010;
D:11011;
E:0001;
F:1100;
G:001;
H:111;
I:01;
J:10;
带权路径长度WPL=(权重X编码位数)求和
用文字描述一棵哈夫曼树“译码的过程”(即如何将二进制哈夫曼编码翻译为字符)
1.首先,根据哈夫曼树的构造过程,得到每个字符对应的编码。编码是由0、1组成的二进制序列,根据字符在哈夫曼树的路径确定。
2.从根结点开始,根据输入的编码位逐步向下遍历哈夫曼树。如果当前位是0,则向左子结点移动;如果当前位是1,则向右子结点移动。
3.当遍历到叶子结点时,即找到了一个字符的编码。将该字符输出,并重新回到根结点,继续下一个编码的遍历。
4.重复步骤2、3直至所有输入的编码全部遍历完毕。
通过以上步骤可将输入的编码序列译码为原始的字符序列。由于哈夫曼树的构造过程保证了每个字符的编码是唯一的,因此译码过程是可靠的。
并查集的应用
写代码:定义一个并查集(用长度为n的数组实现)
typedef int Elemtype;
typedef struct UnionFindSet {
int n; //总的元素(结点)个数
Elemtype* parents; //存储元素parent的数组的首元素指针
}UnionFindSet;
UnionFindSet Initialize(int n) { //并查集的初始化
UnionFindSet ufs;
ufs.n = n;
for (int i = 0; i < n; i++) {
ufs.parents[i] = -1; //初始时,所有结点的parent设置为-1,表示树内只有一个结点
}
return ufs;
}
基于上述定义,实现并查集的基本操作——并Union
int Union(UnionFindSet& ufs, Elemtype e, Elemtype t) {
int eRoot = Find(ufs, e);
int tRoot = Find(ufs, t);
if (eRoot == -1 || tRoot == -1) //判断是否查找成功
return -1;
if (eRoot != tRoot)
ufs.parents[tRoot] = eRoot; //将y树合并到x树
return eRoot;
}
基于上述定义,实现并查集的基本操作——查Find
int Find(UnionFindSet& ufs, Elemtype e) {
if (e >= ufs.n || e < 0) //该元素在集合中不存在
return -1;
while (ufs.parents[e] >= 0) //parent值>0,表示有父节点
e = ufs.parents[e];
return e;
}
自己设计一个例子,并查集初始有10个元素,进行若干次Union操作,画出每一次Union后的样子
基于上一步得到的并查集,进行若干次Find操作(每次Find会进行“路径压缩”)画出每次Find(路径压缩)后的样子
二叉排序树、平衡二叉树的应用题潜在考法
自己设计一个例子,给出不少于10个关键字序列,按顺序插入一棵初始为空的二叉排序树,画出每一次插入后的样子
关键字序列{25,20,99,16,11,1,22,26,42,17}
对该二叉排序树进行中序遍历得到有序关键字序列{1,11,16,17,20,22,25,26,42,99}
基于你设计的例子,计算二叉排序树在查找成功和查找失败时的ASL
ASLsuc=(1X1+2X2+3X3+4X3+5X1)/10=3.1;(10为○的个数;1、2、3、3、1为○在该层的个数;1、2、3、4、5为○的层数)
ASLunsuc=(2X1+3X3+4X5+5X2)/11=3.73;(11为□的个数;1、3、5、2为□在该层的个数;2、3、4、5为□的父结点所在的层数)
基于你设计的例子,依次删除4个元素,画出每一次删除之后的样子(需要包含四种删除情况——删一个叶子节点、删一个只有左子树的结点、删一个只有右子树的结点、删一个既有左子树又有右子树的结点)
自己设计一个例子,给出不少于10个关键字序列,按顺序插入一棵初始为空的平衡二叉树,画出每一次插入后的样子
关键字序列{25,20,99,16,11,1,22,26,42,17}
对该平衡二叉树进行中序遍历得到有序关键字序列{1,11,16,17,20,22,25,26,42,99}
基于你设计的例子,计算平衡二叉树在查找成功和查找失败时的ASL
ASLsuc=(1X1+2X2+3X3+4X4)/10=3;(10为○的个数;1、2、3、4为○在该层的个数;1、2、3、4为○的层数)
ASLunsuc=(2X1+3X2+4X8)/11=3.73;(11为□的个数;1、2、8为□在该层的个数;2、3、4为□的父结点所在的层数)