86
1. 树树树树树树树 2. 树树树 : 树树 树树树 树树 、、 3. 树树树树树树 4. 树树树树树树树树树树 *5. 树树树树树 6. 树树树树树树树树树 7. 树树树树 第第第 第第第第第

第五章 树及二叉树

  • Upload
    tanuja

  • View
    94

  • Download
    0

Embed Size (px)

DESCRIPTION

第五章 树及二叉树. 1 . 树的定义和术语 2 . 二叉树 : 定义、性质、存储 3 . 二叉树的遍历 4. 二叉树遍历的迭代器类 * 5. 中序穿线树 6. 最优二叉树及其应用 7. 树和森林. 高度为 4 、度为 3 的树. A. B. C. D. L. E. F. G. H. I. 树和森林. 树 : n > 0 个结点的集合,根 + 其余结点分为 m >= 0 个集合,每一个集合本身又是一棵树(子树) 结点的 度: 该结点的子树数目 树的度: 树中各结点度数的最大值 叶子、父结点、儿子结点、兄弟结点 - PowerPoint PPT Presentation

Citation preview

Page 1: 第五章  树及二叉树

1. 树的定义和术语 2. 二叉树 : 定义、性质、存储 3. 二叉树的遍历 4. 二叉树遍历的迭代器类 *5. 中序穿线树 6. 最优二叉树及其应用 7. 树和森林

第五章 树及二叉树

Page 2: 第五章  树及二叉树

树和森林

•树: n > 0 个结点的集合,根 + 其余结点分为 m >= 0 个集合,每一个集合本身又是一棵树(子树)•结点的度:该结点的子树数目•树的度:树中各结点度数的最大值•叶子、父结点、儿子结点、兄弟结点•祖先结点:从根结点到该结点的路径上所有结点•层次、高度:根为 1, 依次往下数•有序树:规定所有结点的儿子结点次序,否则为无序树•森林: m >= 0 棵互不相交树的集合

其它表示方法:

