补充知识 1 :二叉树的存储结构 ( 顺序、链式 )

65
补补补补 1 补补补补补补补补 ( 补补 补补 ) 1. 补补补补补补 补补补补补补补补补补补自自自自 自自自自 补补补补补补补补补补补# define MAX_TREE_SIZE 100 typedef TElemType SqBiTree[MAX_TREE _SIZE] 1 2 4 5 3 补补补补补 : 补补补 i 补补补补补补补补补补补 i-1 补补补补1 2 3 4 5 0 1 2 3 4

Upload: tyrell

Post on 12-Jan-2016

282 views

Category:

Documents


0 download

DESCRIPTION

1. 2. 4. 5. 3. 1. 2. 3. 4. 5. 0 1 2 3 4. 补充知识 1 :二叉树的存储结构 ( 顺序、链式 ). 1. 顺序存储结构. 用一组 地址连续 的存储单元依次 自上而下、自左至右 存储二叉树上的结点元素。. # define MAX_TREE_SIZE 100 typedef TElemType SqBiTree[ MAX_TREE_SIZE ]. 完全二叉树 : 编号为 i 的元素存储在数组下标为 i - 1 的分量中;. 1. 2. 3. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

补充知识 1 :二叉树的存储结构 ( 顺序、链式 )

1. 顺序存储结构

用一组地址连续的存储单元依次自上而下、自左至右存储二叉树上的结点元素。

# define MAX_TREE_SIZE 100

typedef TElemType SqBiTree[MAX_TREE_SIZE]

1

2

4 5

3

完全二叉树 : 编号为 i 的元素存储在数组下标为 i-1 的分量中;

1 2 3 4 50 1 2 3 4

Page 2: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

1

2 3

4 5

一般二叉树 : 对照完全二叉树,存储在数组的相应分量中;

在最坏情况下,深度为 k 的右单支二叉树需要 2k-1 个存储空间。

1

2

3

结论 : 顺序存储结构适用于完全二叉树。空间浪费

1 2 3 4 50 0

0 表示不存在此结点

0 1 2 3 4 5 6

1 2 30 00 0

0 1 2 3 4 5 6

Page 3: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2. 链式存储结构

D = ( root , DL , DR ) 。

链表结点包含 3 个域 : 数据域、左指针域、右指针域

数据域左指针域 右指针域

data rchildlchild

data

lchild rchild

由这种结点结构得到的二叉树存储结构称为二叉链表。

Page 4: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

二叉链表存储表示

struct BitNode {

}

TElemType data ; BitNode * lchild , * rchild ;

Page 5: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

A

B C

D E

F G H

∧ ∧

∧ ∧∧ ∧∧ ∧

Page 6: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

有时还可以在结点结构中增加一个指向父亲的指针。

数据域左指针域 右指针域

data rchildlchild father

父指针域

data

lchild rchild

father

采用何种存储结构,依赖于进行何种操作 ?

Page 7: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

1.1 基本操作

1.1.1 遍历二叉树

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

线性结构的遍历;

非线性结构—二叉树的遍历。

Page 8: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

D = ( root , DL , DR ) 。

如果能依次遍历这三部分,就可以遍历整个二叉树;

设以 D 、 L 、 R 分别表示访问根结点、遍历左子树、遍历右子树,则可以存在 6 种遍历方案 : DLR 、 DRL 、 LDR 、 LRD 、 RDL 、 RLD ;

若限定先左后右的原则,则只有 3 种情况 : 先 ( 根 )

序遍历、中 ( 根 ) 序遍历、后 ( 根 ) 序遍历。

Page 9: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

先 ( 根 ) 序遍历 :

若二叉树为空,则返回;否则

(1) 访问根结点;(2) 先 ( 根 ) 序遍历左子树;(3) 先 ( 根 ) 序遍历右子树;

AA A

B C

A B C

Page 10: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

printf(T->data) ;

PreOrderTraverse ( T->lchild, printf ) ;

PreOrderTraverse ( T->rchild, printf ) ;

If (T) {

}

Status PreOrderTraverse ( BiTree T , printf(e) ) {

}

