slide1
Download
Skip this Video
Download Presentation
教学要求:

Loading in 2 Seconds...

play fullscreen
1 / 190

教学要求: - PowerPoint PPT Presentation


  • 135 Views
  • Uploaded on

教学要求: 掌握树的定义及基本术语,掌握二叉树的定义、性质和存储结构,熟练掌握二叉树的遍历方法及其实现和线索二叉树的构造,掌握树,森林和二叉树之间相互转换的方法,掌握哈夫曼树的定义和哈夫曼算法,了解哈夫曼编码的方法。 教学重点与难点: 二叉树的存储结构,二叉树的遍历及线索二叉树的构造,树、森林和二叉树之间的相互转换,哈夫曼树的定义和哈夫曼算法。. 第六章 树和二叉树. 6.1 树的定义与基本术语 6.2 二叉树 6.3 二叉树的遍历与线索化 6.4 树、森林和二叉树的关系 6.5 哈夫曼树及其应用 6.6 总结与提高.

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' 教学要求:' - read


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1

教学要求:

掌握树的定义及基本术语,掌握二叉树的定义、性质和存储结构,熟练掌握二叉树的遍历方法及其实现和线索二叉树的构造,掌握树,森林和二叉树之间相互转换的方法,掌握哈夫曼树的定义和哈夫曼算法,了解哈夫曼编码的方法。

教学重点与难点:

二叉树的存储结构,二叉树的遍历及线索二叉树的构造,树、森林和二叉树之间的相互转换,哈夫曼树的定义和哈夫曼算法。

slide2

第六章

树和二叉树

  • 6.1 树的定义与基本术语
  • 6.2 二叉树
  • 6.3 二叉树的遍历与线索化
  • 6.4 树、森林和二叉树的关系
  • 6.5 哈夫曼树及其应用
  • 6.6 总结与提高
slide3

6.1 树的定义与基本术语

1.树的基本概念

2.树的图解表示法

3.树的相关术语

4.树的抽象数据类型

slide4

6.1 树的定义与基本术语

1.树的基本概念

树定义:是n(n≥0)个结点的有限集合T。当n=0时,称为空树;当n>0时,该集合满足如下条件:

(1) 其中必有一个称为根(root)的特定结点,它没有直接前驱,但有零个或多个直接后继。

(2) 其余n-1个结点可以划分成m(m≥0)个互不相交的有限集T1,T2,T3,…,Tm,其中Ti又是一棵树,称为根root的子树。每棵子树的根结点有且仅有一个直接前驱,可有零个或多个直接后继。

slide5

A

B

C

D

E

F

G

H

I

J

K

L

M

例如:一棵树的逻辑结构图(6.1)为:

从图中可以看出它好象一棵倒栽的树。

slide6

A

B

C

D

A

B

C

D

2.树的图解表示法

1)倒置树结构(树形表示法)

2)文氏图表示法(嵌套集合形式) 图6.2

3)广义表形式(嵌套扩号表示法)

4)凹入表示法 图6.3

(A(B(D),C))

图6.2 文氏图表示法

广义表表示法

slide7

3.树的相关术语:

结点:包含一个数据元素及若干指向其它结点的分支信息。

结点的度:一个结点的子树个数称为此结点的度。

叶结点:度为0的结点,即无后继的结点,也称为终端结点。

分支结点:度不为0的结点,也称为非终端结点。

结点的层次:从根结点开始定义,根结点的层次为1,根的直接后继的层次为2,依此类推。

结点的层次编号:将树中的结点按从上层到下层、同层从左到右的次序排成一个线性序列,依次给它们编以连续的自然数。

slide8

树的度:树中所有结点的度的最大值。

树的高度(深度):树中所有结点的层次的最大值。

有序树:在树T中,如果各子树Ti之间是有先后次序的,则称为有序树。

森林:m(m≥0)棵互不相交的树的集合。将一棵非空树的根结点删去,树就变成一个森林;反之,给森林增加一个统一的根结点,森林就变成一棵树。

同构:对两棵树,通过对结点适当地重命名,就可以使两棵树完全相等(结点对应相等,对应结点的相关关系也像等),则称这两棵树同构。

slide9

我们常常借助人类家族树的术语,以便于直观理解结点间的层次关系。我们常常借助人类家族树的术语,以便于直观理解结点间的层次关系。

孩子结点:一个结点的直接后继称为该结点的孩子结点。如上图的B、C是A的孩子。

双亲结点:一个结点的直接前驱称为该结点的双亲结点。上图中A是B、C的双亲。

兄弟结点:同一双亲结点的孩子结点之间互称兄弟结点。上图中的结点H、I、J互为兄弟结点。

堂兄弟:父亲是兄弟关系或堂兄关系的结点称为堂兄弟结点。在图6.1中,结点E、G、H互为堂兄弟。

slide10

祖先结点:一个结点的祖先结点是指从根结点到该结点的路径上的所有结点。如结点K的祖先结点是A、B、E。

子孙结点:一个结点的直接后继和间接后继称为该结点的子孙结点。如结点D的子孙是H、I、J、M。

前辈:层号比该结点小的结点,都称为该结点的前辈。

后辈:层号比该结点大的结点,都称为该结点的后辈。

slide11

A

B

C

D

E

F

G

H

I

J

K

L

M

叶子:K,L,F,G,M,I,J

结点A的度:3

结点B的度:2

结点M的度:0

结点I的双亲:D

结点L的双亲:E

结点A的孩子:B,C,D

结点B的孩子:E,F

树的度:3

树的深度:4

结点B,C,D为兄弟

结点K,L为兄弟

结点A的层次:1

结点M的层次:4

结点F,G为堂兄弟

结点A是结点F,G的祖先

slide12

F

root

A

森林(forest):

B

C

D

是m(m≥0)棵互

不相交的树的集合

E

F

G

H

I

J

K

L

M

任何一棵非空树是一个二元组

Tree = (root,F)

其中:root 被称为根结点

F 被称为子树森林

slide13

4.树的抽象数据类型

数据对象D:一个集合,该集合中的所有元素具有相同的特性。

数据关系R:若D为空集,则为空树。若D中仅含有一个数据元素,则R为空集,否则R={H},H是如下的二元关系:

(1) 在D中存在唯一的称为根的数据元素root,它在关系H下没有前驱。

(2) 除root以外,D中每个结点在关系H下都有且仅有一个前驱。

slide14

基本操作:

(1)InitTree(Tree): 将Tree初始化为一棵空树。

(2) DestoryTree(Tree): 销毁树Tree。

(3) CreateTree(Tree): 创建树Tree。

(4) TreeEmpty(Tree): 若Tree为空,则返回TRUE,否则返回FALSE。

(5) Root(Tree): 返回树Tree的根。

slide15

(6) Parent(Tree,x): 树Tree存在,x是Tree中的某个结点。若x为非根结点,则返回它的双亲,否则返回“空”。

(7) FirstChild(Tree,x): 树Tree存在,x是Tree中的某个结点。若x为非叶子结点,则返回它的第一个孩子结点,否则返回“空”。

(8) NextSibling(Tree,x): 树Tree存在,x是Tree中的某个结点。若x不是其双亲的最后一个孩子结点,则返回x后面的下一个兄弟结点,否则返回“空”。

slide16

基本操作:

(9) InsertChild(Tree,p,Child): 树Tree存在,p指向Tree中某个结点,非空树Child与Tree不相交。将Child插入Tree中,做p所指向结点的子树。

(10) DeleteChild(Tree,p,i): 树Tree存在,p指向Tree中某个结点,1≤i≤d,d为p所指向结点的度。删除Tree中p所指向结点的第i棵子树。

(11) TraverseTree(Tree,Visit()): 树Tree存在,Visit()是对结点进行访问的函数。按照某种次序对树Tree的每个结点调用Visit()函数访问一次且最多一次。若Visit()失败,则操作失败。

返回

slide17

6.2 二叉树

  • 6.2.1 二叉树的定义与基本操作
  • 6.2.2 二叉树的性质
  • 6.2.3 二叉树的存储结构
slide18

6.2.1 二叉树的定义与基本操作

定义:我们把满足以下两个条件的树型结构叫做二叉树(Binary Tree):

(1)每个结点的度都不大于2;

(2)每个结点的孩子结点次序不能任意颠倒。

下面给出二叉树的五种基本形态:

(d)左右子树均

非空的二叉树

(e)只有右子树

的二叉树

(b)只有根结

点的二叉树

(a)空二叉数

(c)只有左子

树的二叉树

slide19

二叉树或为空树,或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成。二叉树或为空树,或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成。

右子树

根结点

A

B

E

C

F

G

D

左子树

H

K

slide20

