第 6 章 树

62
2022年6年9年 年年年 1 第第第 年6年 6.1 第第第第第第第 6.2 第第第第第第第第第 6.3 第第第第第第第第 6.4 第第第第第第 6.5 第第第第第 6.6 第第第第第第 6.7 第第第第第第第第第 6.8 第第第第第第第第

description

第 6 章 树. 6.1 树的概念与表示 6.2 二叉树的概念与性质 6.3 二叉树的存储结构 6.4 二叉树的遍历 6.5 线索二叉树 6.6 树的存储结构 6.7 树、森林与二叉树的转换 6.8 哈夫曼树及其应用 小 结. 本章学习目标 树型结构是一类重要的非线性结构,在计算机领域有着广泛的应用,如数据库系统中的信息组织等。. 通过本章学习,应掌握如下内容: 树的定义及相关术语。 二叉树的性质、存储结构及其遍历算法。 树的存储结构,树和森林与二叉树的转换。 哈夫曼算法及应用。. - PowerPoint PPT Presentation

Transcript of 第 6 章 树

Page 1: 第 6 章      树

2023年4月21日 星期五

1

第六章 树

第 6 章 树 6.1 树的概念与表示 6.2 二叉树的概念与性质 6.3 二叉树的存储结构 6.4 二叉树的遍历 6.5 线索二叉树 6.6 树的存储结构 6.7 树、森林与二叉树的转换 6.8 哈夫曼树及其应用 小 结

Page 2: 第 6 章      树

2023年4月21日 星期五

2

第六章 树

本章学习目标树型结构是一类重要的非线性结构,在计算机领域有着广泛的应用,如数据库系统中的信息组织等。

通过本章学习,应掌握如下内容:       树的定义及相关术语。         二叉树的性质、存储结构及其遍历算法。        树的存储结构,树和森林与二叉树的转换。        哈夫曼算法及应用。

Page 3: 第 6 章      树

2023年4月21日 星期五

3

第六章 树6.1 树的概念与表示

6.1.1 树的定义 树( Tree )是 n 个( n≥0 )个结点组成的有限集合 T ,当 T 为空集时称为空树,否则它满足如下两个条件: (1) 有且仅有一个特定的称为根( Root )的结点。

(2) 除根结点外的其余结点可分为 m ( m>0 )个互不相交的子集 T1 , T2 ,…, Tm ,其中每个子集 Ti

本身又是一棵树,并称其为根的子树。

A

B

F

D

E G I

C

H

K

A

树 非树

Page 4: 第 6 章      树

2023年4月21日 星期五

4

第六章 树

树具有下面两个特点: ( 1 )树的根结点没有前驱结点,除根结点之外的所有结点有且只有一个前驱结点。 ( 2)树中所有结点可以有零个或多个后继结点。

WEB HEL FONT

SYS

DOS USER WIN

C:\

TEM COM

TEMP

Page 5: 第 6 章      树

2023年4月21日 星期五

5

第六章 树6.1.2 树的相关术语 结点:一个数据元素及若干个指向其子树的分支。结点的度:结点所拥有的子树的个数称为该结点的度。树的度:树中结点度的最大值。

A

B

F

K

D

E G I

C

H

LJ

叶子结点:度为 0的结点称为叶子结点,或者称为终端结点。分支结点:度不为 0 的结点称为分支结点或非终端结点。

孩子、双亲和兄弟结点:树中结点子树的根称为该结点的孩子。相应地,该结点称为孩子结点的双亲。

Page 6: 第 6 章      树

2023年4月21日 星期五

6

第六章 树

A

B

F

K

D

E G I

C

H

LJ

具有同一个双亲的孩子结点互称为兄弟。祖先、子孙:从树中根结点到某一结点所经过的分支上的所有结点称为该结点的祖先。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。 结点的层数:规定树的根结点的层数为 1 ,其余结点的层数等于它的双亲结点的层数加 1 。树的深度:树中所有结点的最大层数称为树的深度或高度。

有序树和无序树:如果一棵树中结点的各子树从左到右是有次序的——有序树;反之,——无序树。

森林: m ( m≥0 )棵互不相交的树的集合称为森林。

Page 7: 第 6 章      树

2023年4月21日 星期五

7

第六章 树 6.1.3 树的表示 树的表示有四种方法,直观表示法、嵌套集合表示法、凹入表示法、广义表表示法。

A

B C

ED

FH

G

I

A B D E H F C G I

(A(B(D,E(H),F),C(G,I))) 广义表表示法

A

B

F

K

D

E G I

C

H

LJ

Page 8: 第 6 章      树

2023年4月21日 星期五

8

第六章 树

6.2 二叉树的概念与性质 6.2.1 二叉树的基本概念 定义:二叉树是 n个( n≥0)结点的集合,该集合或者为空、或者由一个根结点和两个分别称为左子树和右子树的互不相交的二叉树组成,当集合为空时,称该二叉树为空二叉树。 二叉树具有五种基本形态:空二叉树、仅有根结点的二叉树、右子树为空的二叉树、左子树为空的二叉树和左右子树均不为空的二叉树。