// visit(e) 函数可以看作 printf (e)

算法 1.1 先序遍历递归算法

else return OK ;

Page 11: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

A

B C

D E

F G H

先序遍历

0

0 0 0 0

0

0

0 0

bigin end

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

A

B

D

F G

C

E

H

Page 12: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

中 ( 根 ) 序遍历 :

若二叉树为空,则返回;否则

(2) 访问根结点;

(1) 中 ( 根 ) 序遍历左子树;

(3) 中 ( 根 ) 序遍历右子树;

AA

A

B C

B A C

Page 13: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

printf(T->data) ;

InOrderTraverse ( T->lchild, printf ) ;

InOrderTraverse ( T->rchild, printf ) ;

If (T) {

} else return OK ;

Status InOrderTraverse ( BiTree T , printf(e) ) {

}

// visit(e) 函数可以看作 printf (e)

算法 1.2 中序遍历递归算法

Page 14: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

A

B C

D E

F G H

中序遍历

0

0 0 0 0

0

0

0 0

bigin end

中序遍历顺序 : AB DF G C E H

B

F

D

G

A

C

E

H

Page 15: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

后 ( 根 ) 序遍历 :

若二叉树为空,则返回;否则

(3) 访问根结点;

(1) 后 ( 根 ) 序遍历左子树;

(2) 后 ( 根 ) 序遍历右子树;

AA

A

B C

B C A

Page 16: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

printf(T->data) ;

PostOrderTraverse ( T->lchild, printf ) ;

PostOrderTraverse ( T->rchild, printf ) ;

If (T) {

} else return OK ;

Status PostOrderTraverse ( BiTree T , printf(e) ) {

}

// visit(e) 函数可以看作 printf (e)

算法 1.3 后序遍历递归算法

Page 17: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

A

B C

D E

F G H

后序遍历

0

0 0 0 0

0

0

0 0

bigin end

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

A

B

D

F G

C

E

H

Page 18: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

例,表达式 a + b * c – d / e

-

+ /

* d e

b c

a

先序遍历 :

– + a * b c / d e

前缀式,波兰式

中序遍历 :

a + b * c – d / e

中缀式,算术表达式

后序遍历 :

a b c * + d e / -

后缀式,逆波兰式

Page 19: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

中缀表示 : 适于人的思维

后缀表示 : 适于计算机的思维

a + b * c – d / e

a b c * + d e / -

Page 20: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

对二叉树除可以进行先序、中序、后序的遍历外,还可以进行层次遍历。

H

C

F E

D

A

层次遍历 : H , C , D , F , E , A

过程 : 打印 H ;打印 H 的左儿子 C ;

打印 C 的左、右儿子 F 、 E ;打印 D 的左、右儿子 A ;

栈实现 ?

打印 H 的右儿子 D ;

Page 21: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

队列实现层次遍历 H

C

F E

D

A

H

入队出队

H

C D

C

F E

D

A

F E A

Page 22: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

1.1.2 线索二叉树

二叉树的遍历实现了对一个非线性结构进行线性化的操作,从而使每个结点在线性序列中有且仅有一个直接前驱和直接后继。

H

C

F E

D

A

但以二叉链表作为存储结构时,

如何直接找到任意结点的前驱和后继结点?

Page 23: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

方法 1: 增加两个指针域 fwd 和 bkwd ,分别指示其前驱和后继。

缺点 : 需要大量额外空间。优点 : 实现方便、简单。

方法 2: 利用闲余的空链域。

数据域左指针域 右指针域

data rchildlchildfwd bkwd

前驱 后继

A

B C

E FD

Page 24: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

性质 : 含有 n 个结点的二叉链表中有 n+1 个空链域。

证明 : (1) 设,终端结点数为 n0 ,

度为 1 的结点数为 n1 ,

故二叉树的结点总数为 n = n0 + n1 + n2 ;

度为 2 的结点数为 n2 ,

(2) 空链域个数为 2n0 + n1 ,

已知, n0 = n2 + 1 ,

故, 2n0 + n1

= n0 + n1 + n2 + 1

= n + 1

Page 25: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

如何利用空链域描述前驱和后继信息?