二叉树的基本操作:

  • Initiate(bt):将bt初始化为空二叉树。
  • (2) Create(bt):创建一棵非空二叉树bt。
  • (3) Destory(bt): 销毁二叉树bt。
  • (4) Empty(bt): 若bt为空,则返回TRUE,否则返回FALSE。
  • (5) Root(bt): 求二叉树bt的根结点。若bt为空二叉树,则函数返回“空”。
  • (6) Parent(bt,x):求双亲函数。求二叉树bt中结点x的双亲结点。若结点x是二叉树的根结点或二叉树bt中无结点x,则返回“空”。
slide21

基本操作:

(7) LeftChild(bt,x):求左孩子。若结点x为叶子结点或x不在bt中,则返回“空”。

(8) RightChild(bt,x):求右孩子。若结点x为叶子结点或x不在bt中,则返回“空”。

(9) Traverse(bt): 遍历操作。按某个次序依次访问二叉树中每个结点一次且仅一次。

(10) Clear(bt):清除操作。将二叉树bt置为空树。

slide22

6.2.2 二叉树的性质

性质1:在二叉树的第i层上至多有2i-1个结点(i≥1)。

证明:

当i=1时,整个二叉树只有一根结点,此时2i-1=20=1,结论成立。

假设i=k时结论成立,即第k层上结点总数最多为2k-1个。

现证明当i=k+1时,结论成立:

因为二叉树中每个结点的度最大为2,则第k+1层的结点总数最多为第k层上结点最大数的2倍,即2×2k-1=2(k+1)-1,故结论成立。

slide23

k

k

第i层上的最大结点个数=  2i-1= 2k-1

i=1

i=1

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

证明:

因为深度为k的二叉树,其结点总数的最大值是将二叉树每层上结点的最大值相加,所以深度为k的二叉树的结点总数至多为:

故结论成立。

slide24

性质3:对任意一棵二叉树T,若终端结点数为n0,而其度数为2的结点数为n2,则n0= n2+1 。

证明:设二叉树中结点总数为n,n1为二叉树中度为1的结点总数。因为二叉树中所有结点的度小于等于2,所以有

n= n0+ n1+n2

设二叉树中分支数目为B,因为除根结点外,每个结点均对应一个进入它的分支,所以有:n=B+1。

又因为二叉树中的分支都是由度为1和度为2的结点发出,所以分支数目为: B=n1+2n2

整理上述两式可得到:n=B+1=n1+2n2+1

将n= n0+ n1+n2代入上式得出n0+ n1+n2=n1+2n2+1,整理后得n0= n2+1,故结论成立。

slide25

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

两种特殊的二叉树:

满二叉树:深度为k且有2k-1个结点的二叉树。在满二叉树中,每层结点都是满的,即每层结点都具有最大结点数。

满二叉树

slide26

1

2

3

4

5

6

7

8

9

10

11

12

13

14

完全二叉树:

深度为k,结点数为n的二叉树,如果其结点1~n的位置序号分别与满二叉树的结点1~n的位置序号一一对应,则为完全二叉树

关系:满二叉树必为完全二叉树,而完全二叉树不一定是满二叉树。

slide27

性质4:具有n个结点的完全二叉树的深度为㏒2n+1。

证明:设n个结点的完全二叉树的深度为k,根据性质2可知,k-1层满二叉树的结点总数为: 2k-1-1

k层满二叉树的结点总数为: 2k-1

显然有:

2k-1 - 1 < n  2k- 12k- 1 n< 2k

取对数有:k -1  log2n < k

因为k是整数,所以k -1 =log2n , k= ㏒2n+1

结论成立。

slide28

性质5:对于具有n个结点的完全二叉树,如果按照从上到下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点有:

(1)若i = 1, 则 i 无双亲结点

若i >1, 则 i 的双亲结点为i /2

(2)若2*i > n, 则 i 无左孩子

若2*i≤n, 则 i 结点的左孩子结点为2*i

(3)若 2*i+1> n ,则i 无右孩子

若 2*i+1≤n, 则i的右孩子结点为2* i+1

用归纳法证明其中的(2)和(3)。

slide29

课堂练习

1、若一个二叉树共有n个结点,试问其最小高度为多少?最大高度为多少?

解:最小高度为└log2n┘ + 1

最大高度为n(当此二叉树是歪斜树时,最大高度为n)。

2、若二叉树有10个树叶结点,试问其度为2的结点有几个?

n0=n2+1 n2=9

slide30

3.对于一棵具有n个结点的完全二叉树,一个结点的编号为i(1≤i≤n),若它有左孩子则左孩子结点的编号为________,若它有右孩子,则右孩子结点的编号为________,若它有双亲,则双亲结点的编号为________。3.对于一棵具有n个结点的完全二叉树,一个结点的编号为i(1≤i≤n),若它有左孩子则左孩子结点的编号为________,若它有右孩子,则右孩子结点的编号为________,若它有双亲,则双亲结点的编号为________。

4. 深度为k的二叉树的结点总数最多为( ).

A.2k-1 B.2K+1 C.2K-1 D. 2k-1

5.假定一棵树的广义表表示为A(D(E,G),H(I,J)),则树中所含的结点数为_________个,树的深度为___________,树的度为_________。

6.在树中,一个结点的直接后继结点称为该结点的________。一个结点的直接前趋结点称为该结点的________。

slide31

6.2.3 二叉树的存储结构

二叉树的结构是非线性的,每一结点最多可有两个后继。

二叉树的存储结构有两种:顺序存储结构和链式存储结构。

slide32

A

B

C

D

E

F

G

H

I

J

K

L

1.顺序存储结构:是用一组连续的存储单元来存放二叉树的数据元素 。

二叉树的顺序存储结构

slide33

A

B

C

D

对于一般的二叉树,我们必须按照完全二叉树的形式来存储,就会造成空间的浪费。单支树就是一个极端情况。

单支树

slide34

A

A

B

C

B

C

D

E

F

D

E

F

G

G

2. 链式存储结构

对于任意的二叉树来说,每个结点只有两个孩子,一个双亲结点。我们可以设计每个结点至少包括三个域:数据域、左孩子域和右孩子域:

二叉链表

二叉链表

二叉树T

slide35

用C语言描述定义二叉树的二叉链表结构如下

typedef struct Node

{DataType data;

strct Node * LChild;

struct Node * RChild;

}BiTNode, *BiTree;

结论:若一个二叉树含有n个结点,则它的二叉链表中必含有2n个指针域,其中必有n+1个空的链域。

slide36

证明:

分支数目B=n-1,即非空的链域有n-1个,故空链域有2n-(n-1)=n+1个。

为了便于找到双亲结点,可以增加一个Parent域,以指向该结点的双亲结点。采用这种结点结构存放称做二叉树的三叉链表存储结构。

三叉链表

LChild

Data

Parent

RChild

slide37

A

A

C

B

C

B

D

E

D

E

lchild data parent rchild

二叉树

三叉链表

三叉链表的结点结构

返回

slide38

6.3 二叉树的遍历与线索化

  • 6.3.1 二叉树的遍历
  • 6.3.2 遍历算法应用
  • 6.3.3 基于栈的递归消除
  • 6.3.4 线索二叉树
  • 6.3.5 由遍历序列确定二叉树
slide39

LChild

Data

RChild

Data

LChild

LChild

RChild

6.3 二叉树的遍历与线索化

二叉树的遍历:指按一定规律对二叉树中的每个结点进行访问且仅访问一次。

6.3.1 二叉树的遍历

二叉树的基本结构由根结点、左子树和右子树组成

如图示

slide40

用L、D、R分别表示遍历左子树、访问根结点、遍历右子树,那么对二叉树的遍历顺序就可以有:用L、D、R分别表示遍历左子树、访问根结点、遍历右子树,那么对二叉树的遍历顺序就可以有:

  • 访问根,遍历左子树,遍历右子树(记做DLR)。
  • 访问根,遍历右子树,遍历左子树(记做DRL)。
  • 遍历左子树,访问根,遍历右子树(记做LDR)。
  • 遍历左子树,遍历右子树,访问根 (记做LRD)。
  • 遍历右子树,访问根,遍历左子树 (记做RDL)。
  • 遍历右子树,遍历左子树,访问根 (记做RLD)。
slide41

在以上六种遍历方式中,如果我们规定按先左后右的顺序,那么就只剩有 DLR 、LDR 和LRD三种。根据对根的访问先后顺序不同,分别称DLR为先序遍历或先根遍历;LDR为中序遍历(对称遍历);LRD为后序遍历。

注意:先序、中序、后序遍历是递归定义的,即在其子树中亦按上述规律进行遍历。

slide42

三种遍历方法的递归定义:

(1)先序遍历(DLR)操作过程:

若二叉树为空,则空操作,否则依次执行如下操作:

①访问根结点;

②按先序遍历左子树;

③按先序遍历右子树。

slide43

D L R

A

D L R

D L R

C

B

A

D

D L R

>

>

>

B

C

>

