第六章 树和二叉树

126
+ 第第第 第第第第第

Upload: baris

Post on 05-Jan-2016

47 views

Category:

Documents


4 download

DESCRIPTION

第六章 树和二叉树. 树的定义和基本术语 树的存储 二叉树 遍历二叉树和线索二叉树 树、森林与二叉树的转换 赫夫曼树及其应用. 树的定义和基本术语. 树的类型定义. 数据对象 D : D 是具有相同特性的数据元素的集合。 数据关系 R : 若 D 为空集,则称为空树 。 否则 : 在 D 中存在唯一的称为根的数据元素 root ; 当 n>1 时,其余结点可分为 m (m>0) 个互不相交的有限集 T 1 , T 2 , …, T m ,其中每一棵子集本身又是一棵符合本定义的树,称为根 root 的子树。. A. C. B. D. E. F. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第六章 树和二叉树

+

第六章 树和二叉树

Page 2: 第六章 树和二叉树

+

树的定义和基本术语树的存储二叉树遍历二叉树和线索二叉树树、森林与二叉树的转换赫夫曼树及其应用

Page 3: 第六章 树和二叉树

树的定义和基本术语

Page 4: 第六章 树和二叉树

树的类型定义数据对象 D :

D 是具有相同特性的数据元素的集合。数据关系 R :

若 D 为空集,则称为空树 。否则 :

在 D 中存在唯一的称为根的数据元素 root ;当 n>1 时,其余结点可分为 m (m>0) 个互不相交的有限集 T1, T2, …, Tm ,其中每一棵子集本身又是一棵符合本定义的树,称为根root 的子树。

Page 5: 第六章 树和二叉树

K

E F JH IG

B DC

A根子树

A( B(E, F), C(G), D(H(k), I, J))

T1 T3T2树根

Page 6: 第六章 树和二叉树

树的逻辑特征:

树中只有根结点无直接前驱除了根结点,其余结点都有且仅有一个直接前驱

树中每个结点可以有零个或多个直接后继

Page 7: 第六章 树和二叉树

树的表示法1. 分支图表示法

2. 嵌套集合表示法

AA

BB CC

DD

AABB CCDD 3. 广义表表示法

(A(B(D),C))

Page 8: 第六章 树和二叉树

基本术语结点结点的度叶子结点或终端结点分支结点或非终端结点树的度孩子和双亲兄弟祖先和子孙结点的层次堂兄弟树的深度

Page 9: 第六章 树和二叉树

A

B C D

E F G H I J

MK L

基本术语

结点 结点的度叶结点分支结点树的度

Page 10: 第六章 树和二叉树

基本术语

孩子和双亲 兄弟 祖先和子孙 堂兄弟

A

B C D

E F G H I J

MK L

Page 11: 第六章 树和二叉树

基本术语

结点的层次 树的深度

A

B C D

E F G H I J

MK L

1 层

2 层

4 层

3 层

height= 4

Page 12: 第六章 树和二叉树

基本术语

有序树和无序树:如果将树中结点的各子树看成从左至右是有次序的,则称该树为有序树,否则称为无序树。

森林:森林是 m 棵互不相交的树的集合。

Page 13: 第六章 树和二叉树

树的三种存储结构双亲表示法孩子链表表示法二叉链表表示法

Page 14: 第六章 树和二叉树

0 1 2 3 4 5 6 7 8 9 10

data A B C D E F G H I J Kparrent -1 0 0 0 11 2 33 3 7

K

E F JH IG

B DC

A双亲表示法

data parent

结点结构 :

Page 15: 第六章 树和二叉树