data rchildlchild LTag RTag

其中 :

LTag = 0 lchild 域指示结点的左儿子

1 lchild 域指示结点的前驱结点

RTag = 0 rchild 域指示结点的右儿子

1 rchild 域指示结点的后继结点

增加线索的二叉树称之为线索二叉树。

对二叉树按照某种次序使其成为线索二叉树的过程叫做线索化

Page 26: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

typedef enum PionterTag ( Link , Thread ) ; // 0 , 1

TElemType data ;

struct BiThrNode * lchild , * rchild ;

PointerTag LTag , RTag ;

typedef struct BiThrNode {

}

二叉线索树的存储表示

线索二叉链表的建立是依据二叉树的遍历顺序的。

Page 27: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

A 00

B 01 C 01

D 00

F 11 G 11

E 01

H 11

bt

中序遍历 AB DF G C E H

例,中序线索链表

指向左儿子 指向左

儿子

指向前驱

指向后继

NULL

NULL

Page 28: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

以下为中根次序线索化二叉树的遍历算法,在空缺上填写具体句 , 使之成为一个完整的算法。

struct nodex { char data; struct nodex *lch, *rch; int ltag, rtag; /* 左、 右标志域,有孩子时标志域为 0 ,为线索时值为 1 */

};

struct nodex   *insucc(struct nodex *q) // 计算结点 q 的后继   { if(q->rtag==1) ___(1)_______ else {r=q->rch; while (r->ltag!=1) _____(2)_______ p=r; } return(p); }

Page 29: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

void inthorder(stuct nodex *t) { p=t; if(p! =NULL) while(p->lch! =NULL) p=p->lch; /* 查找二叉树的最左结点 */ cout<< p->data; while(p->rch!=NULL) { p=insucc(p); /* 调用求某结点 p 后继的算法 */ cout<<p->data; } } /* inthorder */

Page 30: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

补充知识 2 :树的应用

1. 二叉分类树 ( 二叉排序树 )

2. 赫夫曼树 ( 最优二叉树 )

Page 31: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2.1. 二叉分类树 ( 二叉排序树 )

二叉分类树或者是一棵空树;或者是具有下列性质的二叉树:

(1) 左子树上所有结点的值均小于等于它的根结点的值;

(2) 右子树上所有结点的值均大于它的根结点的值;

(3) 根结点的左、右子树也分别为二叉分类树。

13

8

5

23

18 37

如何插入新结点 9 ?9

9右儿子

为空

9

Page 32: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

13

8

5

23

18 37

利用插入操作可以构造一棵二叉分类树

首先给出结点序列 :

13 、 8 、 23 、 5 、 18 、 37

Φ 13

5 37 18

8 23

8235

5

18

18

37

37

分类二叉树的应用 :

快速、方便查找某个结点

Page 33: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2.2. 1 赫夫曼树 Huffman ( 最优二叉树 )

1 基本概念 :

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

路径上的分支数目称做路径长度。

X

Y

Z

X 到 Y 的路径路径长度为 2

X 到 Z 的路径

Page 34: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

以此推广,得到结点加权 w 。

7 3

5

A B

C

D

E

结点的带权路径长度为从根结点到该结点之间的路径长度与结点上权值的乘积。

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

记做 WPL = ∑ wk L(vk ) n

k=1

wk 为叶子结点 vk 的权值

L(vk ) 为叶子结点 vk 的路径长度

树的路径长度是从树根到每一个结点的路径长度之和。 在具有相同结点数的所有二叉树中, 的路径长度是最短的。完全二叉树

Page 35: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

例, 3 棵二叉树,都有 4 个叶子结点 a 、 b 、 c 、 d ,分别带权7 、 5 、 2 、 4 ,求它们各自的带权路径长度。

a b c d7 5 2 4

(1) a b

d

c

7 5

4

2

(2)

c d

b

a

2 4

5

7

(3)

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

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

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

Page 36: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

假设有 n 个权值 {w1,w2, … wn} ,试构造一棵有 n 个叶子结点的

二叉树,每个叶子结点带权为 wi ,则其中带权路径长度 WPL 最