>

D

先序遍历:

先序遍历序列:A B D C

演示

slide44

(2)中序遍历(LDR)操作过程

若二叉树为空,则空操作,否则依次执行如下操作:

①按中序遍历左子树;

②访问根结点;

③按中序遍历右子树。

slide45

A

C

B

D

B

L D R

L D R

>

>

>

A

C

L D R

>

>

D

中序遍历:

L D R

中序遍历序列:B D A C

演示

slide46

补充

二叉树中序遍历的直观方法:投影法

对于一棵二叉树,从根结点所在的层开始,将所有非空左子树完全位于当前根结点的左方,将所有非空右子树完全位于当前根结点的右方,其于各层均按该方法处理。这样就构造好了一棵符合要求的二叉树,如下图所示:

slide47

A

B

C

D

E

F

G

D B E A F C G

在上述二叉树的正下方画一条水平直线,将树中各结点依次垂直投影到这条水平直线上,此时该水平直线上得到的结点序列即为该二叉树的中序遍历序列(DBEAFCG)

slide48

(3)后序遍历(LRD)操作过程:

若二叉树为空,则空操作,否则依次执行如下操作:

①按后序遍历左子树;

②按后序遍历右子树;

③访问根结点。

slide49

A

C

B

D

L R D

L R D

B

L R D

>

>

>

A

C

>

>

D

后序遍历:

L R D

后序遍历序列: D B C A

演示

slide50

A

B

C

D

E

H

F

G

对于如下图的二叉树,其先序、中序、后序遍历的序列为:

先序遍历: A、B、D、F、G、C、E、H 。

中序遍历: B、F、D、G、A、C、E、H 。

后序遍历: F、G、D、B、H、E、C、A 。

slide51

-

+

/

a

*

f

e

b

-

c

d

-

+

a

*

b

-

c

d

/

e

f

先序遍历:

a

+

b

*

c

-

d

-

e

/

f

中序遍历:

a

b

c

d

-

*

+

e

f

/

-

后序遍历:

层次遍历:

-

+

/

a

*

e

f

b

-

c

d

slide52

以二叉链表作为存储结构,讨论二叉树的遍历算法以二叉链表作为存储结构,讨论二叉树的遍历算法

1) 先序遍历

void PreOrder(BiTree root)

/*先序遍历二叉树, root为指向二叉树(或某一子树)根结点的指针*/

{ if (root!=NULL)

{Visit(root ->data); /*访问根结点*/

PreOrder(root ->LChild); /*先序遍历左子树*/

PreOrder(root ->RChild); /*先序遍历右子树*/

}

}

slide53

2) 中序遍历

void InOrder(BiTree root)

/*中序遍历二叉树, root为指向二叉树(或某一子树)根结点的指针*/

{ if (root!=NULL)

{

InOrder(root ->LChild); /*中序遍历左子树*/

Visit(root ->data); /*访问根结点*/

InOrder(root ->RChild); /*中序遍历右子树*/

}

}

slide54

3) 后序遍历

void PostOrder(BiTree root)

/* 后序遍历二叉树,root为指向二叉树(或某一子树)根结点的指针*/

{ if(root!=NULL)

{

PostOrder(root ->LChild); /*后序遍历左子树*/

PostOrder(root ->RChild); /*后序遍历右子树*

Visit(root ->data); /*访问根结点*/

}

}

slide55

B

A

D

Ф

B

C

Ф

Ф

D

E

以中序遍历为例来说明中序遍历二叉树的递归过程

第一次经过

第三次经过

第二次经过

slide56

A

B

C

左是空返回

左是空返回

>

右是空返回

T

D

T

左是空返回

B

T

D

右是空返回

T

printf(B);

>

A

T

printf(D);

主程序

pre(T L);

printf(A);

pre(T L);

pre(T R);

pre(T L);

>

pre(T R);

T

Pre( T )

pre(T R);

T

C

>

T

printf(C);

pre(T L);

>

pre(T R);

T

void Preorder (BiTree T,

void( *visit)(TElemType& e))

{ if (T) {

visit(T->data);

Preorder(T->lchild, visit);

Preorder(T->rchild, visit);

}

}

A

B

C

D

返回

返回

返回

返回

先序序列:A B D C

返回

slide57

练习:

1.若二叉树有12个结点而且度为1的个数有5个,试问该树中叶子结点有几个?

2. A Tree is represented as

A(B(CD)E(F(G)H(I(JK)L(MNO))))

Please draw the tree

slide58

3.下面的二叉树中,( )不是完全二叉树。

4.先根序列和中根序列相同的非空二叉树是( )。A.任一结点均无右子树的非空二叉树

B.根结点无右子树的非空二叉树C.任一结点均无左子树的非空二叉树

D.根结点无左子树的非空二叉树

slide59

6.3.2 遍历算法应用

1.输出二叉树中的结点

【算法思想】输出二叉树中的结点并无次序要求,因此可用三种遍历算法中的任何一种完成,只需将访问操作具体变为打印操作即可。下面给出采用前序遍历实现的算法。

【算法描述】

void PreOrder(BiTree root)

/* 先序遍历输出二叉树结点, root为指向二叉树根结点的指针 */

{if (root!=NULL)

{printf (root ->data); /* 输出根结点 */

PreOrder(root ->LChild); /* 先序遍历左子树 */

PreOrder(root ->RChild); /* 先序遍历右子树 */

}

}

slide60

2.输出二叉树中的叶子结点

【算法思想】输出二叉树中的叶子结点与输出二叉树中的结点相比,它是一个有条件的输出问题,即在遍历过程中走到每一个结点时需进行测试,看是否满足叶子结点的条件。

slide61

【算法描述】

void PreOrder(BiTree root)

/* 先序遍历输出二叉树中的叶子结点 , root为指向二叉树根结点的指针 */

{if (root!=NULL)

{if (root ->LChild==NULL && root ->RChild==NULL)

printf (root ->data); /* 输出叶子结点 */

PreOrder(root ->LChild); /* 先序遍历左子树 */

PreOrder(root ->RChild); /* 先序遍历右子树 */

}

}

slide62

3.统计叶子结点数目

方法一:

【算法思想】统计二叉树中的叶子结点数目并无次序要求,因此可用三种遍历算法中的任何一种完成,只需将访问操作具体变为判断是否为叶子结点及统计操作即可。

slide63

【算法描述】

/* LeafCount为保存叶子结点数目的全局变量,调用之前初始化值为0 */

void leaf(BiTree root)

{if(root!=NULL)

{leaf(root->LChild);leaf(root->RChild);

if (root ->LChild==NULL && root ->RChild==NULL)

LeafCount++;

}

}

slide64

3.统计叶子结点数目

方法二:

【算法思想】采用递归算法,如果是空树,返回0;如果只有一个结点,返回1;否则为左右子树的叶子结点数之和。

slide65

【算法描述】

int leaf(BiTree root)

{

int LeafCount;

if(root==NULL)

LeafCount =0;

else if ((root->LChild==NULL)&&(root->RChild==NULL))

LeafCount =1;

else /* 叶子数为左右子树的叶子数目之和 */

LeafCount =leaf(root->LChild)+leaf(root->RChild);

return LeafCount;

}

slide66

4.建立二叉链表方式存储的二叉树

【算法思想】采用类似先序遍历的递归算法,首先读入当前根结点的数据,如果是’.’则将当前树根置为空,否则申请一个新结点,存入当前根结点的数据,分别用当前根结点的左子域和右子域进行递归调用,创建左右子树。

slide67

【算法描述】

void CreateBiTree(BiTree *bt)

