(1)树的定义
树(Tree)n(n>=0)个有限数据元素的集合。当n=0时称为空树。当n>0时,是非空树,它满足以下两个条件:
- 有且仅有一个特定的称为根(Root)的结点;
- 其余结点可分为m (m≥0)个互不相交的有限集T1,T2,T3,…Tm,其中每一个集合本身又是一棵树,并称为根的子树(SubTree)。
(2)树的表示方法
树的逻辑结构表示主要有四种:树形表示法,嵌套集合表示法,凹入表示法和广义表表示法等
线性结构 | 树结构 |
---|---|
第一个数据元素无前驱 | 根结点(只有一个)无双亲 |
最后一个数据元素无后继 | 叶子结点(可以有多个)无孩子 |
其它数据元素 | 其它结点—中间结点 |
一个前驱,一个后继 | 一个双亲,多个孩子 |
一对一 | 一对多 |
(1)二叉树的定义
二叉树是n(n≥O)个结点的有限集,它或者是空集(n = 0),或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成。
特点:
*注意:*二叉树不是树的特殊情况,它们是两个概率
案例︰利用二叉树求解表达式的值以二叉树表示表达式的递归定义如下:
(1)性质1:在二叉树的第i层上至多有2^(i-1)个结点(i≥1)。
第1层:2^0=1
第2层: 2^1=2
第3层:2^2=4
第4层:2^3=8
证:采用归纳法证明此性质。
归纳基:当i=1时,只有一个根结点,2^(i-1)=2*0 =1,命题成立。归纳假设:设对所有的j(1≤j 即第j层上至多7有2^(j-1)个结点。那么可以证明j=i时命题也成立。
归纳证明:由归纳假设可知,第i-1层上至多有2^(i-2)个结点。
由于二叉树每个结点的度最大为2,故在第i层上最大结点数为第i-1层上最大结点数的2倍,即:2x2^(i-2)=2的(i-1)次方 证毕。
(2)性质2:深度为k的二叉树至多有2^k-1个结点(k≥1)。
证:由性质1可知,深度为k的二叉树的最大结点数为:
(3)性质3:对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n,则n0 = n2+ 1。
总边数为B B = n-1=B=n2×2+n1×1
总结点数为n n=n2×2+ n1x1+1又n = n2+ n 1+n0
(4)满二叉树
一棵深度为k且有2^k-1个结点的二叉树称为满二叉树。
特点:
例1:思考:下图中的二叉树是满二叉树吗?
答:不是满二叉树,叶子不在同一层上。且最后一层结点个数不满!
满二叉树在同样深度的二叉树中结点个数最多
满二叉树在同样深度的二叉树中叶子结点个数最多
(5)完全二叉树
深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点—一对应时,称之为完全二叉树。
注:在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树.一定是连续的去掉!!!
答:不是,I与J两结点不连续。
特点:
(7)性质5:如果对一颗有n个结点的完全二叉树的结点按层次有:
(1)顺序存储结构
顺序存储一颗二叉树,先对该二叉树中的各结点进行编号,然后以各结点的编号为下标,把各结点的值存到一维数组中
例3:二叉树结点数值采用顺序存储结构,如图所示。画出二叉树表示:
(2)链式存储结构
typedef struct BiNode{
TElemType data;
struct BiNode *Ichild,*rchild;//左右孩子指针
}BiNode,*BiTree;
typedef struct TriTNode{
TelemType data;
struct TriTNode *lchild,*parent,*rchild;
}TriTNode,*TriTree;
(1)遍历二叉树
//先序遍历的算法实现(DLR)
void PreOrder(BT *T){
if(T==NULL) return;//递归调用结束条件
else
{
printf("%c",T->data);//输出根结点
PreOrder(T->lchild);//先序遍历左子树
PreOrder(T->rchild);//先序遍历右子树
}
}
//中序遍历的算法实现(LDR)
void InOrder(Bt *T){
if(T==NULL) return;//递归调用结束条件
else{
InOrder(T->lchild);//中序遍历左子树
printf("%c",T->data);//输出根结点
InOrder(T->rchild);//中序遍历右子树
}
}
//后序遍历的算法实现(LRD)
void PostOrder(BT *T){
if(T==NULL) return;//递归调用结束条件
else{
PostOrder(T->lchild);//后序遍历左子树
PostOrder(T->rchild);//后序遍历右子树
printf("%c",T->data);//输出根结点
}
}
//层次遍历的算法实现
void LevelOrder(BT *T){
int f ,r;//定义队头指针
BT *p,*q[MAX];//定义循环队列,存放结点指针
p=T;
if(p!=NULL){//若二叉树非空,则根结点地址入队
f=l;q[f]=p;r=2;
}
while(f!=r){//队列不为空
p=q[f];
printf("%c",p->data);//访问队首结点
if(p->lchild!=NULL){
q[r]=p->lchild;r=(r+1)%MAX;//将队首结点的左孩子入队
}
if(p-<rchlid!=NULL){
q[r]=p->rchlid;r=(r+1)%MAX;//将队首结点的右孩子入队
}
f=(f+1)%MAX;
}
}
解:先序:ABDGCEHF;中序:DGBAEHCF;后序:GDBHEFCA
例6:用二叉树表示算术表达式,请写出下图所示二叉树的先序、中序和后序遍历顺序
解:先序:-+axb-cd/ef;中序:a+bxc-d-e/f;后序:abcd-x+ef/-
(2)恢复二叉树
例7:已知先序和中序序列求二叉树
(1)建立二叉树
BT *CreateBTree(){
BT *t;
char ch;
scanf("%c",&ch);
getchar();
if(ch=='0'){
t=NULL;
}
else{
t=(Bt*)malloc(sizeof(BT));
t->data=ch;
printf("请输入%c结点的左孩子结点:",t->data);
t->lchild=CreateBtree();
printf("请输入%c结点的右孩子结点:",t->data);
t->rchlid= CreateBtree();
}
return t;
}
(2)用广义表法输出二叉树
//用广义表法输出二叉树
void ShowBTree(BT *T){
if(T!=NULL){//当二叉树非空时
printf("%c",T->data);//输出该结点数据域
if(T->lchlid!=NULL){//若其左子树非空
printf("(");//输出左括号
ShowBTree(T->lchild);//递归调用输出其左子树各个结点
if(T->rchlid!=NULL){//若其右子树非空
printf(",");//输出“,”
ShowBTree(T->rchild);//递归调用输出其右子树各个结点
}
printf(")");
}
else
if(T->rchild!=NULL){//若其左子树为空,右子树不为空
printf("(");//输出括号
ShowBTree(T->lchild);//递归调用输出其左子树各个结点
if(T->rchlid!=NULL){//若其右子树不为空
printf(",");//输出“,”
ShowBTree(T->rchild);//递归调用输出右子树各个结点
}
printf(")");
}
}
}
(3)统计二叉树叶子结点数目
//统计二叉树叶子结点数目
void Leafnum(BT *T){
if(T){//若树不为空
if(T->lchild==NULL&&T->rchild==NULL)
coun++;
Leafnum(T->lchild);//递归统计T的左子树叶子结点
Leafnum(T->rchild);//递归统计T的右子树叶子结点
}
}
(4)求二叉树结点总数
//求二叉树结点总数
void Leafnum(BT *T){
if(T){//若树不为空
coun++;
Leafnum(T->lchild);//递归统计T的左子树叶子结点
Leafnum(T->rchild);//递归统计T的右子树叶子结点
}
}
(5)求二叉树的深度
//求二叉树的深度
int TreeDepth(BT *T){
int ldep,rdep;
if(T==NULL){
return 0;
else{
ldep=TreeDepth(T->lchild);//递归统计T的左子树深度
rdep=TreeDepth(t->rchild);//递归统计T的右子树深度
if(ldep>rdep)
return ldep+1;
else
return rdep+1;
}
}
}
(6)复制二叉树
//复制二叉树
int Copy(BT *T,BT &NewT){
if(T==NULL){
NewT=new BiTNode; NewT->data=T->data;
Copy(T->lchild,NewT->lchild);//递归复制左子树
Copy(T->rchild,NewT->rchild);//递归复制右子树
}
}
利用二叉链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继
——这种改变指向的指针称为“线索”
加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
为区分Irchid和rchild指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域 Itag和rtag,并约定:
为区分Irchid和rchild指针到底是指向孩子的指针,还是指向削驱现台后继的指针,对二叉链表中每个结点增设两个标志域 Itag和rtag,并约定:
ltag = 0 Ichild 指向该结点的左孩子
ltag = 1 Ichild 指向该结点的前驱
rtag = 0 rchild 指向该结点的右孩子
rtag = 1 rchild 指向该结点的后继
这样,结点的结构为:
typedef struct BiThrNode{
int data;
int Itag, rtag;
struct BiThrNode *lchild,rchild;
}BiThrNode,*BiThrTree ;
例8、画出以下二叉树对应的中序线索二叉树。
该二叉树中序遍历结果为: H,D, I,B,E, A, F,C, G
增设了一个头结点:
ltag=O, Ichild指向根结点,
rtag=1, rchild指向遍历序列中最后一个结点
遍历序列中第一个结点的lc域和最后一个结点的rc域都指向头结点
①加线:在兄弟之间加一连线
②抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
③旋转:以树的根结点为轴心,将整树顺时针转45°
树变二叉树:兄弟相连留长子
例9:将树转换成二叉树
①加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子…沿分支找到的所有右孩子,都与p的双亲用线连起来
②抹线:抹掉原二叉树中双亲与右孩子之间的连线
③调整:将结点按层次排列,形成树结构
二叉树变树:左孩右右连双亲,去掉原来右孩线。
例10:将二叉树转换成树
哈夫曼树(Huffman Tree)是一种特殊的二叉树,这种树的所有的叶子结点都有权值,从而构造出带路径长度最短的二叉树,即哈夫曼树,又称最优树。
(1)路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
(2)结点的路径长度:两结点间路径上的分支数。
(a)从A到 B,C,D, E, F, G,H,l的路径长度分别为1,1,2,2,3,3,4,4。
(b)从A到B,C,D, E, F, G,H,I的路径长度分别为1,1,2,2,2,2,3,3。
(3)树的路径长度:从树根到每一个结点的路径长度之和。记作:TL
TL (a)=0+1+1+2+2+3+3+4+4=20
TL (b)=0+1+1+2+2+2+2++3+3=16
结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树。
(4)权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
(5)结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。树的带权路径长度:树中所有叶子结点的带权路径长度之和。记作:
例11:有4个结点a, b, c d,权值分别为7,5.2,4,构造以此4个结点为叶子结点的二叉树:
带权路径长度是:
(a)WPL=7x2+5x2+2x2+4x2= 36
(b)WPL=7x3+5x3+2x1+4×2= 46
(1)哈夫曼算法(构造哈夫曼树的方法)
void CreatHuffmanTree (HuffmanTree HT, int n){//构造哈夫曼树——哈夫曼算法 if(n<=1)return;
HT=new HTNode[mt1];//Q号单元未用,HT[m]表示根结点
for(i=1;i<=m;++i){//将2n-1个元素的Ich、rch、parent置为0
HT[i].lch=O; HT[i].rch=O; HT[i].parent=O;
}
for(i=1;j<=n;++i) cin>>HT[i].weight;//输入前n个元素的weight值//初始化结束,下面开始建立哈夫曼树
for( i=n+1;i<=m; i++){//合并产生n-1个结点——构造Huffman树
Select(HT, i-1, s1, s2);//在HT[k](1≤k≤i-1)中选择两个其双亲域为0,
//且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[s1].parent=i;HT[s2] .parent=i;//表示从F中删除s1,s2
HT[i].Ich=s1; HT[li].rch=s2 ;//s1,s2分别作为i的左右孩子
HT[i].weight=HT[s1].weight +HT[s2] .weight;//i的权值为左右孩子权值之和
}
}
在远程通讯中,要将待传字符转换成由二进制的字符串:
设要传送的字符为:
若将编码设计为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。
问题:什么样的前缀码能使得电文总长最短?
——哈夫曼编码
方法: