数据结构——树
文章目录
前言
- 树的定义和基本术语
- 二叉树的基本概念
- 二叉树的遍历
- 线索二叉树
- 二叉链表和三叉链表
- 树和森林
- 赫夫曼树和哈夫曼编码
提示:以下是本篇文章正文内容,下面案例可供参考
一、树的定义和基本术语
树的定义
- 树的定义:树是由n(n>=0)个结点组成的有限集合。若n=0,则称为空树;若n>0,则:
- 有且仅有一个特定的称为根(root)的结点,只有后继,没有前驱。
- 当n>1时,其余节点可以划分为m(m>0)个互不相交的有限集合。T1,T2,…,Tm. 每个集合又是一棵树称为根的子树。每棵子树的根结点尤且仅有一个直接前驱,但可以有零个或多个,直接后继。
- 树的逻辑结构描述
一棵树的逻辑结构可以用二元组描述为:
其中,n为树中结点个数。若n=0,则为一棵空树,n>0时,称为一棵非空树。而关系r应满足下列条件:
有且仅有一个结点,没有前驱,称该结点为树根
除根节点以外,其余每个结点有且仅有一个直接前驱
树中每个结点可以有多个直接后继(孩子结点)
例题:
K={A,B,C,D,E,F,G,H,I,J,K,L,M}
R={r}
r={<A,B>,<A,C>,<A,D>,<B,E>,<B,F>,<C,G>,<D,H>,<D,I>,<E,J>,<E,K>,<E,L>,<H,M>}
3. 只有一个根结点,其他结点称为根结点的子树.
4. 树的根结点没有前驱结点,除根结点外所有结点只有一个前驱结点,所有结点可以有0或多个后继结点;n个结点的树有n-1个边
树的基本术语
(1)结点: 树的结点包含一个数据元素及若干指向其子树的分支
(2)度: 一个结点包含子树的数目,称为该结点的度。
(3)叶子(终端)结点:度为0的结点,称为叶子结点或树叶,也叫终端结点
(4)孩子结点:若结点X有子树,则子树的根结点为X的孩子结点,也称为孩子,儿子,子女等。
(5)双亲结点:若结点X有子女Y,则X为Y的双亲结点。
(6)祖先结点:从根结点到该结点所经过分支上的所有结点为该结点的祖先
(7)子孙结点:某一结点的子女及子女的子女都为该结点子孙。
(8)兄弟结点:具有同一个双亲的结点,称为兄弟结点。
(9)分支结点:除叶子结点外的所有结点,为分支结点,也叫非终端结点。(度不为0的结点)
(10)层数(层次):根结点的层数为1,其它结点的层数为从根结点到该结点所经过的分支数目再加1。
(11)树的高度(深度):树中结点所处的最大层数称为树的高度,如空树的高度为0,只有一个根结点的树高度1。
(12)树的度:树中结点度的最大值称为树的度。
(13)有序树:若一棵树中所有子树从左到右的排序是有顺序的,不能颠倒次序。称该树为有序树。
(14)无序树:若一棵树中所有子树的次序无关紧要,则称为无序树。
(15)森林(树林):若干棵互不相交的树组成的集合为森林。一棵树可以看成是一个特殊的森林。
(16)堂兄弟结点:在树中,双亲在同一层的那些结点,互成为堂兄弟结点。
(17)根往下走过的路径直到尾结点K,前面所有结点都是K的祖先结点,K是前面所有结点的子孙结点,路径上最接近K的结点叫K的双亲结点,K是其孩子结点,有相同双亲结点的叫兄弟结点
(18)树中一个结点的子结点(直接孩子结点)个数叫称为该结点的度,树中结点最大的度叫树的度
(19)度大于0的叫分支结点,又叫非终端结点,度为0的叫叶子结点(终端结点)
(20)结点的层次,深度是从根结点开始自顶向下逐渐累加;高度是从叶子结点自底向上累加;树的高度(深度)是树中结点最大的层数
(21)路径和路径长度:路径长度是所经过边的个数
(22)森林:是m>=0棵互不相交树的集合
树的表示
-
树型结构表示法
-
凹入法表示法
-
嵌套集合表示法
4. 广义表表示法
(A(B(E(J,K,L),F),C(G),D(H(M),I)))
树的性质
(1)树中结点数等于所有结点度数加1
(2)度为m的树中第i层上至多有m的i-1次幂个结点
(3)高度为h的m叉树至多有 个结点
(4)具有n个结点的m叉树最小高度为
二、二叉树的基本概念
二叉树的定义及主要特性
- 二叉树的定义:二叉树是n(n>=0)个结点的有限集,他或者是空集(n=0),或者是由一个根节点及两个互不相交的左子树和右子树组成(不存在度大于2的结点,左右顺序不能颠倒)
- 特点:每个结点最多有两个孩子,或者说,再二叉树中不存在大于2的结点,并且二叉树是有序树(树为无序树),其子树的顺序不能颠倒
- 二叉树与度为2的树的区别:度为2的树至少有3个结点,而二叉树可以为空;二叉树有左右子树之分,不能颠倒
几种特殊的二叉树
-
满二叉树:深度为k具有2k-1个结点的二叉树,称为满二叉树。所有层都含最多个结点;对于满二叉树,对于编号为i的结点,若有双亲,则其双亲为i/2 ,若有左孩子为2i ,右孩子为 2i+1
-
完全二叉树:一个具有i个结点的深度为k的二叉树,他的每一个结点都与深度为k的满二叉树一 一对应,则称则棵二叉树为完全二叉树
-
若i<=n/2(向下取整)则结点i为分支结点,否则为叶子结点
-
叶子结点只可能在层次最大的两层上出现
-
如果有度为1的结点,则只能有1个,且该结点只有左孩子没有右孩子
-
按层序编号后,一旦出现某结点(编号为i)为叶子结点或只有左孩子,则编号大于i的结点均为叶子结点
-
若n为奇数,则每个结点都有左孩子和右孩子;若为偶数,则编号最大的只有左孩子,无右孩子
-
二叉排序树:左子树上所有结点关键字小于根结点,右子树上所有关键字大于根结点,左子树和右子树又是一个二叉排序树
-
平衡二叉树:树上任意结点的左子树和右子树深度之差不超过1
-
二叉树的性质
(1)非空二叉树叶子结点数等于度为2的结点数+1,即N0=N2+1
(2)非空二叉树第k层上至多有2的k-1次幂个结点
(3)高度为H的二叉树至多有2的h次幂-1个结点
(4)当i>1时,结点i的双亲编号为 ,即当n为偶数,双亲编号为i/2,它是双亲的左孩子;i为奇数,双亲编号为(i-1)/2,它是双亲结点的右孩子
(5)2i<=N,结点的左孩子编号为2i,否则无左孩子
(6)2i+1<=N,结点i的右孩子编号为2i+1,否则无右孩子
(7)结点i所在层次为 log以2为底i的对数向下取整+1
(8)具有N个结点的完全二叉树高度为log以2为底N的对数向下取整+1
(9)深度为k的完全二叉树中最少有2的k-1次幂个结点,最多有2的k次幂-1个结点二叉树的存储结构
- 顺序存储结构:按编号顺序存放,完全二叉树和满二叉树比较合适;要从数组下标为1开始存储
三、二叉树的遍历
- 定义: 二叉树的遍历是指按一定规律访问二叉树的每个结点,且每个结点只被访问一次的过程
- 二叉树的遍历的方法:
- 中序遍历动态图
-
先序遍历
方法:根左右 先序找根
-
后序遍历
方法:左右根
例题:
遍历顺序
例题:
例题
中序:CBEDAGHFJI
后序:CEDBHGJIFA
中序:CDBFEAIHGJ
后序:DCFEBIHJGA
先序:ABCDEFGHIJ
- 先序遍历递归算法
void PreOrder(BiTree T){
if(T!=NULL){
visit(T->data); //如果输出则改成printf(“%d”,n);
preorder(T->lchild);
preorder(T->rchild);
}
}
- 中序遍历递归算法
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
visit(T->data); //输出时,改成printf(“%d”,n);
InOrder(T->rchild);
}
}
- 后序遍历递归算法
void PreOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T->data); //输出时,改成printf(“%d”,n);
}
}
时间与空间复杂度都是O(n)
度为0的条件语句
T->lchild==NULL && T->rchild==NULL
度为1的条件语句
(T->lchild!=NULL && T->rchild==NULL)||(T->lchild==NULL && T->rchild !=NULL)
度为2的条件语句
T->lchild!=NULL&&T->rchild!=NULL
- 中序遍历非递归算法
void intravel (struct node *p)
{ q=p;top=0;bool=1;
while(bool)
{ while(q!=NULL)
{ top++;
s[top]=q;
q=q->lchild;
}
if(top==0) bool=0;
else{ q=s[top];
top--;
printf(“%c”,q->data);
q=q->rchild;
}
}
}
- 先序遍历非递归算法
二叉树先序遍历的非递归算法:
void intravel (struct node *p)
{q=p;top=0;bool=1;
while(bool)
{while(q!=NULL)
{printf(“%c”,q->data);
top++;
s[top]=q;
q=q->lchild;
}
if(top==0) bool=0;
else{
q=s[top];
top--;
q=q->rchild;}
}
}
- 后序遍历非递归算法
void lasttravel(struct node *p)
{q=p; top=0;bool=1;
while(bool)
{while(q!=NULL){ top++;
s[top]=q;
s2[top]=1;
q=q->lchild;}
if(top==0) bool=0;
else{if(s2[top]==1) {s2[top]=2; //第二次经过,不退栈
q=s[top];
q=q->rchild;}
else {q=s[top]; //第三次经过,退栈并且访问根节点
s2[top]=0;
top--;
printf(“%c”,q->data);
q=NULL; }
}
}
}
- 按层遍历二叉树
void leveltravel(struct node *p)
{struct node *q[20];
front =rear=0;
if(p!=NULL) {
rear++;
q[rear]=p;
}
while(front!=rear)
{
front++;
p=q[front];
printf(“%c”,p >data);
if(p->lchild!=NULL) {
rear++;
q[rear]=p->lchild;
}
if(p->rchild!=NULL) {
rear++;
q[rear]=p->rchild;
}
}
}
- 输出二叉树的结点
void PreOrder(BITree T)
{
if (T!=NULL)
{
printf(T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
- 输出二叉树的叶子结点
void PreOrder(BITree T)
{
if (T!=NULL)
{if(T->lchild==NULL&&T->rchild==NULL)
printf(T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
- 输出二叉树中叶子结点的个数
int n=0;
void leafcount(BITree T)
{
if (T!=NULL)
{if(T->lchild==NULL&&T->rchild==NULL)
n++;
leafcount (T->lchild);
leafcount (T->rchild);
}
}
- 求出二叉树的高度
int PostTreeDepth(BITree T)
{
if (T!=NULL)
{
hl=PostTreeDepth (T->lchild);
hr=PostTreeDepth (T->rchild);
max=hl>hr ? hl:hr;
return(max+1);
}
else return 0;
}
- 非递归建立二叉树算法
struct node *creat()
{printf(“i,x=”); scanf(“%d,%d”,&i,&x);
while(i!=0&&x!=0)
q=(struct node *)malloc(sizeof(struct node));
q->data=x;
q->lch=NULL;
q->rch=NULL;
s[i]=q;
if(i==1) t=q;
else{ j=i/2;
if(i%2==0)s[j]->lch=q;
else s[j]->rch=q;}
scanf(“%d%d”,&i,&x); }
}
四、线索二叉树
1.相关概念:
在一个n结点的链式存储二叉树中,有n+1个指针域是空指针域,可以把每个空指针域用于存放分别指向某种遍历次序的前趋和后继结点的指针。
线索:在结点的空指针域中存放的该结点在某遍历次序下的前趋结点和后继结点的指针叫做线索。
线索二叉树:对一个二叉树中的所有结点的空指针域按照某种遍历次序加线索的过程叫作线索化,被线索化了的二叉树称作线索二叉树。
在一个线索二叉树中,必须设法将线索与指向结点左、右儿子结点的指针加以区别。
可给每个结点增加两个标志域,即左线索标志域LTag,右线索标志域RTag。
2.基本概念:利用二叉树中大量空链域(N个结点中有N+1个空指针)存放直接前驱或后继的指针;线索化时规定,若无左子树,令lchild指向其前驱结点,若无右子树,rchild指向其后继结点
- 线索二叉树的构造:遍历一次二叉树,遍历过程中,检查当前结点左右指针域是否为空,若为空,将他们改为指向前驱或后继的线索(标识tag有孩子则为0,无则为1)
注:有时为方便,在二叉树线索链表上添加一个头结点,并令其lchild域指针指向二叉树根结点,其rchild指向中序遍历的最后一个结点;反之,令二叉树第一个结点的lchild和最后一个结点的rchild指向头结点,变成一个双向循环链表
注:先序序列为n个顺序的树,不同二叉树个数为
五、二叉链表和三叉链表
1. 二叉链表
通常每个结点中设置三个域,即值域、左指针域和右指针域,其结点结构如下:
其中data表示值域,用于存储放入结点的数据,lchild和rchild分别表示左指针域和右指针域,用以分别存储指向左儿子结点和右儿子结点的指针
先序创建二叉链表
Status CreateBiTree(BITree &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;
}
2. 三叉链表
通常每个结点中设置四个域,即值域、左指针域、右指针域和双亲指针域,其结点结构如下:

其中data表示值域,用于存储放入结点的数据,lchild和rchild分别表示左指针域和右指针域,用以分别存储指向左儿子结点和右儿子结点的指针,parent指向双亲结点
3. 链式存储结构
1) 二叉树链式存储结构表示
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild ,*rchild; //左、右孩子指针
} BiTNode,*BiTree;
在含有n个结点的二叉链表中含有n+1个空指针域
六、树和森林
-
双亲表示法:这种方法用一组连续的空间来存储树中的结点,再保存每个结点的同时,附设一个指示其双亲结点在表中的位置,其双亲的结构如下:
-
孩子表示法:这种方法通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n个结点共有n个孩子链表(叶结点的孩子链表为空表),而n个结点的数据和n个孩子链表的头指针又组成一个顺序表.
-
孩子兄弟表示法
树、森林和二叉树的转化
- 树转化为二叉树规则:每个结点左指针指向它第一个孩子结点,右指针指向它在树中的相邻兄弟结点,表示为“左孩子右兄弟”,由于根没有右兄弟,所以树转化为二叉树没有右子树
- 树转化为二叉树方法:
- 森林转化为二叉树
方法如下:
实例:
4. 二叉树转化为森林:二叉树根及其左子树为第一棵树的二叉树形式,二叉树根的右子树又可以看做是一个由除第一棵树外的森林转化后的二叉树,应用同样的方法,直到最后产生一棵没有右子树的二叉树为止,就得到了原森林
注:
(1)树转化为二叉树:在兄弟结点间加一条线;对每一个结点,只保留它与第一个子结点的连线,与其他子结点的连线全部抹掉;以树轴为轴心,顺时针旋转45°
(2)森林转化为二叉树:将每棵树根相连,将森林中每棵树转化为相应的二叉树;以第一棵树根轴心顺时针旋转45°
树和森林的遍历
1.树的遍历
(1)先根遍历:与相应的二叉树的先序遍历相同
(2)后根遍历:与相应的二叉树的中序遍历相同
(3)层次遍历:与二叉树一样
2.森林的遍历
(1)先序遍历森林:访问森林每一棵树根结点;先序遍历第一棵树中根结点子树森林;先序遍历除去第一棵树之后剩余的树构成的森林
(2)中序遍历森林:中序遍历森林中第一棵树的根结点的子树森林;访问第一棵树根结点;中序遍历除去第一棵树之后剩余的树构成的森林
七、赫夫曼树和赫夫曼编码
- 基本术语
路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径
路径长度:路径上的分支数称为这两点之间的路径长度
树的路径长度:树的路径长度是从根到每一个结点的路径长度之和一般记作pl
在结点树木相同的二叉树中,完全二叉树或满二叉树的路径长度最短
结点的权:在许多实际应用中,常常将树中结点赋予一个有某种意义的实数,称为该结点的权。
结点的带权路径长度:从树的根结点到该结点之间的路径长度与该结点上权的乘积称为该结点的带权路径长度。
树的带权路径长度定义为树中所有叶子结点的带权路径长度之和,通常记为:
其中n表示叶子结点个数,wi和li分别表示叶子结点ki的权值和根到ki之间的路径长度。
给定一组n个实数,以它们作为各个叶子结点的权,可构成不同的有n个叶子结点的二叉树,这些二叉树的带权路径长度wpl可能不同。
3.哈夫曼树的定义:树中结点被赋予值(权),从树根结点到任意结点的路径长度(经过的边数)与该点上权值的乘积称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度,记为 式中,wi是第i个叶结点所带的权值;li是该叶结点到根结点的路径长度。在含有N个带权叶子结点的二又树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称为最优二叉树。
4.哈夫曼树又称最优二叉树,它是这样定义的:设有n个权值{w1,w2,…,wp},在这些权值为各个叶子结点的权构成的二叉树中,带权路径长度wpl,最小的二叉树叫做哈夫曼树
例如,下图甲的3棵二叉树,都有4个叶子结点a、b、c、d,分别带权7、5、2、4、它们的带权路径长度分别为
5. 哈夫曼树的构造:
构造过程:
6. 哈夫曼树特点:
例如,权值{7},5,2,4}的哈夫受树的构造过程如图所示
- 哈夫曼编码:规定左子树为0,右子树为1,从上到下连接到一起,所组成的码为哈夫曼编码。
总结
- 树的定义和基本术语
- 二叉树的基本概念
- 二叉树的遍历
- 线索二叉树
- 二叉链表和三叉链表
- 树和森林
- 赫夫曼树和哈夫曼编码