{ char ch;

ch=getchar();

if(ch==\'.\') *bt=NULL;

else

{*bt=(BiTree)malloc(sizeof(BiTNode));

(*bt)->data=ch;

CreateBiTree(&((*bt)->LChild));

CreateBiTree(&((*bt)->RChild));

}

}

slide68

5.求二叉树的高度

设函数表示二叉树bt的高度,则递归定义如下:

若bt为空,则高度为0

若bt非空,其高度应为其左右子树高度的最大值加1

bt

High=max(hl+hr)+1

hl

hr

slide69

【算法思想】二叉树bt的高度可以递归定义如下:【算法思想】二叉树bt的高度可以递归定义如下:

l若bt为空,则高度为0

l若bt非空,其高度应为其左右子树高度的最大值加1

slide70

【算法描述】

int PostTreeDepth(BiTree bt) /* 后序遍历求二叉树bt高度的递归算法 */

{ int hl, hr, max;

if(bt!=NULL)

{ hl=PostTreeDepth(bt->LChild); /* 求左子树的深度 */

hr=PostTreeDepth(bt->RChild); /* 求右子树的深度 */

max=hl>hr?hl:hr; /* 得到左、右子树深度较大者*/

return(max+1); /* 返回树的深度 */

}

else return(0); /* 如果是空树,则返回0 */

}

slide71

求二叉树的高度是也可用前序遍历的方式实现。求二叉树的高度是也可用前序遍历的方式实现。

【算法思想】二叉树的高度(深度)为二叉树中结点层次的最大值。设根结点为第1层的结点,所有h层的结点的左右孩子结点在h+1层。故可以通过遍历计算二叉树中的每个结点的层次,其中最大值即为二叉树的高度。

slide72

【算法描述】

void PreTreeDepth(BiTeee bt, int h)

/* 先序遍历求二叉树bt高度的递归算法,h为bt指向结点所在层次,初值为1*/

/*depth为当前求得的最大层次,为全局变量,调用前初值为0 */

{if(bt!=NULL)

{if(h>depth) depth = h; /*如果该结点层次值大于depth,更新depth的值*/

PreTreeDepth(bt->Lchild, h+1); /* 遍历左子树 */

PreTreeDepth(bt->Rchild, h+1); /* 遍历右子树 */

}

}

slide73

C

A

F

输出

B

C

A

E

D

D

E

B

F

6. 按树状打印的二叉树

假设以二叉链表存储的二叉树中,每个结点所含数据元素均为单字母,要求实现如下图的打印结果。

分析:这是二叉树的横向显示问题,横向显示应是竖向显示的900旋转,又由于二叉树的横向显示算法一定是中序遍历算法,所以把横向显示的二叉树算法改为RDL结构,实现算法为:

slide74

【算法思想】

(1)二叉树的横向显示应是二叉树竖向显示的90。旋转。分析图6.15图示可知,这种树形打印格式要求先打印右子树,再打印根,最后打印左子树,按由上而下顺序看,其输出的结点序列为:CFEADB,这恰为逆中序顺序。解决二叉树的横向显示问题采用“逆中序”遍历框架,所以横向显示二叉树算法为先右子树、再根结点、再左子树的RDL结构。

(2)在这种输出格式中,结点的左右位置与结点的层深有关,故算法中设置了一个表示当前根结点层深的参数,以控制输出结点的左右位置,每当递归进层时层深参数加1。这些操作应在访问根结点时实现。

slide75

【算法描述】

void PrintTree(BiTree bt, int nLayer) /* 按竖向树状打印的二叉树 */

{

if(bt = =NULL) return;

PrintTree(bt ->RChild, nLayer+1);

PrintTree(bt ->LChild , nLayer+1);

}

for(int i=0; i<nLayer; i++)

printf(“ ”);

printf(“%c\n”, bt ->data); /*按逆中序输出结点,用层深决定的左右位置*/

slide76

6.3.3 基于栈的递归消除

在大量复杂的情况下,递归的问题无法直接转换成循环,需要采用工作栈消除递归。工作栈提供一种控制结构,当递归算法进层时需要将信息保留;当递归算法出层时需要从栈区退出信息。

1. 中序遍历二叉树的非递归算法

slide77

【算法思想】

从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:

(1)从当前结点开始,进栈并走左子树,直到左子树为空。

(2)退栈并访问。

(3)走右子树。

slide78

【算法描述】(直接实现栈操作)

/*s[m] 表示栈,top表示栈顶指针*/

void inorder(BiTree root) /* 中序遍历二叉树,root为二叉树的根结点 */

{top=0; p=root;

do{while(p!=NULL)

{ if (top>m) return;

top=top+1; s[top]=p; p=p->LChild

}; /* 遍历左子树 */

if(top!=0)

{ p=s[top];

top=top-1;

Visit(p->data); /* 访问根结点 */

p=p->RChild; } /* 遍历右子树 */

}

} while(p!=NULL || top!=0)

}

视频

slide79

【算法思想】(调用栈操作函数)

从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:

(1)如果当前结点存在,则进栈并走左子树。

(2)否则退栈并访问,然后走右子树。

视频

slide80

【算法描述】

void InOrder(BiTree root)/* 中序遍历二叉树的非递归算法 */

{ InitStack (&S);

p=root;

while(p!=NULL || !IsEmpty(S))

{

if (p!=NULL) /* 根指针进栈,遍历左子树 */

{ Push(&S,p);

p=p->LChild;

}

else { /*根指针退栈,访问根结点,遍历右子树*/

Pop(&S,&p); Visit(p->data);

p=p->RChild;

}

}

}

slide81

2.后序遍历二叉树的非递归算法

【算法思想】

从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:

(1)从当前结点开始,反复入栈并左走,直到空的左子树;

(2) 如果栈顶结点的右子为空,或者顶结点的右子为刚访问过的结点,则退栈并访问;

(3) 否则,右走一步 。

slide82

void PostOrder(BiTree root)

{BiTNode * p,*q;BiTree s[Stack_Size];int top=0;q=NULL; p=root;

while(p!=NULL || top!=0)

{while(p!=NULL)

{ top=++;

if(top>=Stack_Size)

OverFlow( ); /*栈溢出处理*/

s[top]=p; p=p->LChild; } /*遍历左子树*/

if(top>0)

{p=s[top];

if((p->RChild==NULL) ||(p->RChild==q))/* 无右孩子,或右孩子已遍历过 */

【算法描述】

{ visit(p->data); /* 访问根结点* /

q=p; /* 保存到q,为下一次已处理结点前驱 */

top--; p=NULL;

}

else p=p->RChild;

}

}

}

slide83

A

B

C

D

E

F

G

H

第六章 习题 (一)

1、给出下列二叉树的前序,中序,后序遍历序列。

slide84

第六章 习题 (一)

  • 算法设计:
  • (1).借用队列,试编写按层次遍历二叉树的算法.
  • (2).试编写将二叉树中的所有结点的左右子树互换的算法.
slide85

6.3.4 线索二叉树

1. 基本概念

二叉树的遍历运算是将二叉树中结点按一定规律线性化的过程。当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在遍历序列中的前驱和后继信息。要得到这些信息,第一种方法是将二叉树遍历一遍,在遍历过程中便可得到结点的前驱和后继,但这种动态访问浪费时间;第二种方法是充分利用二叉链表中的空链域,将遍历过程中结点的前驱、后继信息保存下来。

slide86

在有n个结点的二叉链表中共有2n个链域,但只有n-1个有用非空链域,其余n+1个链域是空的。我们可以利用剩下的n+1个空链域来存放遍历过程中结点的前驱和后继信息。 现作如下规定:

若结点有左子树,则其LChild域指向其左孩子,否则LChild域指向其前驱结点;若结点有右子树,则其RChild域指向其右孩子,否则RChild域指向其后继结点。

slide87

0 LChild域指示结点的左孩子

1 LChild域指示结点的遍历前驱

Ltag=

0 RChild域指示结点的右孩子

1 RChild域指示结点的遍历后继

Rtag=

为了区分孩子结点和前驱、后继结点,为结点结构增设两个标志域,如下图所示:

其中:

slide88

线索:在这种存储结构中,指向前驱和后继结点的指针叫做线索。线索:在这种存储结构中,指向前驱和后继结点的指针叫做线索。

线索链表:以这种结构组成的二叉链表作为二叉树的存储结构,叫做线索链表。

线索化:对二叉树以某种次序进行遍历并且加上线索的过程叫做线索化。

线索二叉树:线索化了的二叉树称为线索二叉树。

lchild ltag 0 rtag 1

0 1

0 -0

0 × 0

1 c 1

c

×

1 b 1

1 a 1

a

b

整体结构

增设一个头结点,令其lchild指向二叉树的根结点,ltag=0、rtag=1;

并将该结点作为遍历访问的第一个结点的前驱和最后一个结点的后继;

最后用头指针指示该头结点。

slide90

A

A

B

C

B

C

D

E

F

D

E

F

G

H

NULL

G

H

(a)二叉树

(b)先序线索二叉树

下面是对同一棵二叉树的遍历方法不同得到的不同线索树。

slide91

A

B

C

D

E

F

G

H

(a)二叉树

A

B

C

D

E

F

G

H

NULL

NULL

(c)中序线索二叉树

slide92

A

B

C

D

E

F

G

H

(a)二叉树

A

B

C

D

E

F

G

H

NULL

(d)后序线索二叉树

slide93

2. 二叉树的线索化

【算法思想】

(1)中序线索化采用中序递归遍历算法框架。

(2)加线索操作就是访问结点操作。

(3)加线索操作需要利用刚访问过结点与当前结点的关系,因此设置一个指针pre,始终记录刚访问过的结点,其操作如下:

①如果当前遍历结点root的左子域为空,则让左子域指向pre ;

②如果前驱pre的右子域为空,则让右子域指向当前遍历结点root;

③为下次做准备,当前访问结点root作为下一个访问结点的前驱pre。

slide94

【算法描述】

void Inthread(BiTree root)