typedef struct PTNode { Elem data; int parent; // 双亲位置域 } PTNode;

#define MAX_TREE_SIZE 100

双亲表示法的类型描述

typedef struct { PTNode nodes[MAX_TREE_SIZE]; int r, n; // 根结点的位置和结点个数 } PTree;

Page 16: 第六章 树和二叉树

孩子链表表示法 :

孩子结点结构 : child next

双亲结点结构 data firstchild

Page 17: 第六章 树和二叉树

0 A

1 B

2 C

3 D

4 E

5 F

6 G

7 H

8 I

9 J

10

K

^

^^

^

^

^^

^

1

^

2

^

3

^

4 5

6

7 8 9

10

data firstchild

K

E F JH IG

B DC

A

孩子链表表示法 :

Page 18: 第六章 树和二叉树

typedef struct CTNode { int child; struct CTNode *next; } *ChildPtr;

孩子链表类型描述 :

#define MAX_TREE_SIZE 100

Page 19: 第六章 树和二叉树

typedef struct { CTBox nodes[MAX_TREE_SIZE]; int n, r; // 结点数和根结点的位置 } CTree;

typedef struct { Elemtype data; ChildPtr firstchild; // 孩子链的头指针 } CTBox;

Page 20: 第六章 树和二叉树

左孩子右兄弟表示法

结点结构如下:

存放第一个孩子的地址

存放下一个兄弟的地址

firstchild data nextsibling

指针域 数据域 指针域

Page 21: 第六章 树和二叉树

A

B C D

E F

G

root

C

AB

DEF

G

Page 22: 第六章 树和二叉树

typedef struct CSNode{ Elem data; struct CSNode *firstchild, *nextsibling;} CSNode, *CSTree;

左孩子右兄弟表示法类型描述 :

Page 23: 第六章 树和二叉树

二叉树二叉树的类型定义二叉树的性质二叉树的存储

Page 24: 第六章 树和二叉树

二叉树的类型定义二叉树是 n ( n≥0 )个结点的有限集,它或者为空,或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。

二叉树的特点:二叉树的度小于等于 2二叉树是一棵有序树

Page 25: 第六章 树和二叉树

二叉树的五种形态:

φ

(a) 空树 (b) 仅有根 (c) 右子树空

(e) 左子树空(d) 左、右子树均在

Page 26: 第六章 树和二叉树

二叉树的性质

性质 1 在二叉树的第 i 层上至多有 2i - 1

个结点。性质 2 深度为 k 的二叉树至多有 2k-1个结点 (k1) 。

性质 3 对任何一棵二叉树 T, 如果其叶结点数为 n0, 度为 2 的结点数为 n2,则 n0 = n2 + 1 。

Page 27: 第六章 树和二叉树

满二叉树:深度为 k ,且有 2k-1 个结点的二叉树。

完全二叉树:深度为 k ,结点数为 n 的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从 1 到 n 的结点一一对应时,称为完全二叉树。

两类特殊形态的二叉树

Page 28: 第六章 树和二叉树

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

Page 29: 第六章 树和二叉树

二叉树的性质性质 4 具有 n (n 0) 个结点的完全二叉树的深度为 log2(n) + 1 。

性质 5 如果对一棵有 n 个结点的完全二叉树的结点按层次顺序编号,则对任一结点有:如果 i=1 ,则结点 i 是二叉树的根,无双亲;如果 i>1 ,则其双亲是结点 i/2

如果 2i>n ,则结点 i 无左孩子;否则其左孩子是结点 2i

如果 2i+1>n ,则结点 i 无右孩子;否则其右孩子是结点 2i+1

Page 30: 第六章 树和二叉树

在一棵度为 m 树中,度为 1 的结点数为 n1 ,度为 2 的结点数为 n2 ,……,度为 m 的结点数为 nm ,则该数中含有多少个叶子结点?有多少个非终端结点?

Page 31: 第六章 树和二叉树

解:设度为 0 的结点(即终端结点)数目为 n0 ,树中的分支数为 B ,树中总的结点数为 N ,则有:( 1 )从结点的度考虑: N= n0+ n1+ n2+……+nm( 2 )从分支数目考虑:一棵树中只有一个根结点,其他的均为孩子结点,而孩子结点可以由分支数得到。所以有:N=B+1=0×n0+1×n1+2×n2+…+m×nm+1

由以上两式,可得n0+ n1+ n2+……+nm=0×n0+1×n1+2×n2+…+m×nm+1

从而可导出叶子结点的数目为:n0=0×n1+1×n2+…+ ( m-

1 ) ×nm+1=1+从而可以得到非终端结点的数目为

N- n0= n1+ n2+……+nm=

m

iini

2

)1(

m

iin

1

Page 32: 第六章 树和二叉树

二叉树的顺序存储表示按满二叉树的结点层次编号,用一组地址连续的存储单元,依编号存放二叉树中的数据元素,结点的相对位置蕴含着结点之间的关系。

Page 33: 第六章 树和二叉树

C

E

F G

D

B

A1

2 3

4 5 7

8 9 10

11

6

A B C D E \0 \0 \0 \0 \0 F G

0 1 2 3 4 5 6 7 8 8 10 11

Page 34: 第六章 树和二叉树

二叉树的二叉链表存储表示结点结构

lchild data rchild

数据域左指针域 右指针域

Page 35: 第六章 树和二叉树

H

D E

B C

A

GF

A

B C

F G

ED

H

^ ^

^

^ ^ ^ ^ ^^

bt

Page 36: 第六章 树和二叉树

二叉链表的类型描述typedef char TElemType;

typedef struct BiNode{

TElemType data;

struct BiNode *lchild;

struct BiNode *rchild;

}BiTNode,*BiTree;

Page 37: 第六章 树和二叉树

遍历二叉树和线索二叉树二叉树的遍历及递归算法二叉树遍历算法的应用二叉树遍历算法非递归实现二叉树的线索化线索二叉树的遍历

Page 38: 第六章 树和二叉树

遍历二叉树定义:

按某条搜索路径巡访二叉树中的结点,使得每个结点均被访问,且仅被访问一次。

Page 39: 第六章 树和二叉树

D

L R

LDR 、 LRD 、 DLRRDL 、 RLD 、 DRL

先序遍历

中序遍历

后续遍历

Page 40: 第六章 树和二叉树

先序遍历二叉树

若二叉树为空,则空操作;否则访问根结点先序遍历左子树先序遍历右子树

Page 41: 第六章 树和二叉树

A

D

B C

D L R

AD L R

D L R

>

B

>>

D

>>

C

D L R

先序遍历序列: A B D C

先序遍历 :

Page 42: 第六章 树和二叉树

中序遍历二叉树

若二叉树为空,则空操作;否则中序遍历左子树访问根结点中序遍历右子树

Page 43: 第六章 树和二叉树

A

D

B C

中序遍历序列: B D A C

中序遍历 : L D R

B

L D R

L D R

>

A

>> D

>> C

L D R

Page 44: 第六章 树和二叉树

后序遍历二叉树

若二叉树为空,则空操作;否则后序遍历左子树后序遍历右子树访问根结点

Page 45: 第六章 树和二叉树

A

D

B C

后序遍历序列: D B C A

后序遍历 : L R D

L R D

L R D>

A

>> D

>>

C

L R D

B

Page 46: 第六章 树和二叉树

-

+ /

a *

b -

e f

c d

先序遍历:中序遍历:后序遍历:

- + a * b - cd / e f-+a *b -c d /e f

-+a *b -cd /ef

Page 47: 第六章 树和二叉树

H

D E

B C

A

GF

先序序列:

ABDFCEGH

中序序列:

BFDAGEHC后序序列:

FDBGHECA

Page 48: 第六章 树和二叉树

M

D E GF

B C

A

H

I

L

J K PN

Page 49: 第六章 树和二叉树

思考 1 :

满足下列条件的所有二叉树:1 、先序序列和中序序列相同2 、中序序列和后序序列相同3 、先序序列和后序序列相同

Page 50: 第六章 树和二叉树

思考 2 :

1 、怎样由二叉树的先序序列和中序序列构造一颗二叉树?

2 、怎样由二叉树的后序序列和中序序列构造一颗二叉树?

Page 51: 第六章 树和二叉树

由二叉树的先序和中序序列建树

二叉树的先序序列二叉树的先序序列

二叉树的中序序列二叉树的中序序列 左子树

左子树 右子树

右子树

Page 52: 第六章 树和二叉树

DEBA

先序序列为:中序序列为:

C E D ABD E B CA

E

C

BABD

A

Page 53: 第六章 树和二叉树

先序序列为: A B D E C F H G中序序列为: D B E A H F C G构造一棵二叉树。

D E GF

B C

A

H

Page 54: 第六章 树和二叉树

例如:后序序列为: D E B H F G C A

中序序列为: D B E A H F C G

画出该二叉树

Page 55: 第六章 树和二叉树

二叉树遍历算法递归实现

void InOrder ( BiTNode *T ) {

if ( T != NULL ) {

InOrder ( T->lChild );

Visit( T->data);

InOrder ( T->rChild );

}

}

先序遍历中序遍历后序遍历

void PreOrder ( BiTNode *T )

{

if ( T != NULL ) {

Visit( T->data);

PreOrder ( T->lChild );

PreOrder ( T->rChild );

}

}

void PostOrder ( BiTNode * T )

{

if ( T != NULL ) {

PostOrder ( T->lChild );

PostOrder ( T->rChild );

Visit( T->data);

}

}

Page 56: 第六章 树和二叉树

二叉树遍历算法的应用举例:

统计二叉树中叶子结点的个数求二叉树的深度建立二叉树的存储结构

Page 57: 第六章 树和二叉树

统计二叉树中叶子结点的个数算法基本思想 :

先序 ( 或中序或后序 ) 遍历二叉树,在遍历过程中查找叶子结点,并计数。由此,需在遍历算法中增添一个“计数器”,并将算法中“访问结点”的操作改为:若是叶子,则计数器增 1 。

Page 58: 第六章 树和二叉树

int CountLeaf (BiTree T)

{

if (!T) return 0;

if ((!T->lchild)&& (!T->rchild))

return 1;

return(CountLeaf( T->lchild)+

CountLeaf( T->rchild));

}

Page 59: 第六章 树和二叉树

求二叉树的深度

算法基本思想 :

从二叉树深度的定义可知,二叉树的深度应为其左、右子树深度的最大值加 1 。由此,需先分别求得左、右子树的深度,算法中“访问结点”的操作为:求得左、右子树深度的最大值,最后加 1 。

Page 60: 第六章 树和二叉树

A

B C

D E F

G H

I

Page 61: 第六章 树和二叉树

int Depth (BiTree T ){ if ( !T ) depth= 0; else { Left = Depth( T->lchild ); Right= Depth( T->rchild ); depth=1+(Left>Right? Left:Right); } return depth;}

Page 62: 第六章 树和二叉树

以带空格的先序序列建立二叉树: 根 左子树 右子树定义一棵二叉树

例如 :

以空白字符“ ”︼ 表示•空树

•只含一个根结点的二叉树

A 以字符串“ A ”︼ ︼ 表示

建立二叉树的存储结构

Page 63: 第六章 树和二叉树

如图所示的二叉树的先序遍历顺序为A B C @ @ D E @ G @ @ F @ @ @

A

B

C D

E

G

F@ @

@

@ @

@ @

@

Page 64: 第六章 树和二叉树

void CreateBiTree ( BiTree &T ) { scanf(&ch); if ( ch==‘ ’ ) T=NULL; else{ T=(BiTNode *)malloc(sizeof(BiTNode)) ; T->data = ch; CreateBiTree ( T->lChild ); CreateBiTree ( T->rChild ); }}//CreateBiTree

Page 65: 第六章 树和二叉树

二叉树遍历非递归算法的实现

--借助栈实现

Page 66: 第六章 树和二叉树

在高级语言编制的程序中,调用函数和被调用函数之间的链接及信息交换需通过栈来进行。

为了保证递归函数的正确执行,系统需设立一个递归工作栈作为整个递归函数运行期间使用的数据存储区。每一层递归所需信息构成一个“工作记录”,其中包括所有的实在参数、所有的局部变量以及上一层的返回地址。每进入一层递归,就产生一个新的工作记录压入栈顶,每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录。

Page 67: 第六章 树和二叉树

中序遍历二叉树 ( 非递归算法 ) 用栈实现

ba

a

b c

d e

ada a

a b入栈

b退栈访问

d入栈 d退栈访问

e 退栈

访问

ec c

栈空

a退栈访问

c e 入栈

c 退栈

访问

Page 68: 第六章 树和二叉树

void InOrder ( BiTree T ) {

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); //退栈 visit(p->data); // 访问根 Push(S, p->rchild); }//if

}//while

}

Page 69: 第六章 树和二叉树

void InOrder ( BiTree T ) {

InitStack( &S ); p=T;

while ( p||!StackEmpty(S)) {

while(p) {

Push(S, p); p=p->lchild;}

if ( !StackEmpty(S) ) { //栈非空 Pop(S, p); //退栈 visit(p->data); // 访问根 p=p->rchild; }

}//while

}

Page 70: 第六章 树和二叉树

先序遍历二叉树 ( 非递归算法 ) 用栈实现a

c

a

b c

d e

dc c

访问a进栈c左进b

访问b进栈d左进空

退栈d访问d左进空

退栈c访问c左进e

访问e左进空退栈

结束

Page 71: 第六章 树和二叉树

void PreOrder( BiTree T ) {

InitStack(S); p = T;

while ( p||!StackEmpty(S)) {

while(p) {

visit(p->data);

Push(S,p);

p=p->lchild;} if ( !StackEmpty(S) ) { Pop(S, p);

p = p->rChild; }

}

}

a

b c

d e

Page 72: 第六章 树和二叉树

后序遍历二叉树 ( 非递归算法 ) 用栈实现后序遍历时使用的栈的结点定义typedef struct { BiTNode *ptr; // 结点指针 enum tag{ L, R }; // 该结点退栈标记} StackNode; 根结点的 tag = L ,表示从左子树退出 , 访问右子树。 tag = R, 表示从右子树退出 , 访问根。

ptr tag{L,R}

Page 73: 第六章 树和二叉树

void PostOrder ( BiTree T ) {InitStack(S); p = T;do {

while ( p != NULL ) { //向左子树走

w.ptr = p; w.tag = L; Push (S, w); p = p->lChild; }continue = 1; // 继续循环标记

Page 74: 第六章 树和二叉树

while ( continue && !StackEmpty(S) ) { Pop (S, w); p = w.ptr; switch ( w.tag ) { //判断栈顶 tag标记

case L : w.tag = R; Push (S, w); continue = 0;

p = p->rChild; break; case R : Visit( p->data);

} }} while ( p != NULL || !StackEmpty(S) );

}

Page 75: 第六章 树和二叉树

二叉树的线索化

Page 76: 第六章 树和二叉树

问题的提出: 当以二叉链表作为存储结构时 , 只能找到结

点的左右孩子的信息 , 而不能在结点的任一序列的前驱与后继信息 , 这种信息只有在遍历的动态过程中才能得到。

解决办法: 利用二叉链表中的空指针 增加标志域

效果: 为二叉树建立了一个双向线索链表

Page 77: 第六章 树和二叉树

线索二叉树的结构 结点结构

按遍历序列分为:先序线索二叉树中序线索二叉树后序线索二叉树

ltag data rtag

lchild rchild

指向结点的左儿子

指向结点的前驱

lchild

lchildltag

,0

,1

指向结点的右儿子

指向结点的后继

rchild

rchildrtag

,0

,1

Page 78: 第六章 树和二叉树

以上述结点结构构成的二叉链表称为线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。

Page 79: 第六章 树和二叉树

先序线索二叉树

A

F

E D

C B

0 A 0

0 B 1 0 C 1

1 D 0 1 E 1

1 F 1先序遍历序列:ABDFCE

Null

Page 80: 第六章 树和二叉树

0 A 0

0 B 1 0 C 1

1 D 0 1 E 1

1 F 1

中序线索二叉树

中序遍历序列:DFBAEC Null

Null

A

F

E D

C B

Page 81: 第六章 树和二叉树

0 A 0

0 B 1 0 C 1

1 D 0 1 E 1

1 F 1

后序线索二叉树

后序遍历序列:FDBECA

Null

A

F

E D

C B

Page 82: 第六章 树和二叉树

线索二叉链表的类型定义typedef struct BiThrNode{ TElemtype data; struct BiThrNode *lchild ,*rchild; int ltag,rtag;}BiThrNode,*BiThrTree;

ltag data rtag

lchild rchild

Page 83: 第六章 树和二叉树

void InOrderThr(BiThrTree &Thrt,BiThrTree T){// 中序遍历二叉树 T ,并将其中序线索化 Thrt=(BiThrTree)malloc(sizeof(BiThrNode)); Thrt->ltag=0; Thrt->rtag=1; // 建立头结点 Thrt->rchild=Thrt; // 右指针回指 if (!T) Thrt->lchild =Thrt; else { Thrt->lchild=T; pre=Thrt; InThreading(T); // 中序遍历进行中序线索化 pre->rchild=Thrt; pre->rtag=1; //最后一个结点线索化 Thrt->rchild=pre; }}

Page 84: 第六章 树和二叉树

void InTreading(BiThrTree p){ // 中序遍历进行中序线索化 if (p) { InThreading(p->lchild); // 左子树线索化 if (!p->lchild) // 前驱线索 { p->ltag=1; p->lchild=pre; } if (!pre->rchild) // 后继线索 { pre->rtag=1; pre->rchild=p; } pre=p; InThreading(p->rchild);// 右子树线索化 }}

Page 85: 第六章 树和二叉树

中序遍历线索化二叉树void InOrderTraverse_Thr(BiThrTree T){ p=T->lchild; while(p!=T){ while(p->ltag==0) p=p->lchild; visit(p->data); while(p->rtag==1&&p->rchild!=T){ p=p->rchild; visit(p->data); } p=p->rchild; }}

Page 86: 第六章 树和二叉树

树、森林与二叉树的转换

Page 87: 第六章 树和二叉树

将树转换成二叉树加线:在兄弟之间加一连线抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系

旋转:以树的根结点为轴心,将整树顺时针转 45°

Page 88: 第六章 树和二叉树

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

AB

C

D

E

F

G H

I树转换成的二叉树其右子树一定为空

Page 89: 第六章 树和二叉树

将二叉树转换成树加线:若 p 结点是双亲结点的左孩子,则将 p 的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与 p 的双亲用线连起来

抹线:抹掉原二叉树中双亲与右孩子之间的连线

调整:将结点按层次排列,形成树结构

Page 90: 第六章 树和二叉树

AB

C

D

E

F

G H

I

AB

C

D

E

F

G H

I

AB

C

D

E

F

G H

I

AB

C

D

E

F

G H

I

A

B C D

E F G H I

Page 91: 第六章 树和二叉树

森林转换成二叉树将各棵树分别转换成二叉树将每棵树的根结点用线相连以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构

Page 92: 第六章 树和二叉树

A

B C D

E

F

G

H I

J

A

B

CD

E

F

G

H

I

J

A

B

CD

E

F

G

H

I

J

A

B

C

D

E

FG

H

I

J

Page 93: 第六章 树和二叉树

二叉树转换成森林抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树

还原:将孤立的二叉树还原成树

Page 94: 第六章 树和二叉树

A

B

C

D

E

FG

H

I

J

A

B

C

D

E

FG

H

I

J

A

B

CD

E

F

G

H

I

J

A

B C D

E

F

G

H I

J

Page 95: 第六章 树和二叉树

树和森林的遍历树的遍历

先序遍历:先访问根结点,然后依次先根遍历根的每棵子树。

后序遍历:后根访问根的每棵子树,然后访问根结点。

Page 96: 第六章 树和二叉树

K

E F JH IG

B DC

A

A B E CF G HD

先序遍历序列

K I J

Page 97: 第六章 树和二叉树

K

E F JH IG

B DC

A

E F B CG K IH

后序遍历序列J D A

Page 98: 第六章 树和二叉树

森林的遍历先序遍历若森林非空,则可按下述规则遍历之:访问森林中第一棵树的根结点先序遍历第一棵树中根结点的子树森林先序遍历除去第一棵树之后剩余的树构成的森林

Page 99: 第六章 树和二叉树

中序遍历若森林非空,则可按下述规则遍历之:中序遍历第一棵树中根结点的子树森林访问森林中第一棵树的根结点中序遍历除去第一棵树之后剩余的树构成的森林

Page 100: 第六章 树和二叉树

B DC

A

K

JH I

GE

F

A B C ED F HG

先序遍历序列

K I J

Page 101: 第六章 树和二叉树

B C D FA E HK

中序遍历序列

I J G

B DC

A

K

JH I

GE

F

Page 102: 第六章 树和二叉树

赫夫曼树及其应用

Page 103: 第六章 树和二叉树

路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的 ~

路径长度:路径上的分支数结点的权:在许多应用中,常常将树中的结点赋予一个有意义的数,称为该结点的权。

结点的带权路径长度:是指该结点到树根之间的路径长度与该结点上权的乘积。

基本术语

Page 104: 第六章 树和二叉树

树的路径长度:从树根到每一个结点的路径长度之和树的带权路径长度:树中所有叶结点的带权路径长度之和,记着

n

kkklwwpl

1

基本术语

其中: wk 为权值, lk 为结点到根的路径长度

Page 105: 第六章 树和二叉树

D E GF

B C

A

H

7 5

4

2

Page 106: 第六章 树和二叉树

赫夫曼树( huffman 树)

在权为 w1,w2,…,wn 的 n 个叶子结点的所有二叉树中,带权路径长度 WPL最小的二叉树称为赫夫曼树或最优二叉树。

Page 107: 第六章 树和二叉树

例如,有 4 个结点 a,b,c,d ,分别带权 7,5,2,4 ,由它们作为叶子结点,可以构造下列三棵不同的二叉树

a b dcd

c

ba

a

db

dc

7 5 2 47

7

5

5

2

2

4

4

Ⅰ Ⅱ

Page 108: 第六章 树和二叉树

它们的带权路径长度分别为:

WPL (Ⅰ)=7*2+5*2+2*2+4*2=36

WPL (Ⅱ)=7*3+5*3+2*1+4*2=46

WPL (Ⅲ)=7*1+5*2+2*3+4*3=35

假设给定 n 个权值{ w1,w2,…,wn },如何构造一棵 n 个结点的二叉树,使其为最优二叉树( huffman 树)?

Page 109: 第六章 树和二叉树

构造 Huffman 树的方法—— Huffman 算法构造 Huffman 树步骤

根据给定的 n 个权值 {w1,w2,……wn} ,构造n 棵只有根结点的二叉树,令其权值为 wj

在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和

在森林中删除这两棵树,同时将新得到的二叉树加入森林中

重复上述两步,直到森林中只含一棵树为止,这棵树即哈夫曼树

Page 110: 第六章 树和二叉树

例如权值集合W={7,19,2,6,32,3,21,10 } 构造赫夫曼树的过程。

Page 111: 第六章 树和二叉树

36 103219 27 21

32

5

5

32

32

5 6

11

32

5 6

11

107

17

17

107

32

5 6

11 17

107

28

28

32

5 6

11 17

1072119

40

40

2119

28

32

5 6

11 17

107

32

60

60

3228

32

5 6

11 17

107

Page 112: 第六章 树和二叉树

40

2119

60

3228

32

5 6

11 17

107

100

Page 113: 第六章 树和二叉树

Huffman 树应用最佳判定树赫夫曼编码

Page 114: 第六章 树和二叉树

等级分数段比例

ABCDE

0~59 60~6970~7980~8990~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

NA

70a<80

a<60

C

Y

N

B

Y

N

D

Y

N

E

Y

NA

80a<90

60a<70

E A

D

E

C

a<80

a<70

a<60a<90

E

Y

N

D

Y

NCY

NB

Y

N

A

最佳判定树

Page 115: 第六章 树和二叉树

赫夫曼编码在数据通信中,需要将传送的文字转换成由二进制字符‘ 0’ 和‘ 1’ 组成的二进制串,我们称之为编码。反之,收到电文后要将电文转换成原来的文字,称为译码。

如何构造电文总长最短的二进制编码?

Page 116: 第六章 树和二叉树

例如:将文字“ ABACCDA” 转换成电文。文字中有四种字符,用 2 位二进制便可分辨。

A B C D00 01 10 11

方案 1:

则上述文字的电文为:00010010101100 共 14 位。译码时,只需每 2 位一译即可。

特点:等长等频率编码,译码容易,但电文不一定最短 .

Page 117: 第六章 树和二叉树

A B C D0 00 1 01

方案2:

采用不等长编码,让出现次数多的字符用短码。则 ABACCDA文字的电文为:000011010 共 9 位

但无法译码,它即可译为 BBCCACA ,也可译为 AAAACCDA 等。

Page 118: 第六章 树和二叉树

A B C D0 110 10 111

方案 3:

采用不等长编码,让出现次数多的字符用短码,且任一编码不能是另一编码的前缀。

则 ABACCDA文字的电文为:

0110010101110 共 13 位

Page 119: 第六章 树和二叉树

设有 n 种字符,每种字符出现的次数为 Wi ,

其编码长度为 Li (i=1,2,... n) ,

则整个电文总长度为∑WiLi ,

要得到最短的电文,即使得∑WiLi最小。

也就是以字符出现的次数为权值,构造一棵 Huffman 树,并规定左分支编码位 0 ,右分支编码为 1 ,则字符的编码就是从根到该字符所在的叶结点的路径上的分支编号序列,称为 Huffman 编码。

Page 120: 第六章 树和二叉树

赫夫曼编码的一般步骤:构造赫夫曼树:设需要编码的集合为 {d1,d2,

…,dn} ,它们在电文中出现的次数或频率集合为 {w1,w2,…,wn} ,以 d1,d2,…,dn 作为叶子结点,以 w1,w2,…,wn 作为它们的权值,构造一个赫夫曼树

设计赫夫曼编码:将树中结点引向其左孩子的分支标“ 0” ,引向其右孩子的分支标“ 1” ;则从根结点到每个叶子结点所经分支上的‘ 0’ 和‘ 1’ 的组合即为该字符所对应的编码,我们称之为赫夫曼编码。

Page 121: 第六章 树和二叉树

已知某系统在通信联络中只可能出现 8种字符 A,B,C,D,E,F,G,H ,其概率分别为0.07,0.19,0.02,0.06,0.32,0.03,0.21,0.1试设计赫夫曼编码。

Page 122: 第六章 树和二叉树

40

2119

60

3228

32

5 6

11 17

107

100

0 1

0 1 0 1

0 1

0 1 0 1

0 1 A

B

C

D

A : 1010

B : 00

C : 10000

D : 1001

EE : 11

F

F : 10001G

G : 01

H

H : 1011

Page 123: 第六章 树和二叉树

Huffman 编码算法实现一棵有 n 个叶子结点的 Huffman 树有2n-1 个结点

采用顺序存储结构——一维数组结点类型定义

typedef struct{ int weight; int parent,lchild,rchild;}HTNode,*HuffmanTree;typedef char **HuffmanCode;

Page 124: 第六章 树和二叉树

例如:已知某系统在通信联络中只可能出现 6 种字符,其概率分别为:0.22,0.13,0.07,0.08,0.23,0.27,试设计哈夫曼编码。

Page 125: 第六章 树和二叉树

求哈夫曼编码的算法void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){if(n<1) return;m=2*n-1;HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));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++){ 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; }

Page 126: 第六章 树和二叉树

HC=(HuffmanCode)malloc((n+1)*sizeof(char *));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)); strcpy(HC[i],&cd[start]); }free(cd);}