小的二叉树称做最优二叉树或赫夫曼树。 如何构造赫夫曼树?

(1) 根据给定的 n 个权值 {w1,w2, … wn} 构成 n 棵二叉树的集合

F = {T1,T2, … Tn} ,其中每棵二叉树 Ti 中只有一个权值为 wi 的根结点。(2) 在 F 中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且置新二叉树的根结点的权值为其左、右子树根结点的权值之和。

(3) 在 F 中删除这两棵树,同时将新得到的二叉树加入集合 F 中。

(4) 重复 (2) 和 (3) ,直到 F 中只含一棵树为止。

Page 37: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

例, 4 个叶子结点 a 、 b 、 c 、 d ,分别带权 7 、 5 、 2 、 4 。

c d2 4b5

a 7初始

c d2 4

6

b 5

c d2 4

6

11

a 7

b 5

c d2 4

6

11

18

Page 38: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2.2.2 赫夫曼编码

1. 编码

例,传送 ABACCD ,四种字符,可以分别编码为 00,01,10,11 。

则原电文转换为 00 01 00 10 10 11 。对方接收后,采用二位一分进行译码。

电文编码

二进制二进制 译码

电文

Page 39: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

当然,为电文编码时,总是希望总长越短越好,

如果对每个字符设计长度不等的编码,且让电文中出现次数较多的字符采用较短的编码,则可以减短电文的总长。

例,对 ABACCD 重新编码,分别编码为 0 , 00 , 1 , 01 。A B C D

则原电文转换为 0 00 0 1 1 01 。 减短了。

问题 : 如何译码?

前四个二进制字符就可以多种译法。

AAAA BB

Page 40: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2. 前缀编码

若设计的长短不等的编码,满足任一个编码都不是另一个编码的前缀,则这样的编码称为前缀编码。

例, A , B , C , D 前缀编码可以为 0 , 110 , 10 , 111

利用二叉树设计二进制前缀编码。

叶子结点表示 A , B , C , D 这 4 个字符

A

C

B D

0

0

0 1

1

1

左分支表示 ‘ 0’ ,右分支表示 ‘ 1’

从根结点到叶子结点的路径上经过的二进制符号串作为该叶子结点字符的编码

A(0)

B(110)

C(10)

D(111)

证明其必为前缀编码

路径长度为编码长度

Page 41: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

如何得到最短的二进制前缀编码?

3. 赫夫曼编码

设每种字符在电文中出现的概率 wi 为,则依此 n 个字符出现的

概率做权,可以设计一棵赫夫曼树,使

WPL = ∑ wi li 最小n

i=1

wi 为叶子结点的出现概率 ( 权 )

li 为根结点到叶子结点的路径长度

Page 42: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

例,某通信可能出现 A B C D E F G H 8 个字符,其概率分别为 0.

05 , 0.29 , 0.07 , 0.08 , 0.14 , 0.23 , 0.03 , 0.11 ,试设计赫夫曼编码

不妨设 w = { 5 , 29 , 7 , 8 , 14 , 23 , 3 , 11 }

排序后 w = { 3 , 5 , 7 , 8 , 11 , 14 , 23 , 29 }{ 7 , 8 , 8 , 11 , 14 , 23 , 29 }{ 8 , 11 , 14 , 15 , 23 , 29 }{ 14 , 15 , 19 , 23 , 29 }{ 19 , 23 , 29 , 29 }{ 29 , 29 , 42 }{ 42 , 58 }

100

{ 100 }

0 1

10

0 1

1010

10

10

A (0110)

B (10)

C (1110)

D (1111)

E (110)

F (00)

G (0111)

H (010)

A G

8

5 3 7 8

15

C D

11

19

H 14

29

E

F

42

23 B

58

29

Page 43: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

ACEA 编码为 0110 1110 110 0110

如何译码?1. 从根结点出发,从左至右扫描编码,

2. 若为 ‘ 0’ 则走左分支,若为‘ 1’ 则走右分支,直至叶结点为止,3. 取叶结点字符为译码结果,返回重复执行 1,2,3 直至全部译完为止

1000 1

10

0 1

1010

10

10

A (0110)

B (10)