/* 对root所指的二叉树进行中序线索化,其中pre始终指向刚访问过的结点,其初值为NULL* /

{ if (root!=NULL)

{ Inthread(root->LChild); /* 线索化左子树 */

if (root->LChild==NULL)

{root->Ltag=1; root->LChile=pre; } / *置前驱线索 */

if (pre!=NULL&& pre->RChild==NULL) /* 置后继线索 */

{ pre-> RChild=root; pre->Rtag=1; }

pre=root;/ /*当前访问结点为下一个访问结点的前驱*/

Inthread(root->RChild); /*线索化右子树*/

}

}

slide95

3.在线索二叉树中找前驱、后继结点

(1) 找结点的中序前驱结点

【算法描述】

BiTNode * InPre(BiTNode * p)

/* 在中序线索二叉树中查找p的中序前驱, 并用pre指针返回结果 */

{ if(p->Ltag==1) pre= p->LChild; /*直接利用线索*/

else

{ /* 在p的左子树中查找“最右下端”结点 */

for(q= p->LChild;q->Rtag==0;q=q->RChild);

pre=q;

}

return(pre);

}

演示

slide96

(2)在中序线索树中找结点后继

对于结点p,若要找其后继结点,当p->Rtag=1时,p->RChild即为p的后继结点;当p->Rtag=0时,说明p有右子树,此时p的中序后继结点即为其右子树的“最左下端”的结点。其查找算法如下:

【算法描述】

BiTNode * InNext(BiTNode * p)

/*在中序线索二叉树中查找p的中序后继结点,并用Next指针返回结果*/

{ if (p->Rtag==1) Next= p-> RChild; /*直接利用线索*/

else { /*在p的右子树中查找“最左下端”结点*/

for(q= p->RChild; q->Ltag==0 ;q=q->LChild ); /*循环体为空*/

Next=q;

}

return(Next)

}

演示

slide97

4.遍历中序线索树

(1)在中序线索树上求中序遍历的第一个结点

【算法描述】

BiTNode * InFirst(BiTree Bt)

{

BiTNode *p=Bt;

If(!p) return (NULL);

while(p->LTag==0) p=p->Lchild;

return p;

}

slide98

(2)遍历中序二叉线索树

【算法描述】

void TInOrder(BiTree Bt)

{

BITNode *p;

P=InFirst(Bt);

While(p)

{

Visit(p);

p = InNext(p);

}

}

slide99

5. 线索二叉树的插入、删除运算

二叉树加上线索之后,当插入或删除一结点时,可能会破坏原树的线索。所以在线索二叉树中插入或删除结点的难点在于:插入一个结点后,仍要保持正确的线索。

我们主要以中序线索二叉树为例,说明线索二叉树的插入和删除运算。

slide100

(1)插入结点运算

在中序线索二叉树中插入结点可分为两种情况:

第一种:将新的结点插入到二叉树中,作某结点的左孩子;

第二种:将新的结点插入到二叉树中,作某结点的右孩子。

我们仅讨论后一种情况。

InsNode(BiTNode * p, BiTNode * r)表示在线索二叉树中插入r所指向的结点,做p所指结点的右孩子。

slide101

p

p

r

r

①若结点p的右孩子为空,则插入结点r的过程很简单。原来p的后继变为r的后继,结点p变为r的前驱,结点r成为p的右孩子。结点r的插入对p原来的后继结点没有任何的影响。

结点的右孩子为空时的插入过程为:

插入前

插入后

slide102

p

r

p

p

r

r

②若p的右孩子不为空,则插入后,p的右孩子变为r的右孩子结点,p变为r的前驱结点,r变为p的右孩子结点。这时还需要修改原来p的右子树中“最左下端”结点的左指针域,使它由原来的指向结点p变为指向结点r。插入过程为:

插入后

插入前

slide103

【算法描述】

void InsNode(BiTNode * p , BiTNode * r)

{ if (p->Rtag==1) /* p无右孩子 */

{ r->RChild=p->RChild; /* p的后继变为r的后继 */

r->Rtag=1;

p->RChild=r; /* r成为p的右孩子 */

r->LChild=p; /* p变为r的前驱 */

r->Ltag=1;

}

else /* p有右孩子 */

{ s=p->RChild;

while(s->Ltag==0)

s=s->LChild; /* 查找p结点的右子树的“最左下端”结点 */

r->RChild=p->RChild; /* p的右孩子变为r的右孩子 */

r->Rtag=0;

r->LChild=p; /* p变为r的前驱 */

r->Ltag=1;

p->RChild=r; /* r变为p的右孩子 */

s->LChild=r; /* r变为p原来右子树的“最左下端”结点的前驱 */

}

}

slide104

r

p

p

r

(b) 删除后

(a) )删除前

(2)删除结点运算

与插入操作一样,在线索二叉树中删除一个结点也会破坏原来的线索,所以需要在删除的过程中保持二叉树的线索化。

在中序线索二叉树中删除结点r的过程为:

slide105

6.3.5 由遍历序列确定二叉树

由定义,二叉树的前序遍历是先访问根结点D,其次遍历左子树L,最后遍历右子树R。即在结点的前序序列中,第一个结点必是根D;而另一方面,由于中序遍历是先遍历左子树L,然后访问根D,最后遍历右子树R,则根结点D将中序序列分割成两部分:在D之前是左子树结点的中序序列,在D之后是右子树结点的中序序列。反过来,根据左子树的中序序列中结点个数,又可将前序序列除根以外分成左子树的前序序列和右子树的前序序列两个部分。依次类推,便可递归得到整棵二叉树 。

slide106

例:已知结点的前序序列和中序序列分别为:

前序序列:18 14 7 3 11 22 35 27

中序序列:3 7 11 14 18 22 27 35

则可按上述分解求得整棵二叉树。

首先由前序序列得知二叉树的根为18,则其左子树的中序序列为(3,7,11,14),右子树的中序序列为(22,27,35)。反过来得知其左子树的前序序列必为(14,7,3,11),右子树的前序序列为(22 35 27)。类似地,可由左子树的前序序列和中序序列构造得18的左子树,由右子树的前序序列和中序序列构造得18的右子树 。

slide107

18

22

14

35

7

27

11

3

结论:给定一棵二叉树的前序序列和中序序列,可唯一确定一棵二叉树。

slide108

例一:已知某二叉树的前序序列为ACIKNHLM,中序序列为ICNKALMH,请画出该二叉树并给出其后序序列;例一:已知某二叉树的前序序列为ACIKNHLM,中序序列为ICNKALMH,请画出该二叉树并给出其后序序列;

具体方法……(由前序遍历确定根结点,由中序遍历确定其左,右子树。)

slide109

A

A

C

ICNK

LMH

H

I

C

K

L

H

N

I

NK

M

LM

L

K

N

M

前序序列为ACIKNHLM,中序序列为ICNKALMH

slide110

例二:已知中序:DCBGEAHFIJK

后序:DCEGBFHKJIA

画出该二叉树

请问:只要知道了二叉树的两种遍历序列就能唯一确定一棵二叉树吗?

总结

已知二叉树的中序遍历和后序遍历序列或已知其中序遍历和前序遍历序列,可以唯一确定一棵二叉树。

slide111

1、该二叉树结点的前序遍历的序列为( )。

A. E、G、F、A、C、D、B

B. E、A、G、C、F、B、D

C. E、A、C、B、D、G、F

D. E、G、A、C、D、F、B

2、该二叉树结点的中序遍历的序列为( )。

A. A、B、C、D、E、G、F

B. E、A、G、C、F、B、D

C. E、A、C、B、D、G、F

D. D、C、A、F、G、E

slide112

3、该二叉树的按层遍历的序列为( )。

A. E、G、F、A、C、D、B

B. E、A、C、B、D、G、F

C. E、A、G、C、F、B、D

D. E、G、A、C、D、F、B

4、后缀算式79 2 30 + - 4 2 / *的值为__________。中缀算式(3+X*Y)-2Y/3对应的后缀算式为_______________________________。

slide113

练习一:The following traversals unambiguously define a binary tree. Please draw the tree.

Postorder: CBEHGIFDA

Inorder: BCAEDGHFI

slide114

bn= C

1

n

n+1

2n

  • 练习二:n个结点可以组成多少棵不同的二叉树?
  • 公式:
  • 练习三:Find all binay tree whose nodes appear
  • in exactly all the same sequence in both
  • Preorder and inorder

二叉树是空的(empty)

二叉树只有一个根结点(root)

二叉树是右斜二叉树(right-skewed binary tree)

slide115

b.Preorder and postorder

二叉树是空的(empty)

二叉树只有一个根结点(root)

c.Inorder and postorder

二叉树是空的(empty)

二叉树只有一个根结点(root)

二叉树是左斜二叉树(left-skewed binary tree)

slide116

图2

第六章 习题 二

1、试对图2中的二叉树画出其:中序线索二叉树

slide117