左子树 右子树 左子树 右子树

(b)仅有根结点

(c) 右子树为空

(d)左子树为空

(e) 左右子树均不为空

二叉树的五种基本形态

Page 9: 第 6 章      树

2023年4月21日 星期五

9

第六章 树 6.2.2 二叉树的重要性质二叉树有以下重要性质。 性质 1 二叉树的第 i ( i≥1 )层上最多有 2 i -1 个结点。 对非空二叉树,第一层为根结点,最多只有一个结点,即 20 个;显然第二层最多有 21 个;以此类推,第 i 层上最多只有 2i-1 个结点。 性质 2 在一棵深度为 k 的二叉树中,最多具有 2 k - 1 个结点。

A

B

FE

C

A

B C

D E GF

H I KJ ML N O

1

5

2 3

4 6 7

8 9 10 11 12 13 14 15

Page 10: 第 6 章      树

2023年4月21日 星期五

10

第六章 树

性质 2 在一棵深度为 k的二叉树中,最多具有 2 k - 1个结点。 证明:由性质 1可知,第 i层的结点数最多为 2 i-1 ( 1≤i≤k),则深度为 k的二叉树的结点数最多为:

性质 3 对于一棵非空的二叉树,如果叶子结点数为 n0 ,度数为 2 的结点数为 n2 ,则有 : n0 = n2 + 1 。

1221

1

kk

i

i

A

B

FE

C

A

B C

D E GF

H I KJ ML N O

1

5

2 3

4 6 7

8 9 10 11 12 13 14 15

Page 11: 第 6 章      树

2023年4月21日 星期五

11

第六章 树证明:设度为 i 的结点有 ni 个 ( 其中: i=0 , 1 , 2) ,总结点个数为 n ,总分支数为 e ,则根据二叉树的定义,有如下的式子: n = n0 + n1 + n2 e = 2n2 + n1 = n - 1将两式结合有: 2n2 + n1 = n0 + n1 + n2 - 1 即有 n0 = n2 + 1

一棵深度为 k 且有 2 k - 1 个结点的二叉树称为满二叉树。 当深度为 k ,含有 n 个结点的二叉树,当且仅当其每个结点的编号与相应满二叉树结点顺序编号从 1~n 互相对应时,则称此二叉树为完全二叉树。

A

B C

D E GF

H I KJ ML N O

1

5

2 3

4 6 7

8 9 10 11 12 13 14 15

A

B

FE

C

Page 12: 第 6 章      树

2023年4月21日 星期五

12

第六章 树

(c) 非完全二叉树

D

A 1

2 3

6 74

B C

E F

A

CB

ED F G

H I J

1

2

4

3

5 76

8 9 10(b) 完全二叉树

A

CB

ED F G

H I J

1

2

4

3

5 76

8 9 10

L M

12 13

N O

14 15K

11

(a) 满二叉树

Page 13: 第 6 章      树

2023年4月21日 星期五

13

第六章 树 性质 4 具有 n 个结点的完全二叉树的深度 k 为。

性质 5 对于具有 n个结点的完全二叉树,如果按照从上至下和从左到右的顺序对所有结点从 1开始顺序编号,则对于任意的序号为 i的结点有: (1) 若 i =1 则该结点为根结点 i ,无双亲; (2) 若 i > 1, 则 i 结点的双亲为; (3) 若 2i≤n, 则 i 结点的左孩子为 2i ,否则没有左孩子。 (4) 若 2i+1≤n, 则 i 结点的右孩子为 2i+1 ,否则没有右孩子。 (5)i 结点所在层次为

knk 2log1

1log2 i

Page 14: 第 6 章      树

2023年4月21日 星期五

14

第六章 树

6.3 二叉树的存储结构 二叉树有两种存储结构:顺序存储结构和链式存储结构。6.3.1 顺序存储结构 二叉树的顺序存储,就是把所有结点按照一定的顺序存放到一组连续的存储单元中。一般是按照完全二叉树中结点编号将结点自上而下、自左至右的顺序存储。可以看出,数组下标和结点的编号相对应,即结点在数组中的位置隐含了结点之间的关系。

A B C D E F G H I J

1 2 3 4 5 6 7 8 9 10

A

CB

ED F G

H I J

1

2

4

3

5 76

8 9 10

数组下标

Page 15: 第 6 章      树

2023年4月21日 星期五

15

第六章 树

6.3.2 链式存储结构 二叉树的链式存储结构是指用链表的形式来存储二叉树,即用指针来指示元素之间的逻辑关系。最常用的有二叉链表和三叉链表。 1. 二叉链表存储 每个结点由三个域组成,一个数据域和两个指针域。两个指针 lchild 和 rchild 分别指向其左孩子和右孩子结点。

结点结构描述为:typedef struct BiTNode{ ElemType data; /* 数据域 */ struct BiTNode *lchild, *rchild; /* 左右指针域 */ }BTNode;

lchild data rchild

Page 16: 第 6 章      树