C (1110)

D (1111)

E (110)

F (00)

G (0111)

H (010)

A G

8

5 3 7 8

15

C D

11

19

H 14

29

E

F

42

23 B

58

29

A C E A

Page 44: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

第六章第六章 . . 平衡二叉树平衡二叉树 平衡二叉树或是一棵空树,或是具有下列性质的二叉排序树:

( 1)它的左子树和左子树的深度之差的绝对值不超过 1。

( 2)它的左、右子树都是平衡二叉树。

1. 平衡二叉树和不平衡二叉树

13

171411

1612

15 13

11

12

14

15

16

17(a) (b)

1

-1 -1

0 1 0

0

-2

-1 -3

0 -2

-1

0

Page 45: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

在算法中,通过平衡因子来具体实现上述平衡二叉树的定义。平衡二叉树中每个结点设有一个平衡因子域,其中存放的是该结点的左子树深度减去右子树深度的值,称为该结点的平衡因子。从平衡因子的角度可以说,若一棵二叉树中所有结点的平衡因子取值均为 1 、 0 或 -l ,则该二叉树称为平衡二叉树。

Page 46: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2.平衡二叉树的调整

若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。

失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于 1的结点作为根的子树。假设用 A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。

Page 47: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

( 1) LL型平衡旋转法

由于在 A的左孩子 B的左子树上插入结点 F,使 A的平衡因子由 1增至 2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子 B向右上旋转代替A作为根结点,A向右下旋转成为 B的右子树的根结点。而原来 B的右子树则变成 A的左子树。

1

0 0

0 0DC

EB

A

DC

EB

A

F

2

1 0

1 0

0

DF

AC

B

E

0

1 0

0 0 0

插入前 插入后 调整后

Page 48: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

( 2) RR型平衡旋转法

由于在 A的右孩子 C 的右子树上插入结点 F,使 A的平衡因子由 -1 减至 -2而失去平衡。故需进行一次逆时针旋转操作。即将 A的右孩子 C向左上旋转代替 A作为根结点,A向左下旋转成为 C的左子树的根结点。而原来 C的左子树则变成 A的右子树。

-1

0 0

0 0

F

-2

0 -1

-10

0

DB

EA

C

F

0

0 -1

0 0 0

插入前 插入后 调整后

B

A

D E

C B

A

D E

C

Page 49: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

( 3) LR型平衡旋转法

由于在 A的左孩子 B的右子数上插入结点 F,使 A的平衡因子由 1增至 2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子 B的右子树的根结点 D向左上旋转提升到 B结点的位置,然后再把该D结点向右上旋转提升到 A结点的位置。即先使之成为 LL 型,再按 LL 型处理。

1

0 0

0 0DC

EB

A

DC

EB

A

F

2

-1 0

0 -1

0

FC

AB

D

E

0

1 0

0 0 0

插入前 插入后 调整后

Page 50: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

( 4) RL型平衡旋转法

由于在 A的右孩子 C的左子树上插入结点 F,使 A的平衡因子由 -1 减至 -2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将 A结点的右孩子 C的左子树的根结点 D向右上旋转提升到 C结点的位置,然后再把该 D结点向左上旋转提升到 A结点的位置。即先使之成为 RR型,再按 RR型处理。

-1

0 0

0 0

F

-2

0 1

01

0

FB

CA

D

E

0

0 -1

0 0 0

插入前 插入后 调整后

B

A

D E

C B

A

D E

C

Page 51: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

例: 将一组记录的关键字按 4, 5, 7, 2, 1, 3, 6次序进行插入,生成及调整成平衡二叉树的过程。结点旁的数字是该结点的平衡因子。

(a) 插入结点4,5,7后,需进行RR型调整

RR

-2

4

5

-1

7

0

74

5

0

0 0

1

0 0

0 0

41

2 7

5

(b) 插入结点2,1后,需进行LL型调整

LL

74

5

1

2

2

2 0

1

0

Page 52: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

(c) 插入结点3后,需进行LR型调整

34

LR

1

2 7

5

3

2

-1 0

0 1

0

0

0 -1

0 0 0

7

4

1

2 5