2、已知某二叉树的先序序列为EBADCFHGIKJ,中序序列为ABCDEFGHIJK,试画出该二叉树的一般形式。2、已知某二叉树的先序序列为EBADCFHGIKJ,中序序列为ABCDEFGHIJK,试画出该二叉树的一般形式。

返回

slide118

6.4 树、森林和二叉树的关系

6.4.1 树的存储结构

6.4.2 树、森林与二叉树的相互转换

6.4.3 树与森林遍历

slide119

6.4.1 树的存储结构

树的主要存储方法有:

1.双亲表示法

2.孩子表示法

3.孩子兄弟表示法

slide120

结点序号

Data

Parent

1

1

-1

0

2

0

1

2

3

3

0

2

4

1

3

4

5

1

5

6

7

4

6

1

5

1. 双亲表示法:用一组连续的空间来存储树中的结点,在保存每个结点的同时附设一个指示器来指示其双亲结点在表中的位置,其结点的结构如下:

树的双亲表示法如下图:

slide121

双亲表示法的优点:

利用了树中每个结点(根结点除外)只有一个双亲结点的性质,使得查找某个结点的双亲结点非常容易。

双亲表示法的缺点:

在求某个结点的孩子时,需要遍历整个向量。

slide122

双亲表示法的存储结构

一棵树可以定义为:

#define MAX 100

typedef struct TNode

{

DataType data;

int parent;

}TNode;

typedef struct

{

TNode tree[MAX];

int nodenum; /*结点数*/

}ParentTree;

slide123

2. 孩子表示法:通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n个结点共有n个孩子链表(叶结点的孩子链表为空表),而n个结点的数据和n个孩子链表的头指针又组成一个顺序表。

r

slide124

孩子表示法的存储结构:

typedef struct ChildNode /* 孩子链表结点的定义 */

{ int Child; /* 该孩子结点在线性表中的位置 */

struct ChildNode * next; /*指向下一个孩子结点的指针 */

}ChildNode;

typedef struct /* 顺序表结点的结构定义 */

{ DataType data; /* 结点的信息 */

ChildNode * FirstChild ; /* 指向孩子链表的头指针 */

}DataNode;

typedef struct /* 树的定义 */

{

DataNode nodes[MAX]; /* 顺序表 */

int root,num;

/* 该树的根结点在线性表中的位置和该树的结点个数 */

} ChildTree;

slide125

3. 孩子兄弟表示法(二叉链表表示法):链表中每个结点设有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟(右兄弟)结点。

slide126

孩子兄弟表示法的存储结构:

typedef struct CSNode

{

DataType data; /*结点信息*/

Struct CSNode *FirstChild, *Nextsibling;

/*第一个孩子,下一个兄弟*/

}CSNode, *CSTree;

优点:便于实现树的各种操作。

slide127

A

B

C

D

E

F

G

H

6.4.2 树、森林与二叉树的相互转换

1. 树转换为二叉树

我们约定树中每一个结点的孩子结点按从左到右的次序顺序编号,也就是说,把树作为有序树看待。

例如:右图的树

slide128

H

B

A

F

H

E

G

C

B

D

A

F

D

E

G

C

B

D

A

F

H

E

G

C

将一棵树转换为二叉树的方法:

⑴ 树中所有相邻兄弟之间加一条连线。

⑵ 对树中的每个结点,只保留其与第一个孩子结点之间的连线,删去其与其它孩子结点之间的连线。

⑶以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。

slide129

结论:从转换过程可看出:树中的任意一个结点都对应于二叉树中的一个结点。树中某结点的第一个孩子在二叉树中是相应结点的左孩子,树中某结点的右兄弟结点在二叉树中是相应结点的右孩子。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必然为空。结论:从转换过程可看出:树中的任意一个结点都对应于二叉树中的一个结点。树中某结点的第一个孩子在二叉树中是相应结点的左孩子,树中某结点的右兄弟结点在二叉树中是相应结点的右孩子。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必然为空。

slide130

2. 森林转换为二叉树

森林转换为二叉树的方法为:

(1)将森林中的每棵树转换成相应的二叉树。