1. ( A(B(L,E),C(F),D(G(I),H))

2. 类似于书籍的目录表示法。

高度定义为层数或层数- 1 ,都可以;本书定义为层数。

A

B C D

E F G H

I

L

高度为 4 、度为 3 的树

Page 3: 第五章  树及二叉树

树和森林

ADT 5.1: 树的 ADT

数据及关系 :具有相同数据类型的数据元素或结点的有限集合。树 T 的二元组形式为:

T =( D , R )

其中 D 为树 T 中结点的集合, R 为树中结点之间关系的集合。 D = {Root}∪DF

其中, Root 为树 T 的根结点, DF 为树 T 的根 Root 的子树集合。 R = {<Root , ri> , i = 1 , 2 ,…, m}

其中, ri 是树 T 的根结点 Root 的子树 Ti 的根结点。操作 :

Constructor :前提:已知根结点的数据元素之值。结果:创建一棵树。

Getroot:前提:已知一棵树。 .

结果:得到树的根结点。FirstChild :

前提:已知树中的某一指定结点 p 。结果:得到结点 p 的第一个儿子结点。

NextChild :前提:已知树中的某一指定结点 p 和它的一个儿子结点 u 。结果:得到结点 p 的儿子结点 u 的下一个兄弟结点 v 。

Page 4: 第五章  树及二叉树

例:结点总数为 3 时的所有二叉树的树的形状

二叉树的定义空或有一个根,根有左子树、右子树;而左右子树本身又是二叉树。

和树的区别:可为空

左右子树有序,不可颠倒

B

C

DE F

L

例: 二叉树

Page 5: 第五章  树及二叉树

二叉树的性质

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

B

C

DE F

L

A 1 层:结点个数 21-1=20 个

2 层:结点个数 22-1=21 个

3 层:结点个数 23-1=22 个

证: k = 1 时成立,设 k = i-1 时成立 则当 k = i 时在二叉树的第 i 层上至多有

2 i-1-1 * 2 = 2 i-1 个结点

•性质 2 :高度为 k 的二叉树至多有 2 k- 1 个结点

证:利用性质 1 ,将第 1 层至第 k 层的最多的结点数进行相加:

1 + 2 + 22 + ………… + 2k-2 + 2k-1 = 2k - 1

•性质 3 :二叉树的叶子结点数 n0 等于度为 2 的结点数 n2 + 1

证:设度为 1 的结点数为 n1 ,树枝的总数为 B 则: B = n-1=n0+n1+n2-1

又 B = n1+2×n2 ; 原命题得证。

C

E F

L

A

Page 6: 第五章  树及二叉树

完全二叉树

B

C

DE F

L

A

P SQ R U

B

C

DE F

L

A

P SQ R XU WV

•满二叉树 :

每层结点

数最多

•完全二叉树 :

1 、满二叉树

2 、从满二叉树最底层从右向左依次去掉若干结点形成的

性质 4 :具有 n 个结点的完全二叉树高度为 log2n + 1

证:根据性质 2 和完全二叉树的定义:设其高度为 k

2k-1-1< n <= 2k -1

2k-1 < n + 1 <= 2k

2k-1 <= n < 2k

故: k-1 <= log2n < k ; 原命题得证。

Page 7: 第五章  树及二叉树

完全二叉树

B

C

DE F

L

A

P SQ R U

性质 5 :对一棵有 n 个结点的完全二叉树按照从第一层(根所在的层次〕到最后一层,并且

每一层都按照从左到右的次序进行编号。根结点的编号为 1 ,最后一个结点的编号

为 n 。

1 :对任何一个编号为 i 的结点而言,它的左儿子的编号为 2i( 若 2i <= n) , 而右儿子的 编号为 2i+1( 若 2i +1 <= n) 。

2 :对任何一个编号为 j 的结点而言,它的父亲结点的的编号为 j/2 。根结点无父结 点。

证:对编号归纳

12111098

7654

32

1

Page 8: 第五章  树及二叉树

二叉树的顺序存储

一般情况:

A

B

C

DE F

G

HI

L

A 1 -1

B 9 2

C 5 3

D 6 -1

E -1 -1

F -1 -1

G 8 7

H -1 -1

I -1 -1

L -1 4

0

1

2

3

4

5

6

7

8

9

right leftdata

应用范围:适用于二叉树上的结点个数已知,或不支持动态存储分配的高级语言。

Page 9: 第五章  树及二叉树

二叉树的顺序存储

特殊情况:完全二叉树

A 2 3

B 4 5

C 6 7

D 8 9

E 10 0

F 0 0

G 0 0

H 0 0

I 0 0

L 0 0

0

1

2

3

4

5

6

7

8

9

10

right leftdata

应用范围:适用于完全二叉树,且结点个数已知。

D

C

GE F

B

A

H I L

A

B

C

D

E F

G

H

I

0

1

2

3

4

5

6

7

8

9

L

Page 10: 第五章  树及二叉树

二叉树的顺序存储

特殊情况:若不是完全二叉树,许多数据场为空,不合算。如下例所示:

考虑浪费空间最多的情况,是一种什么情况? 浪费 2^k - 1 – k 个单元

A

B

D

∧ ∧

H

I

0

1

2

3

4

5

6

7

8

9

D

B

A

H I

Page 11: 第五章  树及二叉树

二叉树的链接存储

仅定义结点的类型即可。结点之间的关系通过指针实现。

A

B

C

DE F

G

HI

L

data left right

标准形式:(二叉链表)

广义标准形式:(三叉链表)

data left right Parent

Page 12: 第五章  树及二叉树

二叉树的链接存储例:

A

∧B

/\∧

∧∧

∧∧

C

D E

F G

GF

D

CB

A

E

Page 13: 第五章  树及二叉树

二叉树的 ADT

template <class Type> class BinaryTree; // 二叉树类 BinaryTree 的向前说明template <class Type> class BinaryNode { friend class BinaryTree < Type>; public: BinaryNode ( ) : left(NULL), right(NULL) { } // 二叉树结点的构造函数。

BinaryNode ( Type item, BinaryNode < Type> * L = NULL, BinaryNode < Type> * R = NULL ): data(item),left(L), right( R) { }

~ BinaryNode ( ) { } Type GetData ( ) const { return data; } // 得到二叉树结点的数据值。 BinaryNode<Type> * GetLeft( ) const { return left;}

// 得到二叉树结点的左儿子地址。 inaryNode<Type> * GetRight( ) const { return right; }

// 得到二叉树结点的左儿子地址。 void SetData ( const Type & item ) { data = item; }

// 设置二叉树结点的数据值。 void SetLeft (BinaryNode < Type> * L ) { left = L; }

// 设置二叉树结点的左儿子地址。 void SetRight (BinaryNode < Type> * R ) { right = R; }

// 设置二叉树结点的右儿子地址。

Page 14: 第五章  树及二叉树

二叉树的 ADT

template <class Type> class BinaryTree; // 二叉树类 BinaryTree 的向前说明

template <class Type> class BinaryNode { friend class BinaryTree < Type>; public:

void PrintPreOrder( ) const; // 按前序打印二叉树的结点的数据值。 void PrintPostOrder( ) const; // 按后序打印二叉树的结点的数据值。 void PrintInOrder( ) const; // 按中序打印二叉树的结点的数据值。 BinaryNode<Type> * Duplicate( ) const; // 复制以本结点为根的子树。private: BinaryNode < Type> * left , * right ; // 结点的左、右儿子的地址 Type data; // 结点的数据信息};

Page 15: 第五章  树及二叉树

二叉树的 ADT

template <class Type> class BinaryTree {public: BinaryTree( ) : root( NULL) { } // 构造空二叉树 BinaryTree ( const Type & value ) { root = new BinaryNode<Type> ( value ); } ~BinaryTree ( ) { DelTree ( root ); } int IsEmpty ( ) const { return root == NULL; } // 二叉树为空,返回非 0 ,否则为 0 。 const BinaryNode<Type> * Getroot( )const { return root; } int MakeEmpty ( ) { DelTree( root); root == NULL; } // 使二叉树为空。 const BinaryTree<Type> & operator = ( const BinaryTree<Type> & T);private: BinaryNode<Type> * root; // 二叉树的根结点。 BinaryTree( const BinaryTree<Type> & ); void DelTree( BinaryNode<Type> * T );};template <class Type> const BinaryTree<Type> & BinaryTree<Type> :: operator = ( const

BinaryTree<Type> & T ) { if ( this != &T ) { DelTree(root); // 其具体实现,见程序 5.1 。 if ( T.root != NULL ) root = T.root->Duplicate( );

}}

Page 16: 第五章  树及二叉树

二叉树的 ADT

template <class Type>

Type Max( const Type u, const Type v )

{ if ( u > v ) return u; else return v;}

template <class Type>

int BinaryNode < Type> :: Size ( const BinaryNode <Type> * T ) const {

// 得到以 T 为根的二叉树或子树的结点个数。

if ( T == NULL ) return 0;

else return 1 + Size( T->left ) + Size( T->right);

}

template <class Type>

int BinaryNode < Type> :: Height ( const BinaryNode < Type> * T ) const {

// 得到以 T 为根的二叉树的高度。

if ( T == NULL ) return 0;

else return 1 + Max( Height( T->left ),Height( T->right));

}

· 二叉树的 ADT :求二叉树的结点个数和高度以及删除一棵二叉树。

Page 17: 第五章  树及二叉树

二叉树的 ADT

template <class Type>

void BinaryTree<Type> :: DelTree ( BinaryNode < Type> * T ) {

// 删除以 T 为根的二叉树的所有结点。

if ( T != NULL ) {

DelTree( T->left);

DelTree( T->right);

delete T;

}

}

· 二叉树的 ADT :求二叉树的结点个数和高度以及删除一棵二叉树。

Page 18: 第五章  树及二叉树

二叉树遍历设 N 代表根节点, L 代表左子树, R 代表右子树。

a. 前序(或先序):如果二叉树为空,则操作为空:否则访问根结点;前序遍历左子树;

前序遍历右子树。记为: NLR 。

b. 中序: 如果二叉树为空,则操作为空:否则中序遍历左子树;访问根结点;

中序遍历右子树。记为: LNR 。

c. 后序: 如果二叉树为空,则操作为空:否则后序遍历左子树;后序遍历右子

树;访问根结点。记为: LRN 。 前序: A 、 L 、 B 、E 、

C 、 D 、 W 、X

B

C

DE

L

A

X

W

L

R

L

R

R

L

R中序: B 、 L 、 E 、A 、

C 、 W 、 X 、D后序: B 、 E 、 L 、X 、

W 、 D 、 C 、A

B

C

DE

L

A

X

W

Page 19: 第五章  树及二叉树

二叉树遍历

B

C

DE

L

A

X

W

a. 前序分析:结点的左儿子、左孙子、左后代、…… 将连续输出。结点的右儿子将在结点、结点的左 子树全部输出之后才输出。

b. 中序分析:最先输出的结点是根结点的最左的左后代。将二叉树中的结点投影到水平轴线上,则得到 中序遍历的序列。

c. 后序分析:根结点(或子树的根结点)将在它的左、右子树的结点输出之后。因此,根结点(或子树 的根结点)在后序序列中的序号等于它的左右子树的结点个数 +左右子树中的最先被

访问 的结点的序号。注意,结点、右父亲、右祖父、…… 将连续输出。

前序: A 、 L 、 B 、 E 、 C 、 D 、W 、 X中序: B 、 L 、 E 、 A 、 C 、 W 、X 、 D后序: B 、 E 、 L 、 X 、 W 、 D、 C 、 A

B

C

DE

L

A

X

W

B L E A C W X D

Page 20: 第五章  树及二叉树

二叉树遍历的迭代器类• 二叉树的迭代器: Tree Iterator

ADT 5.3: 二叉树的迭代器类。template <class Type> class TreeIterator {public: TreeIterator ( const BinaryTree < Type > & BT ) : T( BT ), current( NULL) { } virtual ~TreeIterator ( ) { } virtual void First ( ) = 0; // 第一个被访问的结点地址送 current virtual void operator ++ ( ) = 0; // 下一个被访问的结点地址送 current int operator + ( )const{ return current != NULL;}// 当前结点为空吗,非空返回 True const Type & operator ( ) ( ) const; // 返回当前结点指针 current 所指向的结点的数据值。protected: const BinaryTree <Type > & T; const BinaryNode<Type > * current; // 指向当前结点的指针。private: TreeIterator ( const TreeIterator & ) { } const TreeIterator & operator = ( const TreeIterator & ); };template <class Type>

const Type & TreeIterator <Type> :: operator ( ) ( ) const {

Exception( current == NULL, “This node is NULL!” );

return current->GetData( );

}

Page 21: 第五章  树及二叉树

非递归前序遍历

B

C

DE

L

A

•前序的程序实现(非递归):

1 、根结点进栈

2 、结点出栈,被访问

3 、结点的右、左儿子(非空)进栈

4 、反复执行 2 、 3 ,至栈空为止。

前序: A 、 L 、 B 、 E 、 C、 D

e.g:

<A> <C>

<L>

<C>

<E>

<B>

<C>

<E>

<C> <D><C>

A出栈访问

L出栈访问

B出栈访问

E出栈访问

C出栈访问

D出栈访问后,栈空结束

A 进栈 C 、 L

进栈

E 、 B

进栈 D 进栈

分析: p 的前序的直接后继 q: 1 、 p 有左儿子,则 q= 该左儿子; 2 、 p 无左儿子,有右儿子,则 q= 该右儿子; 3 、 p 无左儿子、右儿子 ,搜索其祖先结点的右儿子送 q 。找不到结束。

Page 22: 第五章  树及二叉树

前序遍历的迭代器• 遍历二叉树: Tree Iterator :前序的实现。 template <class Type> class Preorder : public TreeIterator < Type > {public: Preorder( const BinaryTree < Type > & R ); ~Preorder( ) { } void First( ); void operator ++ ( ); void Preorder_NLR ( );protected: Stack < const BinaryNode <Type > * > s;}; template <class Type> Preorder<Type> :: Preorder( const BinaryTree <Type> & R )

: TreeIterator<Type > ( R ) { s.Push( T.Getroot( ) ); }template <class Type> void Preorder <Type> :: First ( ) {

s.MakeEmpty ( ); if ( T.Getroot( ) ) s.Push( T.Getroot( ) );

operator ++( );

} // 堆栈清空。若二叉树 T 非空,则根结点进栈,并得到当前结点。

Page 23: 第五章  树及二叉树

前序遍历的迭代器• 遍历二叉树: Tree Iterator :前序的实现。

template <class Type> void Preorder <Type> :: operator ++ ( ) { if ( s.IsEmpty() ) { if ( current == NULL )

{ cerr << “Advanced past end ” << endl; exit( 1 ) }; current = NULL; return; } current = s.Top( ); s.Pop( ); // 得到当前结点的地址,并进行出栈操作。 if ( current ->GetRight ( ) != NULL ) s.Push( current->GetRight( ) );

// 非空右儿子进栈。 if ( current ->GetLeft ( ) != NULL ) s.Push( current->GetLeft( ) );

// 非空左儿子进栈。}template <class Type> void Preorder < Type > :: Preorder_NLR ( ) { First( ); // 将当前指针 current 指向根结点。 while( operator +( ) ) { // 当前指针 current 非空时继续执行。 cout << operator()() << endl; // 输出当前结点的数据场之值。 operator ++( ); // 使前序次序下的下一个结点成为当前结点。 } }

Page 24: 第五章  树及二叉树

非递归后序遍历• 遍历二叉树:设根结点初始时非空

•后序算法: 1 、结点(初始时为根)进栈(标志 0 ),沿左指针查找左儿子(标志 1 )

2 、左儿子非空,返回 1 。

3 、退栈。若为标志 1转 4 ,否则标志 2 ,转 5 。

4 、进栈转向右子树(标志 2 ),如右儿子非空,则转向 1 ;空转向 3 。

5 、访问该结点,返回 3

6 、反复 1 到 5 ,至栈空为止。 e.g:

C

DE

L

A

后序: E 、 L 、 D 、 C、 A

分析: 1 、二叉树最先被访问的结点是二叉树中最左方的叶子。

2 、 p 的后序的直接后继 q: 1 、 p 是父结点的右儿子,则 q= 该父结点。 2 、 p 是父结点的左 儿子,如果有右子树,则 q= 该右子树中最左的叶子。如果,无右子树, q= 父结点。

3 、 p 无父结点,那么 q == NULL 。

Page 25: 第五章  树及二叉树

非递归后序遍历• 后序遍历时的实现实例;注意堆栈中保存的是当前结点的祖先。

<A>

0 <A>

1 <A>

1<B>

0 <B>

1<D>

0

<A>

1<B>

2<D>

1

<A>

1<B>

2

<D>

2

<A>

1<B>

2<A>

1<B>

2<A>

1 <A>

2<C>

0<E>

0

<A>

2<C>

1

<E>

1

<A>

2<C>

1<E>

2

<A>

2<C>

1<A>

2<C>

1<A>

2<C>

2<A>

2

(1) (2) (3) (4) (5)

(6) (7) (8) (9) (10)

(11) (12) (13) (14) (15)

D

CB

E

A

Page 26: 第五章  树及二叉树

后序遍历的迭代器• 遍历二叉树: Tree Iterator 后序的实现 template <class Type> struct StNode {const BinaryNode<Type > * Node;int TimesPop;StNode( const BinaryNode <Type > * N = NULL ): Node(N), TimesPop(0) { } };template <class Type> class Postorder : public TreeIterator < Type > { public:

Postorder( const BinaryTree < Type > & R );~Postorder( ) { }void First( ); // 后序遍历时的第一个结点的地址。void operator ++ ( ); // 后序遍历时的下一个结点的地址。

protected: Stack < StNode<Type> > s;};template <class Type> Postorder<Type> :: Postorder(const BinaryTree<Type> & R) : TreeIterator<Type> (R) { s.Push( StNode<Type>( T.Getroot( ) ) ); }template <class Type> void Postorder <Type> :: First ( ) { // 得到第一个访问的结点地址 s.MakeEmpty ( ); if ( T.Getroot( ) ) s.push ( StNode<Type>( T.Getroot( ) ); operator ++ ( ); } // 根结点地址及标志 0 进栈,去寻找第一个被访问的最左方的叶子。

Page 27: 第五章  树及二叉树

后序遍历的迭代器• 遍历二叉树: Tree Iterator 后序的实现

template <class Type> void Postorder <Type>:: operator ++ ( ) { if ( s.IsEmpty() ) { // 当栈空且 current 也为空时,遍历结束。 if ( current == NULL ) { cerr << “Advanced past end ” << endl; exit( 1 ); } current = NULL; return; } // 置当前指针为空,结束。 StNode<Type> Cnode; for ( ; ; ) { Cnode = s.Top( ); s.Pop( ); if ( ++ Cnode.TimePop == 3 ) { current = Cnode.Node; return; } // 其左右子树处理完毕 , 该结点可访问。 s.Push( Cnode ); if ( Cnode. TimesPop == 1 ) // 转向访问左子树。

{ if ( Cnode.Node -> GetLeft ( ) != NULL ) // 有左儿子,进栈。 s.Push(StNode<Type>( Cnode.Node -> GetLeft ( ) ) ); }

else { // Cnode.TimePop == 2 访左结束,转向访问右子树。 if ( Cnode.Node -> GetRight ( ) != NULL ) s.Push(StNode<Type>( Cnode.Node -> GetRight ( ) ) ); } }}

Page 28: 第五章  树及二叉树

非递归中序遍历

•中序的程序实现:

1 、结点(初始时是根结点)进栈,沿左指针查找左儿子。2 、若有左儿子,返回第一步。3 、若无左儿子,判栈空?空则结束。非空,栈顶结点出栈 访问。转向右子树,返回 1 。

e.g:

B

C

DE

L

A

X

W

中序: B 、 L 、 E 、 A 、 C 、 W 、X 、 D分析: 1 、二叉树最先被访问的结点是二叉树中最左方的

的左后代。

2 、 p 的中序的直接后继 q: 1 、 p 有右子树,则 q=p 的右子树的最左方的左后代。

2 、 p 无右子树,如 p 是父结点的左儿子,则 q= 该 父结点。

3 、 p 无右子树,如 p 是父结点的右儿子则以 p的父结点为根的子树访问完毕。如该子树为其祖先结点的左子树,那么 q= 其根。否则,继续寻找,找不到结束。

Page 29: 第五章  树及二叉树

中序遍历的迭代器程序 5.5: 中序遍历迭代器类template <class Type> class Inorder : public Postorder < Type > {public:

Inorder( const BinaryTree < Type > & R ) : Postorder<Type> (R) { }void operator ++ ( ); // 中序时的下一个结点的地址。

};template <class Type>void Inorder<Type>:: operator ++ ( ) { if ( s.IsEmpty() ) { // 当栈空且 current 也为空时,遍历结束。 if ( current == NULL ) {cerr<< “Advanced past end ”<<endl;exit(1);} Cnode = NULL; return; } // 置当前指针为空,结束。 StNode<Type> Cnode; for ( ; ; ) { current = s.Top( ); s.Pop( ); if ( ++Cnode. TimesPop == 2 ) // 其左子树处理完毕 , 该结点可访问。 { current = Cnode.Node; if ( Cnode.Node -> GetRight ( ) != NULL ) // 有右儿子,进栈。 s.Push(StNode<Type>( Cnode.Node->GetRight() ) ); return; }

s.Push( Cnode); if ( Cnode.Node -> GetLeft ( ) != NULL ) s.Push(StNode<Type>( Cnode.Node->GetLeft() ) );

}}

Page 30: 第五章  树及二叉树

中序穿线树

•为什么采用中序穿线树:二叉树中的空指针场多达 n + 1 个。•简单证明:设二叉树中结点的总数为 n ,度为 2 的结点总数为 n2 。空指针场用 方框代表。增加了方

框结点的二叉树称之为扩展二叉树。在该二叉树之中,原来的 n 个结点全部成为度为 2 的结

点,方框结点成为叶子。根据二叉树的性质 3 ,原命题得证。

e.g: n = 8

的二叉树

B

C

DE

L

A

X

W

B

C

DE

L

A

X

W

扩展二叉树

怎样利用这 n + 1 个空指针场?•中序穿线树•后序穿线树

Page 31: 第五章  树及二叉树

中序穿线树的链接存储·

•中序穿线树(中序中序穿线树)的实现:

在二叉树的结点的标准形式中,增加两个标志域,用于区别是否是穿线。如下所

示 : ( 注意:在顺序存储一颗二叉树时,可不必使用额外的空间;见下页)

其中 data 为数据场, leftThread = 0 时, leftchild 场指向本结点的真正的左儿子

leftThread = 1 时, leftchild 场指向本结点的按中序遍历序列的前驱结点 (穿线 )

rightThread = 0 时, rightchild 场指向本结点的真正的右儿子

rightThread = 1 时, rightchild 场指向本结点的按中序遍历序列的后继结点(穿线 ) e.g: n = 6

的二叉树

B

C

DE

L

A

中序穿线树

leftchild leftThread data rightThread rightright

B

C

DE

L

A无前驱结点 无后继结点

Page 32: 第五章  树及二叉树

中序穿线树的链接存储·

•中序穿线树中的结点的数据结构:

B

C

DE

L

A无前驱结点 无后继结点

0

0 0

0

0

1 1 1 1 1 1

1

A

L

B E

C

D

Root: 根结点指针

表示

1:穿线

0: 实际儿子

∧∧

Page 33: 第五章  树及二叉树

中序穿线树的顺序存储• 中序穿线树的结点的顺序存储结构:

2

3

4

8

7

6

5

1

0

C 0 6

A 2 5

L 3 4

B 0 -2

X -7 -6

W -5 8

D 7 0

E -2 -1

data left right

BD

L

A

X

W

空空 C

E

Page 34: 第五章  树及二叉树

中序穿线树的中序遍历•在中序穿线树上进行中序遍历、不用堆栈的的算法及程序要点:

1 、最先输出最左的左后代结点

2 、结点无右儿子,则该结点的中序的后继结点,由右儿子的指针场给出。但要注意以下情况:

3 、结点有右儿子,则该结点的中序的后继结点,是它的右子树中的最左的左后代结点。

4 、最先输出的结点无前驱结点,最后输出的结点无后继结点。

B

D

E

L

A

指向当前结点

指向后继结点

B

C

DE

L

A无前驱结点 无后继结点

Page 35: 第五章  树及二叉树

中序穿线树的中序遍历· 中序穿线树遍历的实现算法。

template <class Type>int InorderThreadTree<Type> :: First ( ) { current = root; if ( !current ) return current;// 为 0 是中序穿线二叉树空的标志。while( arr[current].left >0 ) current = arr[current].left; return current; // 在中序次序下第一个被访问的结点的下标地址。}

template <class Type>int InorderThreadTree<Type> :: Next ( ) { aftcurrent = arr[current].right;if ( aftcurrent <= 0 ) { current = - aftcurrent; return current; } // 为 0 是中序遍历结束的标志,否则是中序次序下的下一个结点的地址。while( arr[aftcurrent].left > 0 ) aftcurrent = arr[aftcurrent].left; current = aftcurrent; return current; // 在中序次序下的下一个被访问的结点的地址。}

template <class Type>

void InorderThreadTree<Type> :: Inorder ( ) {

int p = First( );

while( p != 0 ) { cout << arr[p].data << endl; p = Next( ); } return;

}

B

C

DE

L

A

无后继结点

root

Page 36: 第五章  树及二叉树

中序穿线树· 中序穿线树在中序穿线树上插入新的结点的操作 本书中建立的实际上是中序穿线二叉排序树,所以新插入结点时必须寻找他的插入位置,即其父结点。考虑新插入的结点为父结点的右儿子的情况。新插入的结点为父结点的左儿子的情况,类似。

B

C

E

D

A

B

C

E

D

A

新插入结点 D 作为叶子结点,其父结点的右儿子指针场应为非正数。

122

250

300110 200

99

105 230

216

S

U

E

D

Q

新插入结点 D 作为叶子结点,其父结点的左儿子指针场应为非正数。

S

U

E

D

Q

Page 37: 第五章  树及二叉树

中序穿线树template <class Type>Istream & operator >> (istream & in, InorderThreadTree<Type> & x){

int size, j, k = 2;Type ele;cout << “Enter size of your InorderThreadTree array! ” << endl;in >> size; if (size > x.MaxSize ) { cout << “Error: out of spaces!”; return in; }cout << “ Enter data of every element one by one!” << endl;in >> ele;if (ele == x.flag) { cout << “Input data end!” << endl; return in; }x.arr[1].setdata (ele); x.root = 1; // 根结点单独生成。while ( in >> ele, ele != x.flag) {

j = x.root; x.arr[k].setdata (ele);while(1 ) { if ( x.arr[k].data < x.arr[j].data )

if ( x.arr[j].left > 0) j = x.arr[j].left; else { x.arr[k].left = x.arr[j].left; x.arr[k].right = -j; x.arr[j].left = k; break;

} // 新结点 k 插入后为结点 j 的左儿子,是叶子。 else

if ( x.arr[j].right > 0) j = x.arr[j].right; else { x.arr[k].right = x.arr[j].right; x.arr[k].left = -j; x.arr[j].right = k; break;

} // 新结点 k 插入后为结点 j 的右儿子,是叶子。}k++;

}return in;}

Page 38: 第五章  树及二叉树

最优二叉树

• 路径长度:结点之间的树枝的总数• 树的路径长度:从根到每一结点的路径长度之和• 树的带权路径长度:叶子结点的带权路径长度之和。设有 n 片叶子,它们的权值分别

为 w1 、 w2 、…… .wn , 相应的路径长度分别为 L1 、 L2 、…… .Ln 。

则树的带权路径长度可记为: nWPL =∑ wklk k=1

E G HL

L E

H

G

E

G H

L

7

7

7

5 2 4 4

4

2

2

5

5

WPL=36WPL=46 WPL=35

• 最优二叉树或赫夫曼树:树的带权路径长度 WPL 最小的二叉树。

Page 39: 第五章  树及二叉树

赫夫曼算法

• 赫夫曼算法(产生最优二叉树的算法)的实现:

1 、给定一个具有 n 个权值 { w1,w2,………wn } 的结点的集合

F = { T1,T2,………Tn } 。

2 、初始时,设 A = F 。

3 、执行 i = 1 至 n-1 次循环,在每次循环时,做以下事情:

从当前集合中选取权值最小、次最小的两个结点,以 这两个结点作为内部结

点 bi 的左右儿子, bi 的权值为其左右儿子权值之和。在集合中删除这两个权

值最小、次最小的结点。这样,在集合 A 中,结点个数便减少了一个。 e.g: 权值(此处为使用概率)分别为 { 2, 7, 4, 5 } 的结点集合 F= { C, A, S, T } 已知。

利用赫夫曼算法产生最优二叉树。C, 2 A, 7 S, 4 T, 51 、 A=

b1,6 A, 7 T, 5

S, 4C, 2

2 、 A=b1,6

A, 7

S, 4C, 2

b2,11

T, 5

3 、 A=

b3,184 、 A=

Page 40: 第五章  树及二叉树

赫夫曼编码

• 赫夫曼算法用于通信中的字符的编码。权值为使用概率。赫夫曼树的左分支 标记为

0 ,而右分枝标记为 1 ;这从根到每一个叶子结点的路经上标记的字符组成的字符串,即为该字符的赫夫曼编码。

e.g: 权值(此处为使用概率)分别为 { 2, 7, 4, 5 } 的结点集合 F= { C, A, S, T } 已知。

请利用赫夫曼算法产生最优二叉树。

b1,6

A, 7

S, 4C, 2

b2,11

T, 5

b3,18

1

1

1

0

0

0

赫夫曼编码:

A : 0

T : 10

C : 110

S : 111

赫夫曼编码优点:

占用二进制位少

e.g: 左图发送长度为 n 的字符串,等长表示需 2n

个比特。因共有四个字符,表示每个字符需

二个 比特。

采用赫夫曼编码后,总的比特数 35n/18, 因:

A : 1*7n /18

T : 2*5n /18

S : 3*4n /18

C : 3*2n /18

Page 41: 第五章  树及二叉树

最小化堆简介

• 堆: n 个元素的序列 { k1 , k2 ,…… , kn } ,当且仅当满足以下关系时,称 之为堆:

ki <= k2i ki >= k2i

ki <= k2i+1 ki >= k2i+1

( i = 1,2, …… , n/2 )

且分别称之为最小化堆和最大化堆。

e.g: 序列 { 96,83,27,11,9} 最大化堆

{ 12 , 36 , 24 , 85 , 47 , 30 , 53 , 91} 最小化堆

96

911

83 27

2 3

1

4 5

12

4785

91

36 24

5330

6

2

7

3

1

8

4 5

Page 42: 第五章  树及二叉树

42

7

16

24 50

62

88

root

最小化堆的最小元素

77

Page 43: 第五章  树及二叉树

43

[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

16

62

24

50

88

77

7

1

16

2

24

4

50

5

62

3

88

6

root

77

7

最小化堆的顺序存储

Page 44: 第五章  树及二叉树

44

•建堆的时间耗费:比较次数 - 4n 次 O(n) 级 (课本第 241页)

7

1

16

2

24

4

50

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

16

62

24

50

88

77

建堆的复杂性

Page 45: 第五章  树及二叉树

45

50

1

16

2

24

4

7

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

16

62

24

7

88

77 叶子结点,符合堆的的定义。

符合堆的定义,不需调整。即最小化小堆已符合定义。建立的第一个小堆的堆顶的下标为 n/2

数组 h

建最小化堆

Page 46: 第五章  树及二叉树

46

50

1

16

2

24

4

7

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

16

62

24

7

88

77

不合堆的定义,需调整。建立以 L[ 2 ] 为堆顶的正确的最小化小堆。

数组 h

建最小化堆

Page 47: 第五章  树及二叉树

47

50

1

7

2

24

4

16

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

7

62

24

16

88

77

不合堆的定义,需调整。建立以 L[ 2 ] 为堆顶的正确的最小化小堆。

数组 h

建最小化堆

Page 48: 第五章  树及二叉树

48

50

1

7

2

24

4

16

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

7

62

24

16

88

77

不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最小化堆。

数组 h

建最小化堆

Page 49: 第五章  树及二叉树

49

7

1

50

2

24

4

16

5

62

3

88

6

77

7

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

50

62

24

16

88

77

不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最小化堆。

数组 h

建最小化堆

Page 50: 第五章  树及二叉树

50

建堆的复杂性

• 时间耗费的代价:建堆的时间耗费 + 排序的时间耗费• 建堆的时间耗费:设树根处于第 1 层,该堆共有 h 层。建堆从第 h-1 层开始进行。只

要知道了每一层的结点数(建的小堆的个数),和每建一个小堆所 需的比较次数,就可以求得建堆的时间耗费。• 建的小堆的性质:第 i 层上小堆的个数 = 第 i 层上结点的个数 = 最多 2i-1

第 i 层上小堆的高度 = h-i+1

建第 i 层上每个小堆最多所需的比较次数 = 2× ( h-i )次• 建堆的时间耗费:

T(n) <= ∑ 2i-1×(h-i)×2 = ∑ 2i×(h-i) = ∑ 2h-j×j

= 2h ∑ j/2j

注意:即使是 高度为 h 的完全的二叉树,满足以下式子:

2h - 1 <= n <= 2h-1 故: 2h <= 2n

又: ∑ j/2j < 2

所以:建堆的时间复杂性为 4n=O(n) 级

i=h-1i=h-1 j=1

h-1 1 1

j=1

h-1

j=1

10

3040

60 12

708

6

2 3

1

74 5

1

2

3=h

Page 51: 第五章  树及二叉树

51

最小化堆的 ADT

template < class EType> class MinHeap {public:

MinHeap( int sz ); // 堆的构造函数。MinHeap( EType arr[ ], int n, int sz ); // 带有堆的初始值的构造函数。~MinHeap( ) {delete [ ] heap; } // 堆的析构函数。void BuildHeap( int j ); // 建立最小化堆。void Insert( const EType & x ); // 结点插入最小化堆中的堆尾,并调整为最小化堆。void DeleteMin( EType & x ) ; // 删除最小化堆中的最小元素,即:堆顶结点。

// 删除之后,仍应调整为最小化堆。int IsEmpty( ) const { return CurrentSize == 0; } // 判堆空吗?int IsFull( ) { return CurrentSize == MaxSize; } // 判堆满吗,即单元用光吗?void Clear( ) { CurrentSize = 0; } // 置堆空,堆中没有一个元素。

private:EType * heap; // 保存堆的数组,即堆的存储空间。int CurrentSize; // 堆的当前结点的个数。int MaxSize; // 数组可以保存的结点的最大数目,即容量。void SiftDown( int j );

};

Page 52: 第五章  树及二叉树

52

最小化堆的 ADT

template < class EType> MinHeap<EType> :: MinHeap( int sz ) {

heap = new EType[sz + 1];

CurrentSize = 0;

MaxSize = sz;

} // 建立存储堆的数组 heap[1] 至 heap[sz] ,此时仍是空的堆。 sz 为数组的最大容量。

 

template<class EType> MinHeap<EType> :: MinHeap( EType arr[ ], int n, int sz ) {

Exception( sz < n, “MaxSize is less then CurrentSize!”);

CurrentSize = n;

MaxSize = sz;

heap = new EType[ sz + 1];

for ( int j = 1; j<=n; j++ ) heap[j] = arr[j]; // 对当前最小化堆进行赋值。 BuildHeap( );

} // 建立的最小化堆是 heap[1] 至 heap[n] 。 sz 为数组的最大容量。

Page 53: 第五章  树及二叉树

53

建立最小化堆的主要函数 SiftDown 及 BuildHeap 的实现:template < class EType>

void MinHeap<EType> :: SiftDown( int j ) {

// heap[1], …,heap[n] 为存储堆的数组。 heap[0] 不用。 int MinSon; // 用于记录小儿子的下标地址。

heap[0] = heap[j]; // 暂时存放 heap[ j ] 至 heap[ 0 ] 。 for ( ; j*2 <= CurrentSize; j = MinSon) {

MinSon = 2 * j;

if ( MinSon != CurrentSize && heap [MinSon+1 ] < heap [MinSon ] ) MinSon++;

if ( heap[MinSon] < heap[0] ) heap[j] = heap[MinSon];

else break;

}

heap [j ] = heap[0]; // 取回 heap[ j ] 之值。}

 

template < class EType>

void MinHeap<EType> :: BuildHeap( ) {

for ( int j = CurrentSize/2; j > 0; j-- ) SiftDown(j);

}

最小化堆的 ADT

Page 54: 第五章  树及二叉树

54

最小化堆• 利用最小化堆挑选最小和次最小的结点 ( 总共 n-1趟 ) 的总的代价分析: O ( n logn )

考虑最大比较次数: 3 log(n-1) + 5( log(n-2) + log(n-2) +…… log2 )

考察: 5log(n-1) + 5( log(n-2) + log(n-2) +…… log2 ) - 2log(n-1) 的时间代价即可。• 该代价如何得出,请看下例。注意:以下实例是以一趟挑选最小和次最小的结点为例的。

以 n=8 个结点集合为例,它们的权值分别为: {91 , 36 , 24 , 85 , 47 , 30 , 53 ,12}

12

4785

91

36 24

5330

6

2

7

3

1

8

4 5

创建的最小化堆

91

4785

12

36 24

5330

6

2

7

3

1

8

4 5

选出最小结点、删除

24

4785

12

36 30

5391

6

2

7

3

1

8

4 5

调整剩下的结点仍成为最小化堆

53

4785

12

36 30

2491

6

2

7

3

1

8

4 5

30

4785

12

36 53

2491

6

2

7

3

1

8

4 5

30

4785

36 53

3691

6

2

7

3

1

4 5

代价 2 log(n-1)

代价 2 log(n-2)

最大的代价 log (n-1)

Page 55: 第五章  树及二叉树

55

最小化堆• 在赫夫曼算法中,利用最小化堆挑选最小值和次最小值的结点的总的代价的说明。 n=8 个结点集合为

例,它们的权值分别为: { 91 , 36 , 24 , 85 , 47 , 30 , 53 , 12 }

趟数 选最小值之后进行 选次最小值之后 得到中间结点 加入中间结点后 调整的比较次数 调整的比较次数 后结点总数 调整的比较次数 1 、 2 log (n-1) 2 log (n-2) (n-1) log (n-1)

2 、 2 log (n-2) 2 log (n-3) (n-2) log (n-2)

n-2 、 2 log 2 仅一个结点 2 log 2 无需比较

n-1 、 无需比较 无需比较 1 只有一个结点

Page 56: 第五章  树及二叉树

56

最小化堆• 利用最小化堆挑选最小和次最小的结点的代价分析: O ( n logn )

只要求得 log(n-1) + log(n-2) + log(n-2) +…… log2 的级别为 O ( n logn ) 即可。

X轴

Y轴

求 y=logx 的积分的示意图2

1

2 3 4

y=logn

1

n-1 n

j=2

n-1 n

2log(n-1)! = Σ logj ≤ logx dx 不难求出上述积分的值为: nlogn-nloge+2loge - 2 ;命题得证。

1

Page 57: 第五章  树及二叉树

赫夫曼算法的实现赫夫曼算法的实现:使用最小化堆挑选出最小的结点。

template <class Type>void BestBinaryTree ( Type weight[ ], int n, node<Type> BestBinaryTree[ ], int m) { // weight[1] 到 weight[n] 保存权值, weight[0]不用。 BestBinaryTree[1]到 // BestBinaryTree[2n-1] 保存最优二叉树, BestBinaryTree[0 ]不用, m 应为 2n-1 。 node< Type > * minptr = new node<Type>; // 暂存最小值用。 MinHeap< node < Type > > MinHp(n); // 建立最小化堆,容量为 n 个单元, 0 号单元不用。 int j,k = m;for ( j = 1; j <= n; j++ )

{ minptr->setdata( weight[j]); minptr->setorder(j); MinHp.Insert( *minptr); }for ( j = n+1; j <= m; j++ ) { MinHp.DeleteMin ( *minptr ); BestBinaryTree[k] = *minptr; MinHp.DeleteMin ( *minptr ); BestBinaryTree[k-1] = *minptr ; minptr->setdata(BestBinaryTree[k].data + BestBinaryTree[k-1].data, k, k -1 ); minptr->setorder();

k -= 2; MinHp.Insert( *minptr ); // 内部结点 bj 插入最小化堆。 } MinHp.DeleteMin ( *minptr ); BestBinaryTree[1] = *minptr; // 插入根结点。}

0 1 2 …….n…… 2n-2 2n-1 b1,6

A, 7

S, 4C, 2

b2,11

T, 5

b3,18

Page 58: 第五章  树及二叉树

赫夫曼编码

b1,6

A, 7

S, 4C, 2

b2,11

T, 5

b3,180

1

1

1

0

0

算法:在后序遍历的程序基础上进行改进,注意 到当访问到叶子结点时,其父结点全部 在堆栈中,通过堆栈中结点之间的关系 ,可以方便地得到该叶子结点的赫夫曼编 码,如右图。

<b1>

<b2>

<b3>

堆栈

注意:访问到叶子 C 时,只需检查 C 和 b1 、 b1

和 b2 、 b2 和 b3 之间的关系,即可得出叶子 C 的赫夫曼编码 110 。

Page 59: 第五章  树及二叉树

59

附:自上而下建堆法简介

50

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

16

62

24

7

88

77

数组 h

16

2

Page 60: 第五章  树及二叉树

60

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

16

50

62

24

7

88

77

数组 h

50

2

Page 61: 第五章  树及二叉树

61

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

50

16

62

24

7

88

77

数组 h

50

2

62

3

Page 62: 第五章  树及二叉树

62

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

16

50

62

24

7

88

77

数组 h

50

2

62

3

24

4

Page 63: 第五章  树及二叉树

63

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

16

24

62

50

7

88

77

数组 h

24

2

62

3

50

4

Page 64: 第五章  树及二叉树

64

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

16

24

62

50

7

88

77

数组 h

24

2

62

3

50

4

7

5

Page 65: 第五章  树及二叉树

65

附:自上而下建堆法简介

16

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

16

7

62

50

24

88

77

数组 h

7

2

62

3

50

4

24 5

Page 66: 第五章  树及二叉树

66

附:自上而下建堆法简介

7

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

16

62

50

24

88

77

数组 h

16

2

62

3

50

4

24

5

Page 67: 第五章  树及二叉树

67

附:自上而下建堆法简介

7

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

16

62

50

24

88

77

数组 h

16

2

62

3

50

4

24

5

88

6

Page 68: 第五章  树及二叉树

68

附:自上而下建堆法简介

7

1

root[ 1 ]

[ 2 ]

[ 3 ]

[ 4 ]

[ 5 ]

[ 6 ]

[ 7 ]

7

16

62

50

24

88

77

数组 h

16

2

62

3

50

4

24

5

88

6

77

7

Page 69: 第五章  树及二叉树

69

附:自上而下建堆法简介• 利用自上而下法建立最小化堆得代价分析: O ( n logn )

考虑最大比较次数: log2 + log3 + …… log(n-1) + logn

考察: log2 + log3 + …… log(n-1) + logn 的时间代价级别为 O ( n logn ) 。

X轴

Y轴

求 y=logx 的积分的示意图2

1

2 3 4

y=logn

1

n n+1

j=2

n n+1

2 log(n!) = Σ logj ≤ logx dx 不难求出上述积分的值为: (n+1)log(n+1)-(n+1)loge + 2loge - 2 ; 它是 O ( n logn ) 的。

1

Page 70: 第五章  树及二叉树

树的存储结构

•标准形式:树中的每个结点除有数据场之外,还有 K 个指针场;其中 K 为树的度数。

数据场第一个儿子结点的地址

第二个儿子结点的地址

…………

第 K 个儿子结点的地址

父亲结点的地址

•广义标准形式:在标准形式的基础上,再增加指向父亲结点的指针场。

E.g: 度数 K = 3 的树

A

B C D

E F G H

I

L

A 1 2 3 -1

B 4 5 -1 0

C 6 -1 -1 0

D 7 8 -1 0

L -1 -1 -1 1

E -1 -1 -1 1

F -1 -1 -1 2

G 9 -1 -1 3

H -1 -1 -1 3

I -1 -1 -1 7

0

1

2

3

4

5

6

7

8

9

-1 表示空

缺点:空指针场太多,多达

( K -1 )× n + 1 个。

改进:结点中设立度数场,

指针场依度数定。但

操作麻烦。

采用左儿子、兄弟表

示法。

用数组表

示左图的

Page 71: 第五章  树及二叉树

树的存储结构•左儿子、兄弟表示法:树中的每个结点有数据场、指向它的第一棵子树树根的指针场、

指向它的兄弟结点的指针场。实质上是用二叉树表示一棵树。

数据场第一棵子树根结点的地址

下一个亲兄弟结点的地址

E.g: 度数 K = 3 的树

A

B C D

E F G H

I

L

A ∧

B C D ∧

E ∧ ∧ F ∧ G ∧ H ∧ ∧ L

∧ I ∧

Page 72: 第五章  树及二叉树

树和森林与二叉树的转换

•树转换成相对应的二叉树:

1 、保留每个结点的最左面的分支,其余分支都被删除。

2 、同一父结点下面的结点成为它的左方结点的兄弟。

E.g: 度数 K = 3 的树

A

B C D

E F G H

I

L

A

B C D

E F G H

I

L

A

B

C

DE F

G

HI

L

Page 73: 第五章  树及二叉树

树和森林与二叉树的转换

•森林转换成相对应的二叉树:

增加一个虚拟的根结点,它的儿子为各棵树的根。

那么,就变成了树转换成二叉树的问题。

E.g: 3 棵分别以 B 、

C 、 D 为根的树 B

C

DE F

G

HI

L

B C D

E F G H

I

L

A

B C D

E F G H

I

L

A

相应的二叉树

Page 74: 第五章  树及二叉树

树和森林与二叉树的转换

•森林转换成相对应的二叉树:

增加一个虚拟的根结点,它的儿子为各棵树的根。

那么,就变成了树转换成二叉树的问题。

E.g: 3 棵分别以 B 、

C 、 D 为根的树 B

C

DE F

G

HI

L

B C D

E F G H

I

L

A

B C D

E F G H

I

L

相应的二叉树

Page 75: 第五章  树及二叉树

树的前序、后序遍历

1 、类似于二叉树的前序遍历: NLR ; N: 根; L :左子树(第一棵子树),

R :其余的那些子树,遍历方向由第二棵子树至最后一棵子树

2 、类似于二叉树的后序遍历: LRN : L :左子树(第一棵子树), R :其

余的那些子树,遍历方向由第二棵子树至最后一棵子树, N: 根

N

B C D

E F G H

I

L

A

T1 T2 …… T3

L

R 前序: A 、 B 、 L 、 E 、 C 、 F 、 D 、 G、 I 、 H

后序: L 、 E 、 B 、 F 、 C 、 I 、 G 、 H 、D 、 A

Page 76: 第五章  树及二叉树

森林的前序、中序遍历

•前序遍历类似于树的前序遍历。增加一个虚拟的根结点,它的儿子为各棵树的

根。那么对这棵树进行前序遍历,即得到森林的前序序列(不含树根结点)•中序遍历类似于树的后序遍历。增加一个虚拟的根结点,它的儿子为各棵树的

根。那么对这棵树进行后序遍历,即得到森林的中序遍历(去掉树根结点)

前序: B 、 L 、 E 、 C 、 F 、 D 、 G、 I 、 H

中序: L 、 E 、 B 、 F 、 C 、 I 、 G、 H 、 D

B C D

E F G H

I

L

A

B C D

E F G H

I

L

Page 77: 第五章  树及二叉树

树的遍历

•树的前序、后序遍历序列和相应的二叉树的前序、中序遍历序列一一对应:•前序序列和对应的二叉树的前序序列完全一致。

例:左图的树的根 A ,及它的儿子结点 B 、 C 、 D 在树的

的前序序列和相应的二叉树中前序序列中的序号。

根 A: 1 1

结点 B : 2 2

结点 C : 5 5

结点 D : 7 7

B C D

E F G H

I

L

A

A

B

C

DE F

G

HI

L

以 C 为例:

在树中:

序号= 根节点数 + 第一棵子树的结点数 + 1 = 5

在二叉树中:

序号= 根节点数 +左儿子数 +左儿子子树结点数 + 1 = 5

Page 78: 第五章  树及二叉树

树的遍历

•树的前序、后序遍历序列和相应的二叉树的前序、中序遍历序列一一对应:•后序序列和对应的二叉树的中序序列完全一致。

E 、 G :左图的树的根 A ,及它的儿子结点 B 、 C 、 D 在树的

的后序序列和相应的二叉树的中序序列中的序号。

根 A: 10 10

结点 B : 3 3

结点 C : 5 5

结点 D : 9 9

B C D

E F G H

I

L

A

A

B

C

DE F

G

HI

L

后序: L 、 E 、 B 、F 、 C 、 I 、 G 、 H、 D 、 A

以 C 为例:

在树中:

序号= 第一棵子树的结点数 + C 的子树的结点数 + 1 = 5

在二叉树中:

序号= B 的左子树的结点数 +B 的结点数 + C 的左子树结点数 + 1 = 5

中序: L 、 E 、 B 、F 、 C 、 I 、 G 、 H、 D 、 A

Page 79: 第五章  树及二叉树

森林的遍历

•森林的前序、中序(当作后序更好理解)和相应的二叉树的前序、中序遍历序列一一对应

例 : 3 棵分别以 B 、

C 、 D 为根的树

B

C

DE F

G

HI

L

B C D

E F G H

I

L

A

B C D

E F G H

I

L

A

相应的二叉树

• 注意:本书介绍了森林的后序,一般用的很少。• 另外:本书介绍了层次访问,也很简单。只需采用队列,即可实现。

Page 80: 第五章  树及二叉树

二叉树的确定

• 前序(后序) + 中序 唯一确定一棵二叉树

例: 前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C确定过程 : 1 、 定根 A

2 、在中序序列中找到 A

3 、中序序列中的 A 的左部为 A 的左子树

上的所有结点, A 的右部为 A 的右子

树中的所有结点。

4 、根据 A 的左子树的所有结点的前序序

列确定 A 的左子树的根节点,它是 A

的左儿子。

5 、根据 A 的右子树的所有结点的前序序

列确定 A 的右子树的根节点,它是 A

的右儿子。

6 、 在左、右子树中反复以上过程。至所有

结点处理完毕。结束

前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C

前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C

前序 : B 、 D 、 E 、 F

中序 : D 、 B 、 E 、 F

前序 : E 、 F

中序 : E 、 F

A

D 、 B 、 E 、 F

C

A

D 、 E 、 FCB

A

B C

D

E 、 F

Page 81: 第五章  树及二叉树

二叉树的确定二叉树的确定

•前序(后序) + 中序 唯一确定一棵二叉树

例 : 前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C确定过程 : 1 、 定根 A

2 、在中序序列中找到 A

3 、中序序列中的 A 的左部为 A 的左子树

上的所有结点, A 的右部为 A 的右子

树中的所有结点。

4 、根据 A 的左子树的所有结点的前序序

列确定 A 的左子树的根节点,它是 A

的左儿子。

5 、根据 A 的右子树的所有结点的前序序

列确定 A 的右子树的根节点,它是 A

的右儿子。

6 、 在左、右子树中反复以上过程。至所有

结点处理完毕。结束

前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C

前序 : A 、 B 、 D 、 E 、 F 、 C

中序 : D 、 B 、 E 、 F 、 A 、 C

E

F

前序 : B 、 D 、 E 、 F

中序 : D 、 B 、 E 、 F

前序 : E 、 F

中序 : E 、 F

A

D 、 B 、 E 、 F

C

A

D 、 E 、 FCB

A

B C

D

Page 82: 第五章  树及二叉树

树的计数互不相似的二叉树的棵数的计算

计算要点 :

• 设二叉树的前序的序列

为 1 、 2 、 3 ……… n

• 不同的中序序列对应

着不同树形的二叉树• 不同的中序序列的总数

是不同树形的二叉树的

总和•不同的中序序列在中序

周游时和相应的进出栈

序列一一对应。• 不同的进出栈序列的总

数是不同树形的二叉树

的总和

•例:

当 二叉树的结点个数 n = 3

前序序列为 1 、 2 、 3 时

1

2

3

1

2

3

1

2 3

1

2

3

1

2

3

1 、进栈

2 、进栈

3 、进栈

3 、出栈

2 、出栈

1 、出栈

1 、进栈

2 、进栈

2 、出栈

3 、进栈

3 、出栈

1 、出栈

1 、进栈

2 、进栈

2 、出栈

1 、出栈

3 、进栈

3 、出栈

1 、进栈

1 、出栈

2 、进栈

3 、进栈

3 、出栈

2 、出栈

1 、进栈

1 、出栈

2 、进栈

2 、出栈

3 、进栈

3 、出栈

Page 83: 第五章  树及二叉树

树的计数互不相似的二叉树的棵数的计算• 例:

当 二叉树的结点个数 n = 3

前序序列为 1 、 2 、 3 时

1

2

3

1

2

3

1

2 3

1

2

3

1

2

3

1 、进栈

2 、进栈

3 、进栈

3 、出栈

2 、出栈

1 、出栈

1 、进栈

2 、进栈

2 、出栈

3 、进栈

3 、出栈

1 、出栈

1 、进栈

2 、进栈

2 、出栈

1 、出栈

3 、进栈

3 、出栈

1 、进栈

1 、出栈

2 、进栈

3 、进栈

3 、出栈

2 、出栈

1 、进栈

1 、出栈

2 、进栈

2 、出栈

3 、进栈

3 、出栈

• 进出栈序列总数的计算为

2n 个方格中放置 n 个 1 的组合数

- 不合理的序列总数

e.g: n = 3 时, 6格放置 3 个 1 的序列

情况: 1 代表进栈, 0 表示出栈 1 1 1 0 0 0

1 1 0 0 1 0

1 1 0 1 0 0

1 0 1 1 0 0

1 0 1 0 1 0

0 0 0 1 1 1

1 0 0 1 1 0

不合理

Page 84: 第五章  树及二叉树

树的计数进出栈序列总数的计算为

2n 个方格中放置 n 个 1 的组合数

- 不合理的序列总数

e.g: n = 3 时, 6格放置 3 个 1 的序列

情况: 1 代表进栈, 0 表示出栈 1 1 1 0 0 0

1 1 0 0 1 0

1 1 0 1 0 0

1 0 1 1 0 0

1 0 1 0 1 0

0 0 0 1 1 1

1 0 0 1 1 0

不合理

n = 3 时, 6 格放置 3 个 1 的序列情况:

3

序列总数 =

2 × 3

3 + 1

不合理的序列总数:

2 × 3

因此, n = 3 时进出栈序列的总数为:

3 3 + 1

2 × 3 2 × 3

推广到结点数为 n 时的进出栈序列总数:

n n + 1

2 × n 2n

Page 85: 第五章  树及二叉树

树的计数互不相似的二叉树的棵数的计算

• 结点数为 n 时的互不相似的二叉树

的总数为:

n n + 1

2 × n 2 × n

n n - 1

2 × n 2 × n

• 结点数为 n + 1 时的互不相似的有序树

总数为:

n n + 1

2 × n 2 × n

n n - 1

2 × n 2 × n

一种采用递推公式计算的办法,结果一样。b0 = 1

bn = bi × bn-i-1

n-1

i= 0

Page 86: 第五章  树及二叉树

树的计数推论:结点数为 n + 1 时的互不相似的有序树总数和结点数为 n 时的互不相似的二叉树的总数相等。

1

2

3

1

2

3

1

2 3

1

2

3

1

2

3

0

2

3

11

2 3

0

1

2

3

0

1

3

2

0

1 32

0

例 : n = 4

的所有树

只需考虑

以下二叉

树的棵数即

0

2

3

11

2 3

0

1

2

3

0

1

3

2

0

1 32

0