2023年4月21日 星期五

16

第六章 树2. 三叉链表存储 每个结点由四个域组成,具体结构如图 6-6 所示:

其中 data 、 lchild 以及 rchild 三个域的意义同二叉链表结构相同, parent 为指向该结点双亲结点的指针。这种存储结构既便于查找孩子结点,又便于查找双亲结点;但是,相对于二叉链表存储结构而言,它增加了空间开销。 typedef struct BiTNode{ ElemType data; /* 数据域 */ struct BiTNode *lchild, *rchild,*parent; /* 左、右及双亲指针域 */ }BTNode_p;

lchild data parent rchild

Page 17: 第 6 章      树

2023年4月21日 星期五

17

第六章 树

二叉树 T 的二叉链表表示,其中 bt 是一个 *BTNode 类型的变量;三叉链表表示,其中 bt_p 是 *BTNode_p 类型的变量。 A

B

D

C

E F

G

二叉树

A

B ^ C

^ D ^ E ^ ^ F ^

^ G ^

bt

二叉链表

A ^

B ^ C

^ D ^ E ^ ^ F ^

^ G ^

bt_p

三叉链表

Page 18: 第 6 章      树

2023年4月21日 星期五

18

第六章 树 6.3.3 建立二叉树的二叉链表 建立二叉链表有多种方法,比较直观的方法是利用二叉树的性质 5 来创建二叉链表。 对于一棵任意的二叉树,先按满二叉树对其进行编号。这时结点的编号满足性质 5 的关系。算法实现时,设置一个辅助向量数组 p 存放指向结点的指针。如 p[i] 用来存放编号为 i的结点的指针。当输入一个结点 s 信息,即创建了该结点,同时将该结点的指针保存在向量 p[i] 中。当 i=1 时,为根结点。当 i>1 时,其双亲结点编号 j=i/2 ,若 i 为奇数,则它是双亲的右孩子,有 p[ j ]-> rchild=s ;若 i 为偶数,则它是双亲的左孩子,有 p[ j ]->lchild=s ;。这样就让每个结点和其双亲相连,建立了二叉链表。

A B C D E F G H I J

1 2 3 4 5 6 7 8 9 10

Page 19: 第 6 章      树

2023年4月21日 星期五

19

第六章 树【算法 6.1 】建立二叉树的二叉链表存储。 # define MAXNUM 15 /* 最大结点数 */

BTNode *p[MAXNUM+1]; /* 辅助向量 */

BTNode *Create_BiTree (void)