(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。

slide131

A

(b) 森林中每棵树对应的二叉树

G

H

J

I

D

F

A

A

B

E

B

C

D

E

F

G

H

I

J

C

B

E

F

C

G

D

H

I

J

森林转换为二叉树的过程

(a) 森林

(c)森林对应的二叉树

slide132

用递归的方法描述上述转换过程:

将森林F看作树的有序集F={T1,T2,…,TN},它对应的二叉树为B(F):则有:

(1)若N=0,则B(F)为空。

(2)若N>0,二叉树B(F)的根为森林中第一棵树T1的根;B(F)的左子树为B({T11,…,T1m}),其中{T11,…,T1m}是T1的子树森林;B(F)的右子树是B({T2,…,TN})。。

slide133

BT2

E

F

如图: F={T1 ,T2, T3}

G

T2

T3

T1

A

E

H

I

F

C

D

B

J

A

E

B

BT3

BT1

G

G

A

F

C

H

H

B

I

I

C

D

J

D

J

slide134

A

B

E

C

F

G

D

H

I

J

3. 二叉树还原为树或森林

方法:根及左子树转换成第一棵树,其余部分依次类推……

提问?

该二叉树是由几棵树转换而来的?

具体分析……

slide135

D

F

D

A

B

C

E

F

G

H

J

D

B

I

A

A

C

E

I

H

J

F

E

C

B

G

G

H

I

J

一棵二叉树还原为树或森林,具体方法为:

(1)若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子、……都与该结点的双亲结点用线连起来。

(2)删掉原二叉树中所有双亲结点与右孩子结点的连线。

(3)整理由(1)、(2)两步所得到的树或森林,使之结构层次分明。

演示

slide136

用递归的方法描述其转换过程为:

若B是一棵二叉树,T是B的根结点,L是B的左子树,R为B的右子树,且B对应的森林F(B)中含有的n棵树为T1,T2, …,Tn,则有:

(1)B为空,则F(B)为空的森林(n=0)。

(2)B非空,则F(B)中第一棵树T1的根为二叉树B的根T;T1中根结点的子树森林由B的左子树L转换而成,即F(L)={T11,…,T1m};B的右子树R转换为F(B)中其余树组成的森林,即F(R)={ T2, T3, …,Tn}。

slide137

1

2

3

4

5

6

7

8

第六章 习题 三

2、画出和下列树相对应的二叉树。

slide138

1

9

10

2

12

5

13

6

7

14

15

8

3、画出和下列森林相对应的二叉树。

11

3

4

T1 T2 T3 T4

slide139

A

B

C

D

E

F

G

H

6.4.3 树与森林的遍历

1. 树的遍历

树的遍历方法主要有以下两种:

(1)先根遍历

若树非空,则遍历方法为:

①访问根结点。

②从左到右,依次先根遍历根结点的每一棵子树。

如图中树的先根遍历序列为:ABECFHGD。

slide140

(2)后根遍历

若树非空,则遍历方法为:

①从左到右,依次后根遍历根结点的每一棵子树。

②访问根结点。

如图中树的后根遍历序列为:EBHFGCDA。

树的遍历结果与由树转化成的二叉树有如下对应关系:

树的先根遍历 转化二叉树的前序遍历

树的后根遍历转化二叉树的中序遍历

slide141

2.树的遍历算法实现

方法一

void RootFirst(CSTree root)

{

if (root!=NULL)

{ Visit(root ->data); /* 访问根结点 */

p= root-> FirstChild;

while (p!=NULL)

{ RootFirst( p ); /* 访问以p为根的子树 */

p = p -> Nextsibling;

}

}

}

slide142

方法二

void RootFirst(CSTree root)

{

if (root!=NULL)

{

Visit (root ->data); /*访问根结点*/

RootFirst (root->FirstChild); /*先根遍历首子树*/

RootFirst (root->Nextsibling); /*先根遍历兄弟树*/

}

}

slide143

3. 森林的遍历

森林的遍历方法主要有以下三种:

(1)先序遍历

若森林非空,则遍历方法为:

①访问森林中第一棵树的根结点。

②先序遍历第一棵树的根结点的子树森林。

③先序遍历除去第一棵树之后剩余的树构成的森林。

slide144

(2)中序遍历

若森林非空,则遍历方法为:

①中序遍历森林中第一棵树的根结点的子树森林。

②访问第一棵树的根结点。

③中序遍历除去第一棵树之后剩余的树构成的森林。

slide145

(3)后序遍历

若森林非空,则遍历方法为:

①后序遍历森林中第一棵树的根结点的子树森林。

②后序遍历除去第一棵树之后剩余的树构成的森林。

③访问第一棵树的根结点。

返回

slide146

6.5 哈夫曼树及其应用

6.5.1 哈夫曼树

6.5.2 哈夫曼编码

slide147

1. 哈夫曼树的基本概念:

路径:指从一个结点到另一个结点之间的分支序列。

路径长度:指从一个结点到另一个结点所经过的分支数目。

结点的权:给树的每个结点赋予一个具有某种实际意义的实数,我们称该实数为这个结点的权。

带权路径长度:在树形结构中,我们把从树根到某一结点的路径长度与该结点的权的乘积,叫做该结点的带权路径长度。

slide148

n

WPL=wi×li

i=1

树的带权路径长度:为树中所有叶子结点的带权路径长度之和,通常记为:

其中n为叶子结点的个数, wi为第i个叶子结点的权值,li为第i个叶子结点的路径长度。

slide149

(b)权为46

A

D

C

D

B

B

A

C

7

C

B

D

A

5

4

7

2

5

2

2

4

4

7

5

(c)权为35

(a)权为36

例如下图所示的具有不同带权路径长度的二叉树

WPL(a)=7×2+5×2+2×2+4×2=36

WPL(b)=4×2+7×3+5×3+2×1=46

WPL(c)=7×1+5×2+2×3+4×3=35

slide150

问题:什么样的带权树路径长度最小?

例如:给定一个权值序列{2,3,4,7},可构造的多种二叉树的形态。

2 3 4 7

(a) WPL=2×2+2×3+2×4+2×7=32

slide151

2

3

7

4

(b) WPL=1×2+2×3+3×4+3×7=41

slide152

7

4

2

3

(c) WPL=1×7+2×4+3×3+3×2=30

slide153

由于哈夫曼给出了构造这种树的规律,将给定结点构成一棵带权路径长度最小的二叉树,因此就称为哈夫曼树。由于哈夫曼给出了构造这种树的规律,将给定结点构成一棵带权路径长度最小的二叉树,因此就称为哈夫曼树。

2. 构造哈夫曼树

哈夫曼树又叫最优二叉树,它是由n个带权叶子结点构成的所有二叉树中带权路径长度WPL最短的二叉树。

构造哈夫曼算法的步骤如下:

slide154

(1)用给定的n个权值{w1,w2, … ,wn}对应的n个结点构成n棵二叉树的森林F={T1,T2, …,Tn},其中每一棵二叉树Ti (1≤i≤n)都只有一个权值为wi的根结点,其左、右子树为空。

(2)在森林F中选择两棵根结点权值最小的二叉树,作为一棵新二叉树的左、右子树,标记新二叉树的根结点权值为其左右子树的根结点权值之和。

(3)从F中删除被选中的那两棵二叉树,同时把新构成的二叉树加入到森林F中。

(4)重复(2)、(3)操作,直到森林中只含有一棵二叉树为止,此时得到的这棵二叉树就是哈夫曼树。

slide155

直观地看,在哈夫曼树中权越大的叶子离根越近,则其具有最小带权路径长度。直观地看,在哈夫曼树中权越大的叶子离根越近,则其具有最小带权路径长度。

哈夫曼树的手工构造的方法也非常简单:

给定数列{W1....Wn},以n个权值构成n棵树的森林F;将F={T1....Tn}按权从小到大排列;取T1和T2合并组成一棵树,使其根结点的权值T=T1+T2,再按大小插入F,反复此过程直到只有一棵树为止。

slide156

权值

双亲序号

左孩子序号

右孩子序号

3.哈夫曼树的类型定义

(1)存储结构

哈夫曼树是一种二叉树,由于哈夫曼树中没有度为1的结点,则一棵有n个叶子的哈夫曼树共有2×n-1个结点,可以用一个大小为2×n-1 的一维数组存放哈夫曼树的各个结点。由于每个结点同时还包含其双亲信息和孩子结点的信息,所以构成一个静态三叉链表。

静态三叉链表中:每个结点的结构为:

各结点存储在一维数组中,0号单元不使用,从1号位置开始使用。

slide157

data

parent

LChild

RChild

1

A

0

2

3

A

2

B

1

0

0

B

C

3

C

1

4

5

D

E

4

D

3

0

0

5

E

3

0

0

slide158

(2)哈夫曼树的类型定义

用静态三叉链表实现的哈夫曼树类型定义如下:

#define N 20 /* 叶子结点的最大值。*/

#define M 2*N-1 /* 所有结点的最大值*/

typedef struct

{

int weight ; /* 结点的权值*/

int parent ; /* 双亲的下标*/

int LChild ; /* 左孩子结点的下标*/

int RChild ; /* 右孩子结点的下标*/

} HTNode, HuffmanTree[M+1]; /* HuffmanTree是一个结构数组类型,0号单元不用。 */

slide159

4. 哈夫曼树的算法实现

【算法描述】

void CrtHuffmanTree(HuffmanTree ht, int w[ ], int n)

{ /*构造哈夫曼树ht[M+1],w[ ]存放n个权值。*/

for(i=1;i<=n;i++) ht[i] ={ w[i],0,0,0}; /* 1 ~ n号单元存放叶子结点,初始化*/

m=2*n-1;

for(i=n+1;i<=m;i++) ht[i] ={0,0,0,0};

/*————————————初始化完毕!对应算法步骤1—————————*/

for(i=n+1; i<=m; i++) /*创建非叶结点,建哈夫曼树*/

{

select(ht, i-1, s1, s2); /* 在 ht[1] ~ ht[i-1] 的范围内选择两个parent为0且

weight最小的结点,其序号分别赋值给s1、s2返回 */

ht [i].weight= ht [s1].weight+ ht [s2].weight;

ht [s1].parent=i;ht [s2].parent=i;

ht [i].LChild=s1;ht [i].RChild=s2;

} /*哈夫曼树建立完毕*/

}

slide160

0

1

例:传送数据中的二进制编码。要传送数据state,seat,act,tea,cat,set,a,eat,如何使传送的长度最短?

字符 s t a e c

字符出现的次数3 8 7 5 2

为了保证长度最短,先计算各个字符出现的次数,然后将出现次数当作权。

按权构造哈夫曼树的过程如下图:

slide161

按照创建哈夫曼树的算法,对上图建立哈夫曼树的结果如下表:按照创建哈夫曼树的算法,对上图建立哈夫曼树的结果如下表:

6 5 2

二、哈夫曼编码( Huffman 树的应用)

1、问题的提出

通讯中常需要将文字转换成二进制字符串电文进行传送。文字 电文,称为编码。

反之,收到电文后要将电文转换成原来的文字,

电文 文字,称为译码。

6.5.2 哈夫曼编码

slide163

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

编码方案1:

A B C D

00 01 10 11

则上述文字的电文为:00010010101100 共14位。

译码时,只需每2位一译即可。

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

slide164

编码方案2:

A B C D

0 00 1 01

采用不等长编码,让出现次数多的字符用短码。

则上述文字的电文为:000011010 共9位。

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

slide165

编码方案3:

A B C D

0 110 10 111

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

则上述文字的电文为:0110010101110 共13位。

6 5 21

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

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

则整个电文总长度为∑ Wi Li,

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

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

用构造Huffman树编出来的码,称为Huffman编码。

6.5.2 哈夫曼编码

slide167

(1)前缀码:如果在一个编码系统中,任一个编码都不是其他任何编码的前缀(最左子串),则称该编码系统中的编码是前缀码。例如,一组编码01,001,010,100,110就不是前缀码,因为01是010的前缀,若去掉01或010就是前缀码。例如,名字中的郑霞、郑霞锦就不是前缀码。(1)前缀码:如果在一个编码系统中,任一个编码都不是其他任何编码的前缀(最左子串),则称该编码系统中的编码是前缀码。例如,一组编码01,001,010,100,110就不是前缀码,因为01是010的前缀,若去掉01或010就是前缀码。例如,名字中的郑霞、郑霞锦就不是前缀码。

(2)哈夫曼编码:对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的通路上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。

slide168

2.哈夫曼编码的作用

哈夫曼树被广泛的应用,最典型的就是在编码技术上的应用。利用哈夫曼树,我们可以得到平均长度最短的编码。

例: 设有一台模型机,共有7种不同的指令,其使用频率如下表所示。

指令的使用频率

指令的变长编码

slide169

1.00

0.06

0.60

0.30

0.15

0.09

0.40

0.15

0.03

0.05

0.03

0.04

0.30

哈夫曼编码的平均码长为:

n

I4

I7

I2

I6

I1

I3

I5

 pi×li

i=1

利用哈夫曼算法,可以使我们设计出最优的前缀编码。首先,我们以每条指令的使用频率为权值构造哈夫曼树

对于该哈夫曼树,我们规定向左的分支标记为0,向右的分支标记为1。

指令的哈夫曼编码

slide170

3. 哈夫曼编码算法的实现

typedef char * HuffmanCode[N+1]; /* 存储哈夫曼编码串的头指针数组 */

由于每个哈夫曼编码是变长编码,因此使用指针数组存放每个编码串的头指针。

slide171

【算法描述】

void CrtHuffmanCode(HuffmanTree ht, HuffmanCode hc, int n)

/*从叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码*/

{ char *cd;

hc=(HuffmanCode *)malloc((n+1)*sizeof(char *));

/*分配n个编码的头指针*/

cd=(char * )malloc(n * sizeof(char ));

/*分配求当前编码的工作空间*/

cd[n-1]=’\0’; /*从右向左逐位存放编码,首先存放编码结束符*/

for(i=1;i<=n;i++) /*求n个叶子结点对应的哈夫曼编码*/

{

start=n-1; /*初始化编码起始指针*/

c=i; p=ht[i].parent; /* 从叶子结点开始向上倒推 */

slide172

while(p!=0)

{ --start;

if(ht[p].LChild==c) cd[start]=’0’; /*左分支标0*/

else cd[start]=’1’; /*右分支标1*/

c=p; p=ht[p].parent; /* 向上倒推 */

}

hc [i]=(char *)malloc((n-start)*sizeof(char));

/*为第i个编码分配空间*/

strcpy(hc[i], &cd[start]); /*把编码复制到hc[i]中*/

}

free(cd);

}

返回

slide173

第六章 总结

  • *熟练掌握二叉树的结构特性;
  • *熟悉二叉树的各种存储结构的特点和适用范围;
  • *遍历二叉树是二叉树各种操作的基础(前,中,后);
  • *理解二叉树线索化的实质是建立结点与其在相应序列中的前驱,或后继之间的直接联系;
  • *熟悉树的各种存储结构及其特点,掌握树和森林与二叉树的转换方法;
  • *了解最优二叉树的特性,掌握建立最优树和哈夫曼编码的方法。
slide174

A

B

C

D

E

F

G

H

I

J

K

L

M

第六章 复习

一、主要知识点及算法回顾

1、树的定义及基本术语(结点,叶子结点,孩子结点,双亲结点,兄弟结点,度,树的度,树的深度……)

slide175

1

2

3

4

5

15

8

9

10

11

2、二叉树的定义及性质

性质1:在二叉树的第i层上至多有2i-1个结点(i≥1)。

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

性质3:对任意一棵二叉树T,若终端结点数为n0,而其度数为2的结点数为n2,则n0= n2+1 。

slide176

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

满二数和完全二叉树

3、二叉树的存储结构

二叉链表,三叉链表

slide177

4、二叉树的遍历(前序,中序,后序)

能给出相应二叉树的三种遍历序列

二叉树三种遍历的算法描述

二叉树遍历算法的应用

(统计叶子结点个数,建立二叉链表……)

二叉树遍历的非递归算法描述(中序,后序)

5、二叉树的线索化

画出相应二叉树的线索二叉树(前序,中序,后序)

基于线索二叉数的相关操作

(查找前驱、后继结点的算法;线索二叉树的插入算法)

slide178

6、由遍历序列确定二叉树

7、树,森林和二叉树之间的相互转换

8、哈夫曼树的构造方法及算法实现

9、哈夫曼编码的实现及算法描述

二、难点

1、遍历递归算法的执行过程

2、遍历的非递归实现

3、线索二叉树中结点的插入算法

4、哈夫曼编码的算法描述

slide179

例3 层次遍历二叉树

层次遍历是指从二叉树的第一层(根结点)开始,从上至下逐层遍历,在同一层中,则按照从左到右的顺序对结点逐个访问。以此类推,直到二叉树中所有结点均被访问且仅访问一次。

【问题分析】实现层次遍历,需要设置一个队列Q,暂存某层已访问过的结点,同时也保存了该层结点访问的先后次序。按照对该层结点访问的先后次序实现对其下层孩子结点的按次序访问。

slide180

【算法思想】

(1)初始化空队列Q;

(2)若二叉树bt为空树,则直接返回;

(3)将二叉树的根结点指针bt放入队列Q;

(4)若队列非空,则重复如下操作:

①队头元素出队并访问该元素;

②若该结点的左孩子非空,则将该结点的左孩子结点指针入队;

③若该结点的右孩子非空,则将该结点的右孩子结点指针入队;

slide181

int LayerOrder(BiTree bt)

/*层次遍历二叉树bt*/

{ SeqQueue * Q;

BiTree p;

InitQueue(Q); /*初始化空队列Q*/

if(bt= =NULL) return ERROR; /*若二叉树bt为空树,则结束遍历*/

EnterQueue(Q, bt); /*若二叉树bt非空,则根结点bt入队,开始层次遍历*/

while (!IsEmpty(Q)) /*若队列非空,则遍历为结束,继续进行*/

{

DeleteQueue(Q, &p); /*队头元素出队并访问 */

visit(p->data);

if(p-> LChild ) EnterQueue(Q,p-> LChild) ; /*若p的左孩子非空,则进队*/

if(p-> RChild ) EnterQueue(Q,p-> RChild) ; /*若p的右孩子非空,则进队*/

} /*while*/

return OK;

} /* LayerOrder */

【算法描述】

slide182

例题3分析:层次遍历二叉树

int LayerOrder(BiTree bt)

{Sequeue *Q; BiTree p; InitQueue(Q);

if (bt==NULL) return ERROR;

EnterQueue(Q,bt);

while (!IsEmpty(Q))

{ DeleteQueue(Q,&p);visit(p->Lchild)

if (p->Lchild) EnterQueue(Q,p->Lchild);

if (p->Rchild) EnterQueue(Q,p->Rchild);

}

return OK;

}

slide183

A

B

D

C

E

F

G

H

J

I

例:

1、试分别画出具有3个结点的树和3个结点的二叉树的所有不同形态;

2、画出和下列树对应的二叉树,再画出其中序线索二叉树

slide184

3、设一棵完全二叉树具有1000个结点,问该完全二叉树有多少个叶子结点?有多少个度为2的结点?有多少个度为1的结点?若完全二叉树共有1001个结点,再回答上述问题,并说明理由。3、设一棵完全二叉树具有1000个结点,问该完全二叉树有多少个叶子结点?有多少个度为2的结点?有多少个度为1的结点?若完全二叉树共有1001个结点,再回答上述问题,并说明理由。

分析题3:在一般二叉树中,叶子结点总是比度为2的结点多1个;而在完全二叉树中,度为1的结点最多有1个;综合以上两点,如果完全二叉树的结点数为奇数,设为2n+1,则没有度为1的结点,在所有结点中有n个度为2的结点,n+1个叶子结点;如果完全二叉树的结点树为偶数,设为2n,则其中有1个度为1的结点,剩下的结点中有n-1个度为2的结点,n个叶子结点……

slide185

所以在本题中,如果完全二叉树共有1000个结点,则有500个叶子结点,其中有499个度为2的结点,所以在本题中,如果完全二叉树共有1000个结点,则有500个叶子结点,其中有499个度为2的结点,

有1个度为1的结点。如果完全二叉树共有1001个结点,则有501个叶子结点,500个度为2的结点,没有度为1的结点。

slide186

1、设森林F中有三棵树,第一、第二和第三棵树的结点个数分别为M1、M2和M3。与森林F对应的二叉树根结点的右子树上的结点个数是:1、设森林F中有三棵树,第一、第二和第三棵树的结点个数分别为M1、M2和M3。与森林F对应的二叉树根结点的右子树上的结点个数是:

A)M1 B)M1+M2 C)M3 D)M2+M3

