Click here to load reader
Upload
jaser
View
124
Download
10
Embed Size (px)
DESCRIPTION
第六章 树和二叉树. 树是一类重要的非线性数据结构,是以分支关系定义的层次结构 6.1 树的定义 定义 定义:树 (tree) 是 n(n>0) 个结点的有限集 T , 其中: 有且仅有一个特定的结点,称为树的 根 (root) 当 n>1 时,其余结点可分为 m(m>0) 个 互不相交 的有限集 T 1 ,T 2 ,……T m , 其中每一个集合本身又是一棵树,称为根的 子树 ( subtree) 特点: 树中至少有一个结点 —— 根 树中各子树是互不相交的集合. 只有根结点的树. A. A. 有子树的树. B. C. D. E. F. G. - PowerPoint PPT Presentation
Citation preview
第六章 树和二叉树 树是一类重要的非线性数据结构,是以分支关系定义的层次结构
§ 6.1 树的定义定义
定义:树 (tree) 是 n(n>0) 个结点的有限集 T ,其中:有且仅有一个特定的结点,称为树的根 (root)当 n>1 时,其余结点可分为 m(m>0) 个互不相交的有限集 T
1,T2,……Tm,其中每一个集合本身又是一棵树,称为根的子树 (subtree)
特点:树中至少有一个结点——根树中各子树是互不相交的集合
A
只有根结点的树
A
B C D
E F G H I J
K L M
有子树的树 根
子树
基本术语结点 (node)—— 表示树中的元素,包括数据项及若干指向其子树的分支
结点的度 (degree)—— 结点拥有的子树数叶子 (leaf)—— 度为 0 的结点孩子 (child)—— 结点子树的根称为该结点的孩子双亲 (parents)—— 孩子结点的上层结点叫该结点的 ~兄弟 (sibling)—— 同一双亲的孩子树的度——一棵树中最大的结点度数结点的层次 (level)—— 从根结点算起,根为第一层,它的孩子为第二层……
深度 (depth)—— 树中结点的最大层次数森林 (forest)——m(m0) 棵互不相交的树的集合
A
B C D
E F G H I J
K L M
结点 A 的度: 3结点 B 的度: 2结点 M 的度: 0
叶子: K , L , F , G , M , I , J
结点 A 的孩子: B , C , D结点 B 的孩子: E , F
结点 I 的双亲: D结点 L 的双亲: E
结点 B , C , D 为兄弟结点 K , L 为兄弟树的度: 3
结点 A 的层次: 1结点 M 的层次: 4
树的深度: 4
结点 F , G 为堂兄弟结点 A 是结点 F , G 的祖先
树的表示树的表示 树型表示树型表示
b
a
c
g hd e f
i j
凹入表表示凹入表表示
ab
deij
fc
g
h
嵌套集合表示嵌套集合表示
嵌套括号表示嵌套括号表示
i jd f g h
ab ce
(a(b(d,e(i,j),f),c(g,h)))
§ 6.2 二叉树定义
定义:二叉树是 n(n0) 个结点的有限集,它或为空树 (n=0) ,或由一个根结点和两棵分别称为左子树和右子树的互不相交的二叉树构成
特点每个结点至多有二棵子树 ( 即不存在度大于 2 的结点 )二叉树的子树有左、右之分,且其次序不能任意颠倒
基本形态
A
只有根结点的二叉树
空二叉树
A
B
右子树为空
A
B
左子树为空
A
B C
左、右子树均非空
二叉树性质性质 1 : )1(2 1 ii i 个结点层上至多有在二叉树的第
证明:用归纳法证明之 i=1 时,只有一个根结点, 是对的 假设对所有 j(1j<i) 命题成立,即第 j 层上至多有 个结点 那么,第 i-1 层至多有 个结点 又二叉树每个结点的度至多为 2 第 i 层上最大结点数是第 i-1 层的 2 倍,即 故命题得证
122 01 i
12 j
22 i
12 222 ii
性质 2 :深度为 k 的二叉树至多有 个结点(k1)
12 k
证明:由性质 1 ,可得深度为 k 的二叉树最大结点数是
122)(1 1
1
kk
i
k
i
ii层的最大结点数第
性质 3 :对任何一棵二叉树 T ,如果其终端结点数为 n0,度为 2 的结点数为 n2,则 n0=n2+1
证明: n1 为二叉树 T 中度为 1 的结点数 因为:二叉树中所有结点的度均小于或等于 2 所以:其结点总数 n=n0+n1+n2
又二叉树中,除根结点外,其余结点都只有一个 分支进入 设 B 为分支总数,则 n=B+1 又:分支由度为 1 和度为 2 的结点射出, B=n1+2n2
于是, n=B+1=n1+2n2+1=n0+n1+n2
n0=n2+1
几种特殊形式的二叉树满二叉树
定义: ~12 个结点的二叉树称为且有一棵深度为 kk
特点:每一层上的结点数都是最大结点数完全二叉树
定义:深度为 k ,有 n 个结点的二叉树当且仅当其每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应时,称为 ~
特点叶子结点只可能在层次最大的两层上出现对任一结点,若其右分支下子孙的最大层次为 L ,则其左分支下子孙的最大层次必为 L 或 L+1
性质性质 4 : 1log2 nn 深度为个结点的完全二叉树的具有
1
2 3
11
4 5
8 9 12 13
6 7
10 14 15
1
2 3
11
4 5
8 9 12
6 7
10
1
2 3
4 5
6 7
1
2 3
4 5 6
性质 5 :如果对一棵有 n 个结点的完全二叉树的结点按层序编号,则对任一结点 i(1in) ,有:
(1) 如果 i=1 ,则结点 i 是二叉树的根,无双亲;如果 i>1 ,则其双亲是 i/2
(2) 如果 2i>n ,则结点 i 无左孩子;如果 2in ,则其左孩子是 2i
(3) 如果 2i+1>n ,则结点 i 无右孩子;如果 2i+1n ,则其右孩子是 2i+1
§ 6.3 树的存储结构树的存储结构
双亲表示法实现:定义结构数组存放树的结点,每个结点含两个域:
数据域:存放结点本身信息双亲域:指示本结点的双亲结点在数组中位置
特点:找双亲容易,找孩子难
// 数的双亲表存储表示#define MAX_TREE_SIZE 100typedef struct PTnode{ TElemType data; int parent;}PTNode;typedef struct{PTNode nodes[MAX_TREE_SIZE];int r,n;// 根的位置和结点数 }PTree;
a
b c
d e f
hg i
a
c
d
e
f
g
h
i
b 0
1
1
2
4
4
4
0
6
0
1
2
3
4
5
7
8
data parent
如何找孩子结点
-1
孩子表示法多重链表:每个结点有多个指针域,分别指向其子树的根
结点同构:结点的指针个数相等,为树的度 D结点不同构:结点指针个数不等,为该结点的度 d
data child1 child2 ………. childD
data degree child1 child2 ………. childd
孩子链表:每个结点的孩子结点用单链表存储,再用含 n 个元素的结构数组指向每个孩子链表
孩子结点: typedef struct node { int child; // 该结点在表头数组中下标 struct node *next; // 指向下一孩子结点 }JD;表头结点: typedef struct tnode { datatype data; // 数据域 struct node *fc; // 指向第一个孩子结点 }TD; TD t[M]; //t[0] 不用
a
b c
d e f
hg i
6
0
1
2
3
4
5
7
8
9
a
c
d
e
f
g
h
i
b
data fc
2 3 ^
4 5 ^
^
9 7 8 ^
6 ^
^
^
^
^如何找双亲结点
带双亲的孩子链表
6
1
2
3
4
5
7
8
9
a
c
d
e
f
g
h
i
b
data fc 2 3
4 5
9 7 8
6
^
^
^
^
^
^
^
^
^
0
1
2
2
3
5
5
5
1
parenta
b c
d e f
hg i
孩子兄弟表示法(二叉树表示法)实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点
特点操作容易破坏了树的层次
typedef struct node{ datatype data; struct node *fch, *nsib;}JD;
a
b c
d e f
hg i
a
b c
d e f
g h i
^
^
^
^ ^ ^
^ ^ ^ ^
二叉树的存储结构顺序存储结构
实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素
特点:结点间关系蕴含在其存储位置中浪费空间,适于存满二叉树和完全二叉树
a
b c
d e
f g
a b c d e 0 0 0 0 f g 1 2 3 4 5 6 7 8 9 10 11
链式存储结构二叉链表
typedef struct node{ datatype data; struct node *lchild, *rchild;}JD;
lchild data rchild
A
B
C D
E F
G
在 n 个结点的二叉链表中,有 n+1 个空指针域
A
B
C D
E F
G
^
^ ^
^ ^ ^
^ ^
空指针个数: 2*n0+1*n1+0*n2
=2n0+n1
=n0+n1+n0
=n0+n1+n2+1
=n+1
三叉链表
typedef struct node{ datatype data; struct node *lchild, *rchild, *parent;}JD;
lchild data parent rchild
A
B
C D
E F
G
A
B
C D
E F
G
^ ^
^ ^
^ ^ ^
^ ^
树与二叉树转换
A
CB E
D
树A
B
C
D E
二叉树
A ^
^ B
C
^ D ^
^ E ^
A ^
^ B C
^ D ^
^ E ^
A ^
^ B
C
^ D ^ ^ E ^
对应
存储存储
解释解释
将树转换成二叉树加线:在兄弟之间加一连线抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
旋转:以树的根结点为轴心,将整树顺时针转 45°
A
B C D
E F G H I
A
B C D
E F G H I
A
B C D
E F G H I
A
B C D
E F G H I
A
B
C
D
E
F
G H
I树转换成的二叉树其右子树一定为空
将二叉树转换成树加线:若 p 结点是双亲结点的左孩子,则将 p 的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与 p 的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线调整:将结点按层次排列,形成树结构
A
B
C
D
E
F
G H
I
A
B
C
D
E
F
G H
I
A
B
C
D
E
F
G H
I
A
B
C
D
E
F
G H
IA
B C D
E F G H I
森林转换成二叉树将各棵树分别转换成二叉树将每棵树的根结点用线相连以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
A
B C D
E
F
G
H I
J
A
B
C
D
E
F
G
H
I
J
A
B
C
D
E
F
G
H
I
J
A
B
C
D
E
FG
H
I
J
二叉树转换成森林抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:将孤立的二叉树还原成树
A
B
C
D
E
FG
H
I
J
A
B
C
D
E
FG
H
I
J
A
B
C
D
E
F
G
H
I
J
A
B C D
E
F
G
H I
J
§ 6.4 树和二叉树的遍历树的遍历
遍历——按一定规律走遍树的各个顶点,且使每一顶点仅被访问一次,即找一个完整而有规律的走法,以得到树中所有结点的一个线性排列
常用方法先根(序)遍历:先访问树的根结点,然后依次先根遍历根的每棵子树
后根(序)遍历:先依次后根遍历每棵子树,然后访问根结点按层次遍历:先访问第一层上的结点,然后依次遍历第二层,……第 n 层的结点
A
B C D
E F G H
I J K L M
N O
先序遍历:
后序遍历:
层次遍历:
AB E F I GC D H J KL N OM
E I F G B C J K N O L M H D A
A B C D E F G H I J K L MN O
二叉树的遍历方法
先序遍历:先访问根结点 , 然后分别先序遍历左子树、右子树
中序遍历:先中序遍历左子树,然后访问根结点,最后中序遍历右子树
后序遍历:先后序遍历左、右子树,然后访问根结点按层次遍历:从上到下、从左到右访问各结点
D
L R
LDR 、 LRD 、 DLRRDL 、 RLD 、 DRL
A
D
B C
D L R
AD L R
D L R
>B
>>D
>>C
D L R
先序遍历序列: A B D C
先序遍历 :
A
D
B C
L D R
B
L D R
L D R>
A
>> D
>> C
L D R
中序遍历序列: B D A C
中序遍历 :
A
D
B C
L R D
L R D
L R D>
A
>> D
>> C
L R D
后序遍历序列: D B C A
后序遍历 :
B
-
+ /
a *
b -
e f
c d
先序遍历:中序遍历:
后序遍历:
层次遍历:
- + a * b - c d / e f
-+a *b -c d /e f
- + a * b - c d/ e f
-+a *b -c d /e f
// 先序遍历二叉树void preorder(JD *bt){ if(bt!=NULL) { printf("%d\t",bt->data); preorder(bt->lchild); preorder(bt->rchild); }}
主程序
Pre( T )
返回
返回
pre(T R);
返回
返回
pre(T R);
A
CB
D
T B
printf(B);
pre(T L);
B
T A
printf(A);
pre(T L);
A
T D
printf(D);
pre(T L);
D
T C
printf(C);
pre(T L);
C
返回T >
左是空返回
pre(T R);
T >
左是空返回
T >
右是空返回
T >
左是空返回
T >
右是空返回
pre(T R);
先序序列: A B D C
非递归算法
A
B
C D
E F
G
p
i
P->A
(1)
A
B
C D
E F
G
p
i
P->A
P->B
(2)
A
B
C D
E F
G
p i
P->A
P->B
P->C
(3)
p=NULL
A
B
C D
E F
G
i
P->A
P->B
访问: C(4)
p A
B
C D
E F
G
i
P->A
访问: C B(5)
A
B
C D
E F
G
i
P->A
P->D
访问: C B
p
(6)
A
B
C D
E F
G
i
P->A
P->D
P->E
访问: C B
p
(7)
A
B
C D
E F
G
i
P->A
P->D
访问: C B Ep
(8)
A
B
C D
E F
G
i
P->A
P->D
P->G
访问: C B E
P=NULL(9)
A
B
C D
E F
G
iP->A
访问: C B E G D
p
(11)
A
B
C D
E F
G
i
P->A
P->F
访问: C B E G D
p
(12)
A
B
C D
E F
G
i
P->A
P->D
访问: C B E G
p
(10)
A
B
C D
E F
G
i
P->A
访问: C B E G D F
p=NULL(13)
A
B
C D
E F
G
i
访问: C B E G D F A
p
(14)
A
B
C D
E F
G
i
访问: C B E G D F A
p=NULL
(15)
Status InOrderTraverse(BiTree T,Status(*Visit)(TElemType e)){// 采用二叉链表存储结构, Visit 是对数据元素操作的应用函数 .
// 中序遍历二叉树 T 的非递归算法,对每个数据元素调用函数 Visit.
InitStack(S); Push(S,T);// 根指针进栈
while(!StackEmpty(S)){
while(GetTop(S,p)&&p) Push(S,p->lchild);// 向左走到尽头
Pop(S,p); // 空指针退栈
if(!StackEmpty(s)){ // 访问结点,向右一步
Pop(S,p); if(!Visit(p->data)) return ERROR;
Push(S,p->rchild);
} } return OK;}
Status InOrderTraverse(BiTree T,Status(*Visit)(TElemType e)){// 采用二叉链表存储结构, Visit 是对数据元素操作的应用函数 .
// 中序遍历二叉树 T 的非递归算法,对每个数据元素调用函数 Visit.
InitStack(S); p=T;
while(p||!StackEmpty(S)){
if(p) {Push(S,p); p=p->lchild);}// 根指针进栈,遍历右子树
else{// 根指针退栈,访问根结点,遍历右子树
Pop(S,p); if(!Visit(p->data)) return ERROR;
p=p->rchild;
} } return OK;}
遍历算法应用按先序遍历序列建立二叉树的二叉链表,已知先序序列为:
A B C D E G F
A
B
C D
E F
G
A ^
B
^ C ^ D
^ E ^ F ^
^ G ^
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;}
线索二叉树定义:
前驱与后继:在二叉树的先序、中序或后序遍历序列中两个相邻的结点互称为 ~
线索:指向前驱或后继结点的指针称为 ~线索二叉树:加上线索的二叉链表表示的二叉树叫 ~线索化:对二叉树按某种遍历次序使其变为线索二叉树的过程叫 ~
实现在有 n 个结点的二叉链表中必定有 n+1 个空链域在线索二叉树的结点中增加两个标志域
LTag : 若 LTag =0, lchild 域指向左孩子;若 LTag =1, lchild 域指向其前驱
RTag : 若 RTag =0, rchild 域指向右孩子;若 RTag =1, rchild 域指向其后继
lchild LTag data RTag rchild
结点定义:typedef enum PointerTag{Link,Thread};
//Link==0; 指针, Thread==1; 线索typedef struct BiThrNode
{ TElemType data;
struct BiThrNode *lchild, *rchild;
PointerTag LTag,RTag;
}BiThrNode,*BiThrTree;
A
B
C
D
E
A
B D
C E
T
先序序列: ABCDE先序线索二叉树
0 0
0 01 1
1 1 ^1 1
A
B
C
D
E
A
B D
C E
T
中序序列: BCAED中序线索二叉树
0 0
0 01 1
1 1
^
1 1
^
A
B
C
D
E
A
B D
C E
T
后序序列: CBEDA后序线索二叉树
0 0
0 01 1
1 11 1^
A
B
C
D
E
0 A 0
1 B 0 0 D 1
1 C 1 1 E 1
T
中序序列: BCAED带头结点的中序线索二叉树
0 1
头结点:LTag=0, lchild 指向根结点RTag=1, rchild 指向遍历序列中最后一个结点
遍历序列中第一个结点的 lchild 域和最后一个结点的 rchild 域都指向头结点
A
B D
C E
T
中序序列: BCAED中序线索二叉树
0 0
0 01 1
1 1
^
1 1
^
算法按中序线索化二叉树
A
B
C
D
E
t
0 1
pr
p
i
P->A
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
A
B D
C E
bt
^^
^ ^ ^ ^
0 0
0 00 0
0 00 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^^
^ ^ ^ ^
t
0 1
pr
p
i
P->A
P->B
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 00 0
0 00 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^^
^ ^ ^ ^
t
0 1
pr
P=NULL
i
P->A
P->B
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 00 0
0 00 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^^
^ ^ ^ ^
t
0 1
pr
P
i
P->A
输出 :B
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 00 0
0 00 0
1
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^ ^ ^ ^
t
0 1
pr
P
输出 :B
i
P->A
P->C
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
0 00 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^ ^ ^ ^
t
0 1
pr
P=NULL
i
P->A
P->C
输出 :B
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
0 00 0
算法按中序线索化二叉树
Ch5_20.c
Ch5_20.txt
A
B
C
D
E
A
B D
C E
bt
^
^ ^ ^ ^
t
0 1
pr
P
i
P->A
输出 : B C
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
0 00 01
算法按中序线索化二叉树
Ch5_20.c
Ch5_20.txt
A
B
C
D
E
A
B D
C E
bt
^
^ ^ ^
t
0 1
pr
P=NULL
i
P->A
输出 : B C
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
0 01 0
算法按中序线索化二叉树
Ch5_20.c
Ch5_20.txt
A
B
C
D
E
A
B D
C E
bt
^
^ ^ ^
t
0 1
pr
P
i
输出 : B C A
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
0 01 01
算法按中序线索化二叉树
Ch5_20.c
Ch5_20.txt
A
B
C
D
E
A
B D
C E
bt
^
^ ^
t
0 1
P
i
输出 : B C A
pr
P->D
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^ ^
t
0 1
P
i
输出 : B C A
pr
P->D
P->E
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^ ^
t
0 1
P=NULL
i
输出 : B C A
pr
P->D
P->E
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 0
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^ ^
t
0 1
P
i
输出 : B C A E
pr
P->D
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 01
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^
t
0 1
P=NULL
i
输出 : B C A E
pr
P->D
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 1
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
^
t
0 1
P
i
输出 : B C A E D
pr
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 01 1 1
算法按中序线索化二叉树
A
B
C
D
E
A
B D
C E
bt
^
t
0 1
P=NULL
i
输出 : B C A E D
pr
JD *zxxsh(JD *bt){ JD *p,*pr,*s[M],*t; int i=0; t=(JD *)malloc(sizeof(JD)); t->lt=0; t->rt=1; t->rc=t; if(bt==NULL) t->lc=t; else { t->lc=bt; pr=t; p=bt; do{ while(p!=NULL)
{ s[i++]=p; p=p->lc; } if(i>0) { p=s[--i]; printf("%c ",p->data); if(p->lc==NULL) { p->lt=1; p->lc=pr;} if(pr->rc==NULL) { pr->rt=1; pr->rc=p;} pr=p; p=p->rc; }
}while(i>0||p!=NULL); pr->rc=t; pr->rt=1; t->rc=pr; } return(t);}
0 0
0 01 0
1 11 1
1
算法按中序线索化二叉树
A
B
C
D
E
输出 : B C A E D
A
B D
C E
t
0 1
1
1 1 1 1
1
0 0
0 0
Status InOrderThreading(BiThrTree &Thrt,BiThrTree T){
// 中序遍历二叉树 T,并将其中序线索化, Thrt 指向头结点。
if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
Thrt->LTag=Link; Thrt->RTag=Thread;// 建头结点
Thrt->rchild=Thrt; // 右指针回指
if(!T) Thrt->lchild=Thrt;// 若二叉树空。则左指针回指
else{
Thrt->lchild=T; pre=Thrt;
InThreading(T); // 中序遍历进行中序线索化
pre->rchild=Thrt; pre->RTag=Thread;// 最后一个结点线索化
Thrt->rchild=pre;} return OK; }
void InThreading(BiThrTree p){
if(p) {
InThreading(p->lchild); // 左子树线索化
if(!p->lchild) {p->LTag=Therad; p->lchild=pre; }//前驱线索
if(!pre->rchild) {pre->RTag=Thread; pre->rchild=p;} // 后继线索
pre=p; //保持 pre 指向 p 的前驱
InThreading(p->rchild); // 右子树线索化
} }
算法按中序线索化二叉树遍历中序线索二叉树
在中序线索二叉树中找结点后继的方法:( 1 )若 rt=1, 则 rc 域直接指向其后继( 2 )若 rt=0, 则结点的后继应是其右子树的左链尾( lt=1) 的结点
在中序线索二叉树中找结点前驱的方法:( 1 )若 lt=1, 则 lc 域直接指向其前驱( 2 )若 lt=0, 则结点的前驱应是其左子树的右链尾( rt=1) 的结点
A
B
C
D
E
0 A 0
1 B 0 0 D 1
1 C 1 1 E 1
T
中序序列: BCAED带头结点的中序线索二叉树
0 1
在后序线索树中找结点的后继1. 若结点是二叉树的根,则后继为空。2. 若结点是其双亲的右孩子或是其双亲的左孩子且其双亲的没有右子树,则其后继即为双亲结点
3. 若结点是其双亲的左孩子,且其双亲有右子树,则其后继为双亲的右子树上按后序遍历列出的第一个结点。
中序遍历二叉树的线索树Status InOrderTraverse_Thr(BiThrTree T,Status(*Visit)(TElemType e)){// T 指向头结点,头结点的左链 lchild 指向根结点,可参见线索化算法。
// 中序遍历二叉线索树 T 的非递归算法,对每个数据元素调用函数 Visit.
p=T->lchild; //p 指向根结点
while(p!=T){ // 空树或遍历结束时, p==T
while(p->LTag==Link) p=p->lchild;
if(!Visit(p->data)) return ERROR;// 访问其左子树为空的结点
while(p->RTag==Thread &&p->rchild!=T){
p=p->rchild; Visit(p->data); // 访问后继结点
}
p=p->rchild;}return OK;}
§6.5 二叉树的应用赫夫曼树 (Huffman)—— 带权路径长度最短的树
定义路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的 ~路径长度:路径上的分支数树的路径长度:从树根到每一个结点的路径长度之和结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积树的带权路径长度:树中所有带权结点的路径长度之和
结点到根的路径长度——k
l
权值——k
w其中:
n
1kk
lk
wwpl记作:
结点到根的路径长度——k
l
权值——k
w其中:
n
1kk
lk
wwpl记作:
Huffman 树——设有 n 个权值 {w1,w2,……wn} ,构造一棵有 n个叶子结点的二叉树,每个叶子的权值为 wi, 则其中带权路径长度 wpl 最小的二叉树叫 ~
例 有 4 个结点,权值分别为 7 , 5 , 2 , 4 ,构造有 4 个叶子结点的二叉树
a b c d7 5 2 4
WPL=7*2+5*2+2*2+4*2=36d
c
a b
2
4
7 5
WPL=7*3+5*3+2*1+4*2=46
a
b
c d
7
5
2 4WPL=7*1+5*2+2*3+4*3=35
n
kKKLWWPL
1
构造 Huffman 树的方法—— Huffman 算法构造 Huffman 树步骤
根据给定的 n 个权值 {w1,w2,……wn} ,构造 n 棵只有根结点的二叉树,令其权值为 wj
在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和
在森林中删除这两棵树,同时将新得到的二叉树加入森林中
重复上述两步,直到只含一棵树为止,这棵树即哈夫曼树
例 w={5, 29, 7, 8, 14, 23, 3, 11}
5 1429 7 8 23 3 11
1429 7 8 23 11
35
8
87
151429 23
35
811
11
35
8
191429 23
87
15
11
35
8
1929 23
14
87
15
29
29
14
87
15
29
11
35
8
1923
42
11
35
8
1923
42
29
14
87
15
29
58
11
35
8
1923
42
29
14
87
15
29
58
100
Huffman 树应用最佳判定树
等级分数段比例
ABCDE
0~59 60~69 70~79 80~89 90~100
0.05 0.15 0.40 0.30 0.10
a<60
a<90
a<80
a<70
E
Y
N
D
Y
N
C
Y
N
B
Y
N
A
70a<80
a<60
C
Y
N
B
Y
N
D
Y
N
E
Y
N
A
80a<90
60a<70
E A
D
B
C
a<80
a<70
a<60a<90
E
Y
N
D
Y
NCY
NB
Y
N
A
在远程通讯中,要将待传字符转换成由二进制的字符串: 设要传送的字符为: ABACCDA
若编码为: A—00
B—01
C—10
D---11
若将编码设计为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。
00010010101100
设要传送的字符为: ABACCDA
若编码为: A—0
B—00
C—1
D---01
关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀。
ABACCDA
000011010但: 0000
AAAA ABA BB重码
设要传送的字符为: ABACCDA
若编码为 : A—0
B—110
C—10
D---111
0110010101110A
C
B D
0
0
0
1
1
1
设计电文总长度最短的二进制前缀编码即为以 n 种字符出现的频率作权,设计一颗赫夫曼树的问题,由此得到的二进制前缀编码便称为赫夫曼编码
规定:左分支用“ 0” 表示右分支用“ 1” 表示
译码过程:分解接收字符串:遇“ 0” 向左,遇“ 1” 向右;一旦到达叶子结点,则译出一个字符,反复由根出发,直到译码完成。
0110010101110
A
C
B D
0
0
0
1
1
1
ABACCDA
求 Huffman 编码:由叶子→根,若:( 1 )从左分支下去,则分支为“ 0” ;( 2 )从右分支下去,则分支为“ 1” 。
A
C
B D
0
0
0
1
1
1
例:已知某系统在通讯时,只出现 C , A , S ,T , B 五种字符,它们出现的频率依次为 2 ,4 , 2 , 3 , 3 ,试设计 Huffman 编码。 W (权) ={2 , 4 , 2 , 3 , 3} ,叶子结点个数 n=5
14
8
4
6
4
2 2
0
0
0
1
1
13 30 1
构造的 Huffman 树
T B A
C S
由 Huffman 树得 Huffman 编码为:T B A C S
00 01 10 110 111
14
8
4
6
4
2 2
0
0
0
1
1
13 30 1
T B A
C S
出现频率越大的字符,其 Huffman编码越短。
Huffman算法实现
由于赫夫曼树中没有度为 1 的结点,则一棵有 n 个叶子结点的 Huffman 树有 2n-1 个结点为求编码需从叶子结点出发走一条从叶子到根的路径,而为译码需从根出发走一条从根到叶子的路径,则对每个结点而言,既需知双亲的信息,又需知孩子结点的信息由此,设定存储结构为:
typedef struct{ unsigned int weight; unsigned int parent,lchild,rchild;}HTNode,*HuffmanTree;// 动态分配数组存储赫夫曼树Typedef char **HuffmanCode; // 动态分配数组存储赫夫曼编码表
求赫夫曼编码的算法:
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){//w 存放 n 个字符的权值(均 >0), 构造赫夫曼树 HT,并求出 n 个字符的赫夫曼编码 HC.if(n<=1) return;m=2*n-1;HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0 号单元未用for(p=HT+1,i=1;i<=n;++i,++p,++w) *p={*w,0,0,0} ;for(; i<=m; ++i,++p) *p={0,0,0,0};for(i=n+1;i<=m;++i){ // 建赫夫曼树// 在 HT[1..i-1]选择 parent 为 0 且 weight 最小的两个结点,其序号分别为 s1 和 s2.Select(HT,i-1,s1,s2);HT[s1].parent=i; HT[s2].parent=i;HT[i].lchild=s1; HT[i].rchild=s2;HT[i].weight=HT[s1].weight+HT[s2].weight;}
// 从叶子到根逆向求每个字符的赫夫曼编码HC=(HuffmanCode)malloc((n+1)*sizeof(char *));// 分配 n 个字符编码的头指针向量cd=(char *)malloc(n*sizeof(char));// 分配求编码的工作空间cd[n-1]=‘\0’; // 编码结束符。for(i=1;i<=n;++i){ //逐个字符求赫夫曼编码start=n-1; // 编码结束符位置for(c=i,f=HT[i].parent; f!=0;c=f,f=HT[f].parent) // 从叶子到根逆向求编码if(HT[f].lchild==c) cd[--start]=‘0’;else cd[--start]=‘1’;HC[i]=(char *)malloc((n-start)*sizeof(char));// 为第 i 个字符编码分配空间strcpy(HC[i],&cd[start]); // 从 cd复制编码(串)到 HC.}free(cd); //释放工作空间}
从根出发遍历得 Huffman 编码