(d) 插入结点6后,需进行RL型调整

RL

-1

6

7

1

0

31

4

2 5

0 -2

0 0

31 5 7

0 0 0 0

4

6

0

0 0

2

Page 53: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

平衡二叉树上进行查找算法分析

在平衡二叉树上进行查找的过程与二叉排序树的查找算法相同。因此,在查找过程中和关键字进行比较的次数不超过树的深度。含有 n个结点的平衡树的最大深度为:

因此,在平衡树上进行查找的时间复杂性为 O(logn)。

2))1(5(log n

Page 54: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

6.1 B-6.1 B- 树树 B- 树又称为多路平衡查找树。

B- 树中所有结点的孩子结点最大值称为 B- 树的阶,通常用m表示。从查找效率考虑,一般要求m≥3。一棵m阶的 B- 树或者是一棵空树,或者是满足下列要求的m叉树:

( 1)根结点或者为叶子,或者至少有两棵子树,至多有m棵子树。

( 2)除根结点外,所有非终端结点至少有「m/2棵子树,至多有m棵子树。

( 3)所有叶子结点都在树的同一层上。

( 4)每个结点的结构为:

(n, A0 , K1 , A1 , K2 , A2 ,… , Kn , An)

其中, Ki(1≤i≤n)为关键字,且 Ki<Ki+1(1≤i≤n-1)。

Ai(0≤i≤n)为指向子树根结点的指针。且 Ai 所指子树所有结点中的关键字均小于 Ki+1 。 An 所指子树中所有结点的关键字均大于 Kn 。

n为结点中关键字的个数,满足≤ n≤m-1。

Page 55: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

下图是一棵 3阶 B- 树,m=3。它满足:

( 1)每个结点的孩子个数小于等于 3。

( 2)除根结点外,其他结点至少有 =2个孩子。

( 3)根结点有两个孩子结点。

( 4)除根结点外的所有结点的 n大于等于 =1,小于等于m-l=2。

( 5)所有叶结点都在同一层上。

1 100

1 30 2 60 80 2 110 130 2 160 200^ ^^ ^ ^ ^ ^ ^ ^ ^ ^

1 50 1 150

root

A

B

D E F G

C

Page 56: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

1 . B- 树的查找 在 B- 树中查找给定关键字的方法类似于二叉排序树上的查找。

不同的是在每个记录上确定向下查找的路径不一定是二路(即二叉)的,而是 n+l路的。

在 B- 树上进行查找的过程是一个顺指针查找结点和在结点的关键字中进行查找交叉进行的过程。

B- 树存储结构 C语言描述:#define m 3 //B-树的阶,应根据实际情况确定 typedef int KeyType; //关键字类型typedef struct node{ //结点数据类型 int n; //结点中关键字的个数 KeyType key[m+1]; //关键字数组为 key[1..n] , key[0] 不用

struct node *parent; // 指向双亲结点 struct node *son[m+1]; // 孩子指针数组 son[0..n]

}BTREE;

Page 57: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

BTREE *Bsearch(BTREE *root,KeyType k,int *pos,BTREE **p)

{ BTREE *q=root; int i;

*p=NULL;

while(q){

q->key[0]=k;

for(i=q->n;k<q->key[i];i--);

if(i>0 && q->key[i]==k){

*pos=i; return q; }

*p=q; //*p为 q的双亲结点

q=q->son[i]; //继续在第 i棵子树上查找

}

return NULL;

}

在 B- 树 root中非递归查找关键字 k。成功时函数返回找到的结点的地址

Page 58: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

2. B- 树的插入插入的过程分两步完成:

( 1)利用前述的 B- 树的查找算法查找关键字的插入位置。若找到,则说明该关键字已经存在,直接返回。否则查找操作必失败于某个最低层的非终端结点上。

( 2)判断该结点是否还有空位置。即判断该结点的关键字总数是否满足 n<m-1。若满足,则说明该结点还有空位置,直接把关键字 k插入到该结点的合适位置上。若不满足,说明该结点己没有空位置,需要把结点分裂成两个。

分裂的方法是:生成一新结点。把原结点上的关键字和 k按升序排序后,从中间位置(即之处)把关键字(不包括中间位置的关键字)分成两部分。左部分所含关键字放在旧结点中,右部分所含关键字放在新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。如果父结点的关键字个数也超过(m-1 ),则要再分裂,再往上插。直至这个过程传到根结点为止。

Page 59: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

在 B- 树中插入关键字的过程

60 80

(a) 插入40后示意图 (b) 插入70后示意图

100

50 150

30 40 110 130 160 200 30 40 80 110 130 160 20060

100

50 70 150

60

(c) 插入45后的临时结果 (d) 插入45的最终结果

100

40 50 70 150

30 110 130 160 200 30 80 110 130 160 20060

50 100

40 150

8045

70

45

Page 60: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

3. B- 树的删除在 B- 树上删除关键字k的过程分两步完成:

( 1)利用前述的 B- 树的查找算法找出该关键字所在的结点。然后根据 k所在结点是否为叶子结点有不同的处理方法。

( 2 )若该结点为非叶结点,且被删关键字为该结点中第 i个关键字 key[i],则可从指针 son[i]所指的子树中找出最小关键字 Y。代替 key[i]的位置,然后在叶结点中删去 Y。

因此,把在非叶结点删除关键字 k的问题就变成了删除叶子结点中的关键字的问题了。

Page 61: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

在 B- 树叶结点上删除一个关键字的方法是

首先将要删除的关键字 k直接从该叶子结点中删除。然后根据不同情况分别作相应的处理,共有三种可能情况:

( 1)如果被删关键字所在结点的原关键字个数 n> ,说明删去该关键字后该结点仍满足 B- 树的定义。这种情况最为简单,只需从该结点中直接删去关键字即可。

( 2)如果被删关键字所在结点的关键字个数 n等于 ,说明删去该关键字后该结点将不满足 B- 树的定义,需要调整。

调整过程为:如果其左右兄弟结点中有“多余”的关键字。则可将右(左)兄弟结点中最小(大)关键字上移至双亲结点。而将双亲结点中小(大)于该上移关键字的关键字下移至被删关键字所在结点中。

12/ m

12/ m

Page 62: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

( 3)如果左右兄弟结点中没有“多余”的关键字,这种情况比较复杂。需把要删除关键字的结点与其左(或右)兄弟结点以及双亲结点中分割二者的关键字合并成一个结点。如果因此使双亲结点中关键字个数小于 ,则对此双亲结点做同样处理。以致于可能直到对根结点做这样的处理而使整个树减少一层。

12/ m

Page 63: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

在如图( a)所示的 3阶 B- 树中删除 75,52,55,30四个关键字

(a) 3阶B-树 (b) 从(a)删除75后

50

24 55 93

63 755 30 52 98

50

24 55 93

63 5 30 52 98

(c) 从(a)删除52以后 (d) 从(c)删除55后

50

24 63 93

755 30 55 98

50

24 93

5 30 63 75 98

(e) 从(d)删除30以后

50 93

5 24 9863 75

Page 64: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

6.2 B +树       B+ 树是 B- 树的变体,也是一种多路搜索树:       1. 其定义基本与 B- 树同,除了:       2. 非叶子结点的子树指针与关键字个数相同;       3. 非叶子结点的子树指针 P[i] ,指向关键字值属于 [K[i], K[i+1]) 的子树( B- 树是开区

间);       4. 为所有叶子结点增加一个链指针;       5. 所有关键字都在叶子结点出现; 如:(M=3)

Page 65: 补充知识 1 :二叉树的存储结构  ( 顺序、链式 )

B+ 基本特性   B+ 的搜索与 B- 树也基本相同,区别是 B+ 树只有达到叶子结点

才命中( B- 树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找 .( 由于 B +与前面讲的 B- 树大致相同,因而不在此重复讲解,请同学们参照 lecture 15)

     B+ 的特性:       1. 所有关键字都出现在叶子结点的链表中(稠密索引),且

链表中的关键字恰好是有序的;       2. 不可能在非叶子结点命中;       3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子

结点相当于是存储(关键字)数据的数据层;       4. 更适合文件索引系统;