已知叶子结点值2,3,5,6,9,11,构造哈夫曼树,计算其带权路径长度。

slide188

判断正误:正确在( )内打√,否则打 。

( )(1)由树的中序表示和前序表示可以导出树的后序表示。

( )(2)将一棵树转换为二叉树表示后,该二叉树的根结点没有右子树。

( )(3)采用二叉树来表示树时,树的先根次序遍历结果与其对应的二叉树的前序遍历结果是一样的。

( )(4)在Huffman树中,权值较大的叶子结点离根较远。

( )(5)用一维数组存储二叉树时,是以先根遍历的次序存储结点。

slide189

1

9

11

16

2

17

18

10

12

19

3

4

5

13

14

6

7

15

8

T1 T2 T3 T4

第六章 习题 四

1、将下列森林转换为相应的二叉树,在进行中叙线索化。

slide190

2、设有7个带权结点,a,b,c,d,e,f,g其权值分别为2,7,8,12,5,15,4,试以这7个带权结点作为叶子结带,构造一棵二叉树,要求其左子树根结点的权值小于其右子树根结点的权值,再计算该最优二叉树的WPL值。2、设有7个带权结点,a,b,c,d,e,f,g其权值分别为2,7,8,12,5,15,4,试以这7个带权结点作为叶子结带,构造一棵二叉树,要求其左子树根结点的权值小于其右子树根结点的权值,再计算该最优二叉树的WPL值。