{ printf("\n enter i,ch:"); /* 假设数据域为字符类型 */

scanf (“%d,%c”,&i,&ch);

/* 按照完全二叉树型态的结点编号顺序输入 s 结点信息 */

while (i!=0 && ch!=’#’)

{ s=( BTNode *)malloc(sizeof(BTNode));

s->data=ch;

s->lchild=s->rchild=NULL;

/* 创建结点数据域为 ch ,指针域为空 */

p[i]=s; /* 将结点放入向量 p*/

Page 20: 第 6 章      树

2023年4月21日 星期五

20

第六章 树

if (i==1) t=s;

else

{ j=i/2;

if(i%2==0) p[j]->lchild=s;

/*i 为偶数,为双亲左孩子 */

else p[j]->rchild=s; /*i 为奇数,为双亲右孩子 */

}

printf("\n enter i,ch:");

scanf(“%d,%c”,&i,&ch); /* 输入下一个结点信息 */

}

Return t; /* 返回根结点 */

} /* Create_Bt */

A B C D E F G H I J

1 2 3 4 5 6 7 8 9 10

Page 21: 第 6 章      树

2023年4月21日 星期五

21

第六章 树

6.4 二叉树的遍历 二叉树的遍历是指按照某种顺序访问树中的每个结点,并且每个结点仅被访问一次的过程。或者说是树的线性化。 一棵二叉树是由根结点、左子树和右子树三部分组成。若以 N 、 L 、 R 分别表示访问根结点、遍历根结点的左子树、遍历根结点的右子树,则二叉树的遍历方式就有以下六种:NLR 、 LNR 、 LRN 、 NRL 、 RNL 和 RLN 。常用的是前三种方式,即 NLR (称为前序或先根遍历)、 LNR (称为中序或中根遍历)和 LRN (称为后序或后根遍历)。 1.前序遍历 前序遍历的递归过程为: 若二叉树为空,遍历结束。否则( 1)访问根结点;( 2)前序遍历左子树;( 3)前序遍历右子树。

A

B

FE

C

Page 22: 第 6 章      树

2023年4月21日 星期五

22

第六章 树【算法 6.2 】递归算法如下: void preorder1 ( BTNode *bt ) { if (bt) { printf(bt->data) ; /* 访问根结点 */ preorder1(bt->lchild) ; /* 前序遍历左子树 */ preorder1(bt->rchild) ; /* 前序遍历右子树 */ } } /* preorder1 */ 如图按前序遍历所得到的结点序列为:A B D G C E 。 A

B

ED

C

G

A

B

D

C

E

G

Page 23: 第 6 章      树

2023年4月21日 星期五

23

第六章 树【算法 6.3 】二叉树的前序遍历的非递归算法。 void preorder2 ( BTNode *bt )

{ p=bt ; top=-1 ; while(p||top)

{ if(p) /* 二叉树非空 */

{ printf(p->data) ; /* 访问根结点,输出结点 */

++top ; s[top]=p ; /* 根指针进栈 */

p=p->lchild ; } /*p 移向左孩子 */

else /* 栈非空 */

{ p=s[top] ; --top ; /* 双亲结点出栈 */

p=p->rchild ; } /*p 移向右孩子 */

}

}/* preorder2 */

Page 24: 第 6 章      树

2023年4月21日 星期五

24

第六章 树

对于图 6-8(a) 中的二叉树,遍历过程中所使用栈中 s 的内容变化如图 6-9 所示。

A AB

ABD

AB

ABG

AB

A C CE

C

图 6-9 栈 s 的内容变化过程

Page 25: 第 6 章      树

2023年4月21日 星期五

25

第六章 树2.中序遍历 中序遍历的递归过程为: 若二叉树为空,遍历结束。否则( 1 )中序遍历左子树;( 2 )访问根结点;( 3 )中序遍历右子树。【算法 6.4】二叉树的中序遍历的递归算法。 void Inorder(BTNode *bt) { Inorder(bt->lchild) ; /* 中序遍历根结点 */ printf(bt->datd) ; /* 访问根结点 */ Inorder(bt->rchild) ; /* 中序遍历右子树 */ } /* inorder */ 对于图所示的二叉树,按中序遍历所得到的结点序列为: D G B A E C

A

B

ED

C

G

Page 26: 第 6 章      树

2023年4月21日 星期五

26

第六章 树3.后序遍历 后序遍历的递归过程为: 若二叉树为空,遍历结束。否则( 1 )后序遍历左子树;( 2 )后序遍历右子树。( 3 )访问根结点;【算法 6.5】二叉树的后序遍历的递归算法 void Postorder(BTNode *bt) {Postorder(bt->lchild) ; /* 后序遍历根结点 */ Postorder(bt->rchild) ; /* 访问根结点 */ printf(bt->datd) ; /* 后序遍历右子树 */ } /* Postorder*/对于图所示的二叉树,按后序遍历所得到的结点序列为: G D B E C A 。

A

B

ED

C

G

Page 27: 第 6 章      树

2023年4月21日 星期五

27

第六章 树任意一棵二叉树结点的前序和中序和后序序列都是唯一的。反过来,若已知结点的前序和中序序列(或后序和中序序列),便能够唯一确定一棵二叉树。例如,已知一棵二叉树的前序序列与中序序列分别为: A B D C E F G B D A E C G F 根

A

BD

CEFG

以 A 为根的左、右子树先序序列示意图

A

B C

D GF

E

Page 28: 第 6 章      树

2023年4月21日 星期五

28

第六章 树

4.按层次遍历二叉树 所谓二叉树的层次遍历是指从二叉树的第一层开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。图所示的二叉树,按层次遍历所得到的结果序列为:A B C D E G 。 为了实现按层次遍历,可设置一个队列结构,从二叉树的根结点开始,首先将根结点指针入队列,然后反复执行下面两个操作,直到队列空为止。

A

B

ED

C

G

(1) 从队头取出一个元素,每取一个元素,访问该元素所指向的结点; (2) 若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针顺序入队。

Page 29: 第 6 章      树

2023年4月21日 星期五

29

第六章 树

在下面的层次遍历算法中,二叉树以二叉链表存放,一维数组 Queue[MAXNODE] 用以实现队列,变量 front和 rear 分别表示当前队头元素和队尾元素在数组中的位置。【算法 6.6】二叉树的层次遍历算法 void LevelOrder ( BTNode* bt ) /* 层次遍历二叉树 bt*/

{ BTNode* Queue[MAXNODE];

int front,rear;

if (bt==NULL) return;

front=0; /* 非循环队列 */

rear=0;

queue[rear++]=bt;

A

B

ED

C

G

Page 30: 第 6 章      树

2023年4月21日 星期五

30

第六章 树

while(front!=rear) {Visite(queue[front]->data); /*访问队首结点的数据域 */ if (queue[front]->lchild!=NULL) /* 将队首结点的左孩子结点入队列 */ { rear++; queue[rear]=queue[front]->lchild; } if (queue[front]->rchild!=NULL) /* 将队首结点的右孩子结点入队列 */ {rear++; queue[rear]=queue[front]->rchild; } front++; } /* while */ }

A

B

ED

C

G

Page 31: 第 6 章      树

2023年4月21日 星期五

31

第六章 树

6.5 线索二叉树6.5.1 线索二叉树的定义及结构1.线索二叉树的定义 以某种方式对二叉树进行遍历时,可以得到结点的一个线性序列。利用二叉树的二叉链表存储结构中的那些空指针域来指示前驱和后继结点的指针被称为线索,加了线索的二叉树称为线索二叉树。

A

B

D

C

E F

G

0 A 0

0 B 1 0 C 0

^ 1 D 0 1 E 1 1 F 1 ^

1 G 1

A

B

ED

C

G

F

Page 32: 第 6 章      树

2023年4月21日 星期五

32

第六章 树 2.线索二叉树的结构 n 个结点的二叉链表中有 2n 个指针域,另外 n+ 1 个指针域为空而被闲置。因此,可以利用这些空闲的指针域:无左孩子时,令其左指针域指向其直接前驱结点;当该结点无右孩子时,令其右指针域指向其直接后继结点。对于那些非空的指针域,则仍然指向该结点左、右孩子。这样,就得到了线索二叉树。把二叉树改造成线索二叉树的过程称为线索化。可在每个结点中增设两个标志位 Ltag 和 Rtag 。这样结点的结构为:

0 A 0

0 B 1 0 C 0

^ 1 D 0 1 E 1 1 F 1 ^

1 G 1

lchild Ltag data Rtag rchild

Page 33: 第 6 章      树

2023年4月21日 星期五

33

第六章 树

其中: 0 lchild 指向结点的左孩子 1 lchild 指向结点的直接前驱结点 0 rchild 指向结点的右孩子  1 rchild 指向结点的直接后继结点 对应的描述为:typedef struct ThredNode { ElemType data; struct ThredNode *lchild,*rchild; unsigned Ltag,Rtag; }ThrNode ;

Ltag =

Ltag =

lchild Ltag data Rtag rchild

Page 34: 第 6 章      树

2023年4月21日 星期五

34

第六章 树6.5.2 中序线索二叉树操作 1.建立中序线索二叉树 建立线索二叉树,或者说对二叉树线索化,就是将二叉链表中的空指针改为指向前驱和后继结点的线索,而前驱和后继只能在遍历过程中才能得到。因此,中序线索二叉树的建立是在中序遍历二叉树过程中,检查当前结点的左、右指针域是否为空,如果为空,将它们改为指向前驱结点或后继结点的线索,即建立了线索。

0 A 0

0 B 1 0 C 0

^ 1 D 0 1 E 1 1 F 1 ^

1 G 1

Page 35: 第 6 章      树

2023年4月21日 星期五

35

第六章 树

【算法 6.7 】建立中序线索二叉树的递归算法void InOrderThr(ThrNode *t) /* 中序线索二叉树 t */ { if ( t ) { InOrderThr (t->lchild ); /* 线索化左子树 */ if (t ->lchild= =NULL) { t -> Ltag=1;t -> lchild=pre; } if (pre -> rchild=NULL) /* 建立 pre 结点的后继线索 */ { pre -> Rtag=1; pre -> rchild=t; } pre=t; InOrderThr( t -> rchild); /* 线索化右子树 */ } /* InOrderThr */

Page 36: 第 6 章      树

2023年4月21日 星期五

36

第六章 树

2.遍历中序线索二叉树 要遍历线索二叉树,关键是找到结点的后继。有以下两种情况: ( 1 )如果该结点的右标志为 1 ,那么其右指针域所指向的结点便是它的后继结点;( 2 )如果该结点的右标志为 0 ,该结点有右孩子,它的后继结点是以该结点的右孩子为根结点的子树的最左下的结点。【算法 6.8】中序线索二叉树的遍历算法。void InVisitThr(ThrNode *t) 0 A 0

0 B 1 0 C 0

^ 1 D 0 1 E 1 1 F 1 ^

1 G 1

Page 37: 第 6 章      树

2023年4月21日 星期五

37

第六章 树 { p=t ; if(p!= NULL) { while(p->lchild !=NULL) p=p->lchild ; printf(p->data); /* 中序序列第一个结点 */ while(p->rchild!=NULL) { if (p->Rtag==1) p=p->rchild; /* 右标志为 1 ,则右指针域所指向的结点是它的后继结点 */ else { p=p->rchild; /* 右标志为 0 ,表明该结点有右子树 */ while(p->Ltag!=1) p=p->lchild; } printf(p->data); } }/* InVisitThr */

Page 38: 第 6 章      树

2023年4月21日 星期五

38

第六章 树3. 在中序线索二叉树上查找任意结点的中序前驱结点有以下两种情况: ( 1 )如果该结点的左标志为 1 ,那么其左指针域所指向的结点就是它的前驱结点;( 2 )如果该结点的左标志为 0 ,表明该结点有左孩子,它的前驱结点是以该结点的左孩子为根结点的子树的最右结点。【算法 6.9】在中序线索二叉树上找结点 p 的中序前驱结点ThrNode InPreNode ( ThrNode p ){ ThrNode pre;

pre=p->lchild;

if (p->Ltag !=1)

while (pre->Rtag==0) pre=pre->rchild;

return(pre); }

Page 39: 第 6 章      树

2023年4月21日 星期五

39

第六章 树6.6 树的存储结构

1.双亲表示法 因为树中的每个结点都有唯一的一个双亲结点,可用一个一维数组来存储树中的各个结点,其中包括结点本身的信息以及结点的双亲结点在数组中的序号。其结点表示可描述为: Typedef struct PtNode { ElemType data ; int parent ; }PTNode ;

求某个结点的双亲结点是非常容易的,但要查找某一个结点的孩子时,则要访问整个数组。

A

B C

D E F

G H

I J

0

1

7

2

3

4

5

6

A -1

B 0

C 0

D 1

E 1

F 2

I 2

J 2

G 4

H 4

89

Page 40: 第 6 章      树

2023年4月21日 星期五

40

第六章 树

2.孩子表链表示法 将每个结点的孩子用链表连接起来,构成一个单链表。单链表的结构由两个域组成,一个存放孩子结点在一维数组中的序号,另一个域指向下一个孩子。

1 2 ∧3 4 ∧5

8 9 ∧

0

1

7

2

3

4

5

6

A

B

C

D ∧E

F ∧I ∧J ∧G ∧H ∧

6 7 ∧

9

8

Page 41: 第 6 章      树

2023年4月21日 星期五

41

第六章 树

A ∧

∧ G.. ∧ H ∧

B C ∧

∧ D E ∧

∧ F ∧ C ∧ C ∧

3.孩子-兄弟表示法 这种表示法以二叉链表作为树的存储结构,其结点结构为:一个数据域和两个指针域,其中一个指针指向它的第一个孩子结点,另一个指针指向它的兄弟结点。

Page 42: 第 6 章      树

2023年4月21日 星期五

42

第六章 树6.7 树、森林与二叉树的转换

6.7.1 树和森林转换为二叉树1.一般树转换为二叉树( 1)连线:在各兄弟之间用虚线相连。 ( 2 )抹线:对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其它孩子结点之间的连线。 ( 3)旋转:以树的根结点为轴心,将整棵树顺时针转动 45°度角,使之结构层次分明。

A

A

B C D

E F G

H

A

B C D

E F G

H

B

C

D

E

F

G

H

A

B C D

E F G

H

Page 43: 第 6 章      树

2023年4月21日 星期五

43

第六章 树

H

I J

LK

FED

C

B

A

H

I

F

E

D

C

B

A

K J

L

G

G

(a) 一般树的森林

(b) 二叉树的森林

A

B C

L

JKF

E I

HD

G

(c) 最终的二叉树

Page 44: 第 6 章      树

2023年4月21日 星期五

44

第六章 树

2 .森林转换为二叉树 ( 1)将森林中的每棵树转换成相应的二叉树,形成二叉树的森林。 ( 2)按森林中的先后顺序,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子。6.7.2 二叉树还原为树和森林1.二叉树还原为一般树步骤 : (1)加线。若某结点是双亲结点的左孩子,则将该结点的右孩子以及当且仅当连续地沿着右孩子的右链不断搜索到的所有右孩子,都分别与结点的双亲结点用虚线连接。 (2) 抹线:把原二叉树中所有双亲结点与其右孩子连线抹去。 (3) 整理、旋转:把虚线改为实线;把结点按层次排列。

Page 45: 第 6 章      树

2023年4月21日 星期五

45

第六章 树

2 .二叉树还原为森林( 1 )抹线 ; ( 2 )还原 .

A

B

C D

E F

G

I J

H

A

B

C D

E F

G

I J

H

A

B

C D

E F

G

I J

H

A

B

C

D

E

F

G

I

J

H

二叉树还原为树的过程示意图

Page 46: 第 6 章      树

2023年4月21日 星期五

46

第六章 树6.8 哈夫曼树及其应用

哈夫曼( Huffman )树,也称最优二叉树,是指一类带权路径最短的树。1.哈夫曼树的基本概念( 1)路径、路径长度:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支个数称为路径长度。( 2)树的路径长度:从根结点到树中每个结点的路径长度之和。( 3)结点的带权路径长度:从根结点到该结点的路径长度与该结点所带的权值的乘积。( 4)树的带权路径长度:树中所有叶子结点的带权路径长度之和。记为:

n

kkkLWWPL

1

Page 47: 第 6 章      树

2023年4月21日 星期五

47

第六章 树

( 5 )哈夫曼树:设有 n 个权值 { W1 , W2 ,…, Wn } ,构造一个具有 n 个叶子结点的二叉树,每个叶子结点带有权值 Wi ,在所有的二叉树中,带权路径长度 WPL 最小的二叉树称为哈夫曼树。

(c)

4E D6

8

B

A 3

4

C5

(b)

2 4A B3

4C E5

6D

8

(a)

C D

A B3 4

5 8

E 6

哈夫曼树

哈夫曼树

Page 48: 第 6 章      树

2023年4月21日 星期五

48

第六章 树2 .构造哈夫曼树一棵二叉树要使其 WPL 值最小,必须使权值越小的叶子结点越远离根结点。 ( 1 )根据给定的 n 个权值 {W1 , W2 ,…, Wn} 构造 n 棵只有一个根结点的二叉树。从而得到一个二叉树的集合 F = {T1 , T2 ,…, Tn} 。 ( 2 )在集合 F 中选取根结点权值最小的两棵二叉树作为一棵新二叉树的左、右子树,新的二叉树根结点的权值为左、右子树根结点权值之和。( 3 )在集合 F 中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合 F 中。( 4 )重复( 2 )( 3 )两步,当 F 中只剩下一棵二叉树时,这棵二叉树就是所要建立的哈夫曼树。

4E D6

8

B

A 3

4

C5

Page 49: 第 6 章      树

2023年4月21日 星期五

49

第六章 树例: W= {3 , 4 , 5 , 8 , 6} 的哈夫曼树的构造过程。为了直观起见,在图中把带权的叶子结点画成方形,其它非叶子结点仍为圆形。其带权路径长度为 59 。

(a) (b) (c)

3 854 6

3

85

4

7 6

3

8

4

7

5 6

11

(d) (e)

3

8

4

7

15

5 6

11

3

8

4

7

15

5 6

11

26

Page 50: 第 6 章      树

2023年4月21日 星期五

50

第六章 树

3 -1 -1 -14 -1 -1 -15 -1 -1 -18 -1 -1 -16 -1 -1 -1

-1 -1 -1-1 -1 -1-1 -1 -1-1 -1 -1

weight pa lc rc

3 5 -1 -1 4 5 -1 -1 5 6 -1 -1 8 7 -1 -1 6 6 -1 -1 7 7 0 111 8 2 415 8 3 526 -1 6 7

0

1

2

3

4

5

6

7

8

0

1

2

3

4

5

6

7

8

生成前的状态 生成后的状态

weight pa lc rc

Page 51: 第 6 章      树

2023年4月21日 星期五

51

第六章 树

3.哈夫曼树的算法实现 首先定义结点结构 HTNode ,包括权值、双亲、左右孩子等信息,然后再设置一个结构类型 HTNode 数组 Ht[ ] ,用来保存哈夫曼树中各结点的信息,数组 Ht[ ] 的大小设置为2n - 1 ,描述如下: #define MAXVALUE 32767 /* 定义最大权值 */ #define MAXLEAF 8 /* 定义树中叶子结点个数 */ #define MAXNODE MAXLEAF*2-1 Typedef struct node { int weight; /* 权值 */ int parent,lchild,rchild; /* 双亲、左右孩子下标 */ } HTNode;为了判定一个结点是否已加入到要建立的哈夫曼树中,可通过 parent 域的值来确定。

Page 52: 第 6 章      树

2023年4月21日 星期五

52

第六章 树【算法 6.10】哈夫曼树的构造算法。 void Huffman_tree( ) {int i,j,m1,m2,p1,p2; HTNode Ht[MAXNODE]; for(i=0;i<MAXNODE;i++) {Ht[i].parent=-1; Ht[i].lchild=-1; Ht[i].rchild=-1; } for(i=0;i<MAXLEAF;i++){ printf("please input weight\n"); scanf("%d",&Ht[i].weight); } for(i=MAXLEAF;i<MAXNODE;i++){ m1=m2=MAXVALUE ;p1=p2=0;

3 -1 -1 -14 -1 -1 -15 -1 -1 -18 -1 -1 -16 -1 -1 -1

-1 -1 -1-1 -1 -1-1 -1 -1-1 -1 -1

weight pa lc rc

0

1

2

3

4

5

6

7

8

生成前的状态

Page 53: 第 6 章      树

2023年4月21日 星期五

53

第六章 树 for(j=0;j<i;j++) {if(Ht[j].weight<m1&&Ht[j].parent==-1) { m2=m1;p2=p1; m1=Ht[j].weight; p1=j; } else if(Ht[j].weight<m2&&Ht[j].parent==-1) {m2=Ht[j].weight; p2=j; } } Ht[p1].parent=i; Ht[p2].parent=i; /* 将找出的两棵子树合并为一棵子树 */ Ht[i].lchild=p1; Ht[i].rchild=p2; Ht[i].weight=Ht[p1].weight+Ht[p2].weight; } }/*Huffman_tree*/

3 -1 -1 -1

4 -1 -1 -15 -1 -1 -18 -1 -1 -1

6 -1 -1 -1-1 -1 -1-1 -1 -1-1 -1 -1-1 -1 -1

weight pa lc rc0

1

2

3

4

5

6

7

8

生成前的状态

Page 54: 第 6 章      树

2023年4月21日 星期五

54

第六章 树4.哈夫曼树的应用 哈夫曼编码 假定要进行编码的字符集为 {c1,c2,c3,…cn} ,设各个字符在电文中出现的次数或频率为集合 {w1,w2,w3,…wn} 。用叶子结点的权值表示字符 ci ( 1≤i≤n )的出现次数或频率 wi 来构造一棵哈夫曼树,规定生成的哈夫曼树中的左分支代表 0 ,右分支代表 1 ,则从根结点到每个叶子结点所经过的路径分支组成的 0 和 1 的序列就是该结点对应字符的编码,称之为哈夫曼编码。

例如:发送一段电文,其中字符集为 { a, e, g, x, m, y, b } ,各个字符出现的次数为 {41,25,20,14,10,8,3} 。

3 8

1110

21

14 20

3425

46

41

75

121

0

0

0

0

0

0

1

1

1

1

1

1

m

b y

xg

ae

Page 55: 第 6 章      树

2023年4月21日 星期五

55

第六章 树

字符 编码a 11

e 01g 101x 100m 000y 0010b 0011

(a) (b)

实现哈夫曼编码的算法可分为两大部分( 1)构造哈夫曼树;( 2)在哈夫曼树上求叶子结点的编码

3 8

1110

21

14 20

3425

46

41

75

121

0

0

0

0

0

0

1

1

1

1

1

1

m

b y

xg

ae

Page 56: 第 6 章      树

2023年4月21日 星期五

56

第六章 树

在算法中设置一结构数组 HuffCode 用来存放各字符的哈夫曼编码信息,数组元素的结构如下: #define MAXBIT 10 /* 定义哈夫曼编码的最大长度 */

typedef struct {

int bits[MAXBIT];

int start;

char ch;

}HcodeType, HuffCode[MAXLEAF];

其中,分量 bits 为一维数组,用来保存字符的哈夫曼编码, start 表示该编码在数组 bit 中的开始位置。所以,对于第 i 个字符,它的哈夫曼编码存放在 HuffCode[i].bits 中的从HuffCode[i].start 到 n 的分量上。

Page 57: 第 6 章      树

2023年4月21日 星期五

57

第六章 树

【算法 6.11 】哈夫曼编码算法 void Huffman_code() /* 哈夫曼树编码 */ {HCodeType cd; for(i=0;i<MAXLEAF;i++) {cd.start=MAXLEAF; printf("please input code\n"); getchar(); scanf("%c",&cd.ch); c=i; p=Ht[i].parent; while(p!=-1) {cd.start--; if(Ht[p].lchild==c) cd.bits[cd.start]=0; else cd.bits[cd.start]=1; c=p; p=Ht[p].parent; }

Page 58: 第 6 章      树

2023年4月21日 星期五

58

第六章 树

code[i]=cd; } for(i=0;i< MAXLEAF;i++) /*输出字符、权值及编码 */ { printf("char %c weight is %d code is",code[i].ch,Ht[i].weight); for(j=code[i].start;j<MAXLEAF;j++) printf(" %d ",code[i].bits[j]); printf("\n"); } } 实现哈夫曼编码,实质上就是在已建立的哈夫曼树中,从叶子结点开始,沿结点的双亲链域回退到根结点,得到一位哈夫曼码值。

Page 59: 第 6 章      树

2023年4月21日 星期五

59

第六章 树( 2 )最佳判断过程 把百分制转换成优 (x≥90) 、良 (80≤x< 90=、中 (70≤x<80) 、及格 (60≤x< 70=和不及格 (x< 60=五个等级。一般,学生考分大多分布在 70~ 80 分之间 .

不及格

及格

中等

良好 优秀

N

N

N

N

Y

Y

Y

Y

(a)

X<90

X<80

X<70

X<60

分数 0 - 59 60 - 69 70 - 79 80 - 89 90- 100比例数 0.05 0.15 0.40 0.30 0.10

if (a<60) b=”bad”; else if (a<70) b=”pass” else if (a<80) b=”general” else if (a<90) b=”good” else b=”excellent”;

如果输入 100 个数,执行次数为: 5*1+15*2+40*3+30*4+10*4=315 次

5

15

40

30 10

Page 60: 第 6 章      树

2023年4月21日 星期五

60

第六章 树

判定树 (b)执行次数为: (10+5)*4+15*3+30*2+40=205

判定树 (c)执行次数为: (15+5)*3+ (30+10+40)*2=220 次但判定树 (b) 在比较时,每个判断框都是复合比较,运行慢。

不及格

及格

中等

良好

优秀

N

N

N

N

Y

Y

Y

Y

X<60

60≤X<70

80≤X<90

70≤X<80

40

515

30

10不及格 及格

中等 良好 优秀

N

N

N

N

Y

Y

Y

YX<60

X<70

X<80

X<90

5 15

40 30 10

Page 61: 第 6 章      树

2023年4月21日 星期五

61

第六章 树

小 结树 树的定义、树的基本运算 树的分层定义是递归的 树中结点个数与高度的关系二叉树 二叉树定义、基本运算二叉树性质二叉树结点个数与高度的关系不同二叉树棵数完全二叉树的顺序存储 完全二叉树的双亲、子女和兄弟的位置 二叉树的前序 · 中序 · 后序 · 层次遍历中序 线索化二叉树中后继的查找方法 应用二叉树遍历的递归算法

Page 62: 第 6 章      树

2023年4月21日 星期五

62

第六章 树

哈夫曼树 哈夫曼树的构造方法哈夫曼编码带权路径长度的计算树的存储 树与二叉树的对应关系