177
树树树树树树树 树树树 树树树树树 树树树树树树 树树树树树树 树树树树 Huffman

树和森林的概念 二叉树 二叉树遍历 二叉树的计数 线索化二叉树 堆 树与森林 Huffman 树

  • Upload
    bly

  • View
    390

  • Download
    12

Embed Size (px)

DESCRIPTION

第六章 树与森林. 树和森林的概念 二叉树 二叉树遍历 二叉树的计数 线索化二叉树 堆 树与森林 Huffman 树. 树和森林的概念. 树的定义 树是由 n ( n ≥ 0) 个结点组成的有限集合。如果 n = 0 ,称为空树;如果 n > 0 ,则 有一个特定的称之为 根 (root) 的结点,它只有直接后继,但没有直接前驱; 除根以外的其他结点划分为 m ( m ≥ 0) 个 互不相交的有限集合 T 0 , T 1 , …, T m -1 ,每个集合又是一棵树,并且称之为 根的子树 。. 树的特点. - PowerPoint PPT Presentation

Citation preview

树和森林的概念 二叉树 二叉树遍历 二叉树的计数 线索化二叉树 堆 树与森林 Huffman树

树和森林的概念树和森林的概念树的定义树的定义 树是由树是由 nn ( (nn ≥≥ 0) 0) 个结点组成的有限集合。如果 个结点组成的有限集合。如果

nn = 0 = 0 ,称为空树;如果 ,称为空树;如果 nn > 0 > 0 ,则,则有一个特定的称之为有一个特定的称之为根根 (root)(root) 的结点,它只的结点,它只有直接后继,但没有直接前驱;有直接后继,但没有直接前驱;

除根以外的其他结点划分为 除根以外的其他结点划分为 mm ( (mm ≥≥ 0) 0) 个 互个 互不相交的有限集合不相交的有限集合 TT00, , TT11, …, , …, TTmm-1-1 ,每个集合,每个集合又是一棵树,并且称之为又是一棵树,并且称之为根的子树根的子树。。

树的特点树的特点 每棵子树的根结点有且仅有一个直接前每棵子树的根结点有且仅有一个直接前驱,但可以有驱,但可以有 00 个或多个直接后继。个或多个直接后继。

0 层

1 层

3 层

2 层height

= 3

A

C

G

B D

E F

K L

H

M

I J

0 层

1 层

3 层

2 层height

= 3

A

C

G

B D

E F

K L

H

M

I J

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

子女子女 双亲双亲 兄弟兄弟

祖先祖先 子孙子孙 结点层次结点层次

树的度树的度 树树高度高度 森林森林

template <class Type> class Tree {// 对象 : 树是由 n(≥0) 个结点组成的有限集// 合。在类界面中的 position 是树中结点的// 地址。在数组存储方式下是下标型 , 在链// 表存储方式下是指针型。 Type 是树结点中存放数据的类型。public: Tree ( ); ~Tree ( );

树的抽象数据类型树的抽象数据类型

position Root ( ); BuildRoot ( const Type& value ); position FirstChild ( position p ); position NextSibling ( position p ); position Parent ( position p ); Type GetData ( position p );

int InsertChild ( const position p, const Type &value );

int DeleteChild ( position p, int i );}

二叉树二叉树 (Binary Tree)(Binary Tree)

二叉树的定义二叉树的定义

二叉树的五种不同形态二叉树的五种不同形态

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

L LR R

性质性质 11 若二叉树的层次从若二叉树的层次从 00 开始开始 , , 则在二则在二叉树的第 叉树的第 i i 层最多有 层最多有 22i i 个结点。个结点。 ((ii ≥≥ 0 0))

[[ 证明用数学归纳法证明用数学归纳法 ]]

性质性质 22 高度为 高度为 h h 的二叉树最多有 的二叉树最多有 22h+h+11--11

个结点。个结点。 ((hh ≥≥ --11))

[[ 证明用求等比级数前证明用求等比级数前 kk 项和的公式项和的公式 ]]

2200 + 2 + 211 + 2 + 222 + + … + 2… + 2hh = 2 = 2hh+1+1--11

二叉树的性质二叉树的性质

性质性质 33 对任何一棵二叉树对任何一棵二叉树 , , 如果其叶结如果其叶结点有 点有 nn0 0 个个 , , 度为度为 22 的非叶结点有 的非叶结点有 nn2 2 个个 ,,

则有则有 nn00 == nn22 ++ 11

证明:若设度为证明:若设度为 11 的结点有 的结点有 nn11 个,总结个,总结点个数为 点个数为 nn ,总边数为 ,总边数为 ee ,则根据二叉,则根据二叉树的定义,树的定义, nn = = nn00 + + nn11 + + nn22 ee = 2 = 2nn22 + + nn1 1 = = nn -- 1 1

因此,有因此,有 22nn22 + + nn11 = = nn00 + + nn11 + + nn22 -- 1 1

nn22 = = nn00 -- 1 1 nn00 = = nn22 + 1 + 1

定义定义 11 满二叉树 满二叉树 (Full Binary Tree)(Full Binary Tree) 定义定义 22 完全二叉树 完全二叉树 (Complete Binary Tre(Complete Binary Tree)e) 若设二叉树的高度为若设二叉树的高度为 hh ,则共有,则共有 hh+1+1层。除第 层。除第 h h 层外,其它各层 层外,其它各层 (0 (0 hh--1) 1) 的结点数都达到最大个数,第 的结点数都达到最大个数,第 h h 层从右层从右向左连续缺若干结点,这就是完全二叉树。向左连续缺若干结点,这就是完全二叉树。

性质性质 44 具有 具有 n n ((n n 0) 0) 个结点的完全二个结点的完全二叉树的高度为叉树的高度为 loglog22((nn+1)+1) --1 1

证明:证明:设完全二叉树的高度为 设完全二叉树的高度为 hh ,则有,则有 22hh -- 1 < 1 < nn 2 2hh+1+1 -- 1 1

变形 变形 22hh < < n+n+1 1 2 2hh+1+1 取对数 取对数 hh < log < log22((nn+1) +1) hh+1+1

有 有 loglog22((nn+1)+1) = = hh+1+1

hh = = loglog22((nn+1)+1) --1 1

上面 h 层结点数 包括第 h 层的最大结点数

23-124-1

性质性质 55 如将一棵有如将一棵有 nn 个结点的完全二叉树个结点的完全二叉树自顶向下,同一层自左向右连续给结点编自顶向下,同一层自左向右连续给结点编号号 0, 1, 2, …, 0, 1, 2, …, nn--11 ,则有以下关系:,则有以下关系: 若若 i = i = 0, 0, 则 则 i i 无双亲无双亲 若若 i i > 0, > 0, 则 则 i i 的双亲为的双亲为 ((i i --1)/21)/2 若若 2*2*ii+1 <+1 < n n, , 则 则 i i 的左子女为 的左子女为 2*2*ii+1+1 ,,若若 2*2*ii+2 < +2 < nn, , 则 则 i i 的右子女为的右子女为 2*2*ii+2+2 若 若 i i 为偶数为偶数 , , 且且 ii != 0, != 0, 则其左兄弟为则其左兄弟为 ii--1, 1, 若若 i i 为奇数为奇数 , , 且且 ii != != nn-1, -1, 则其右兄弟为则其右兄弟为 ii+1 +1

0

1 2

3

7

4 5 6

8 9

二叉树的抽象数据类型二叉树的抽象数据类型template <class Type> class BinaryTree {public: BinaryTree ( ); // 构造函数 BinaryTree ( BinTreeNode<Type> * lch, BinTreeNode<Type> * rch, Type item ); // 构造以 item 为根, lch 和 rch 为左、右 // 子树的二叉树 int IsEmpty ( ); // 判二叉树空否? BinTreeNode<Type> * Parent ( ); // 双亲

BinTreeNode<Type> * LeftChild ( ); // 取左子女结点地址 BinTreeNode<Type> * RightChild ( ); // 取右子女结点地址 int Insert ( const Type& item ); // 插入 int Find ( const Type &item ) const; // 搜索 Type GetData ( ) const; // 取得结点数据 BinTreeNode<Type> *GetRoot ( ) const; // 取根结点地址}

正则二叉树 理想正则二叉树 理想平衡二叉树平衡二叉树

满的

完全二叉树 一般二叉树的顺序表示 的顺序表示

二叉树的顺序表示二叉树的顺序表示

0

0 1 2 3 4 5 6 7 8 9

13

0 1 2 3 5 6 7 8 9 11 13

1

3

7 8 9

4 5 6

2

0

1 2

653

7 8 11

4

9 10 12

0

0

2

6

14

2 6 14

30

极端情形极端情形 : : 只有右单支的二叉树只有右单支的二叉树

30

二叉树的链表表示二叉树的链表表示

leftChild data rightChild

data

leftChild rightChild

二叉链表

二叉树的链表表示二叉树的链表表示

leftChild data parent rightChild

parent

data

leftChild rightChild

三叉链表

二叉树链表表示的示例二叉树链表表示的示例

A A A

B B B

CCC D D D

FFFE E E

root root root

二叉树 二叉链表 三叉链表

二叉链表的静态结构二叉链表的静态结构

A

B

C D

FE

rootdata parent leftChild rightChild

012345

AA --1 1 1 1 --11BB 0 2 3 0 2 3CC 1 1 --1 1 --11DD 1 4 5 1 4 5EE 3 3 --1 1 --11FF 3 3 --1 1 --11

template <class Type> class BinaryTree;

template <class Type> Class BinTreeNode {friend class BinaryTree<Type>;private: Type data; BinTreeNode<Type> * leftChild; BinTreeNode<Type> * rightChild; public: BinTreeNode ( ) : leftChild (NULL),

rightChild (NULL) { }

二叉树的类定义二叉树的类定义

BinTreeNode ( Type item, BinTreeNode<Type> *left = NULL,

BinTreeNode<Type> *right = NULL ) : data (item), leftChild (left), rightChild (right) { } Type GetData ( ) const { return data; } BinTreeNode<Type> * GetLeft ( ) const { return leftChild; } BinTreeNode<Type> * GetRight ( ) const { return rightChild; } void SetData ( const Type& item ) { data = item; }

void SetLeft ( BinTreeNode <Type> * L ) { leftChild = L; } void SetRight ( BinTreeNode <Type> * R ) { rightChild = R; }};

template <class Type> class BinaryTree {private: BinTreeNode <Type> *root; Type RefValue; void CreateBinTree ( ifstream& in, BinTreeNode<Type> * & current );

BinTreeNode<Type> * Parent ( BinTreeNode<Type> * subTree, BinTreeNode<Type> * current); int Insert (BinTreeNode<Type> * & subTree, const Type &item); // 插入 void Traverse (BinTreeNode<Type> *subTree, ostream &out) const // 遍历 int Find (BinTreeNode<Type> *subTree, const Type &item) const // 搜索 void destroy (BinTreeNode<Type> * subTree);

// 删除

public: virtual BinaryTree ( ) : root (NULL) { } virtual BinaryTree ( Type value ) : RefValue (value), root (NULL) { } virtual ~BinaryTree ( ) { destroy ( root ); } virtual int IsEmpty ( ) // 判树空否 { return root == NULL ? 1 : 0; } virtual BinTreeNode <Type> * Parent ( BinTreeNode <Type> *current ) { return root == NULL || root == current ? NULL : Parent ( root, current ); } // 找 current 结点的双亲

virtual BinTreeNode<Type> * LeftChild ( BinTreeNode<Type> *current )

// 取 current 结点的左子女 { return root != NULL ? current->leftChild : NULL; } virtual BinTreeNode<Type> * RightChild ( BinTreeNode<Type> *current ) // 取 current 结点的右子女 { return root != NULL ? current->rightChild : NULL; } virtual int Insert ( const Type& item);

virtual BinTreeNode<Type> * Find (const Type& item) const; // 搜索 BinTreeNode<Type> *GetRoot ( ) const { return root; } // 取根 friend istream& operator >> (istream& in, BinaryTree<Type>& Tree)

// 重载操作:输入 friend ostream& operator << (ostream&out, BinaryTree<Type>& Tree) // 重载操作:输出}

二叉树部分成员函数的实现二叉树部分成员函数的实现

template<class Type> void BinaryTree<Type> :: destroy (BinTreeNode<Type> *subTree) { if ( subTree != NULL ) { destroy ( subTree->leftChild ); destroy ( subTree->rightChild ); delete subTree; }}

template <class Type> BinTreeNode <Type> * BinaryTree <Type> :: Parent ( BinTreeNode <Type> * subTree, BinTreeNode <Type> * cuurent ) { if ( subTree == NULL ) return NULL; if ( subTree->leftChild == current || subTree->rightChild == current ) return subTree; BinTreeNode <Type> *p; if (( p = Parent ( subTree->leftChild, current )) != NULL ) return p; else return Parent(subTree->rightChild, current);}

template <class Type> void BinaryTree<Type> :: Traverse ( BinTreeNode <Type> *subTree, ostream &out ) const {// 私有函数 : 搜索并输出根为 subTree 的二叉树 if ( subTree != NULL ) { out << subTree->data << ‘ ’; Traverse ( subTree->leftChild, out ); Traverse ( subTree->rightChild, out ); }}

template<class Type> istream& operator >> ( istream& in, BinaryTree<Type> & Tree ) {// 建立一棵二叉树 Tree 。 in 是输入流对象 Type item; in ( filename, ios :: in | ios :: nocreate );

// 打开文件 if ( !in ) { cerr << “ 文件未发现 !” << endl; exit (1); } CreateBinTree (in, Tree.root); in.close ( ); // 关闭文件}

template <class Type> ostream& operator << ( ostream& out, BinaryTree<Type> &Tree ) { out << “ 二叉树的前序遍历 .\n"; Tree.Traverse ( Tree.root, out ); out << endl; return out;}

二叉树遍历二叉树遍历 树的遍历就是按某种次序访问树中树的遍历就是按某种次序访问树中的结点,要求每个结点访问一次且仅访问一的结点,要求每个结点访问一次且仅访问一次。次。 设设访问根结点访问根结点记作记作 VV ,, 遍历根的左子树遍历根的左子树记作记作 LL ,, 遍历根的右子树遍历根的右子树记作记作 RR ,, 则可能的遍历次序有则可能的遍历次序有 前序 前序 VLR VLR 镜像 镜像 VRLVRL 中序 中序 LVR LVR 镜像 镜像 RVLRVL

后序 后序 LRV LRV 镜像 镜像 RLVRLV

中序遍历二叉树算法的框架是:中序遍历二叉树算法的框架是: 若二叉树为空,则空操作;若二叉树为空,则空操作; 否则否则

中序遍历左子树 中序遍历左子树 (L)(L) ;; 访问根结点 访问根结点 (V)(V) ;; 中序遍历右子树 中序遍历右子树 (R)(R) 。。

遍历结果遍历结果 aa + + bb * * cc -- dd -- ee / / ff

中序遍历 中序遍历 (Inorder Traversa(Inorder Traversal)l)

--

--

//++

**aa

bb

cc dd

ee ff

二叉树递归的中序遍历算法二叉树递归的中序遍历算法template <class Type> void BinaryTree <Type> :: InOrder ( BinTreeNode <Type> *subTree ) { if ( subTree != NULL ) { InOrder ( subTree->leftChild ); cout << subTree->data; InOrder ( subTree->rightChild ); }}

前序遍历二叉树算法的框架是:前序遍历二叉树算法的框架是: 若二叉树为空,则空操作;若二叉树为空,则空操作; 否则否则

访问根结点 访问根结点 (V)(V) ;; 前序遍历左子树 前序遍历左子树 (L)(L) ;; 前序遍历右子树 前序遍历右子树 (R)(R) 。。

遍历结果遍历结果 -- + + aa * * bb -- c dc d / / e fe f

前序遍历 前序遍历 (Preorder Traversal)(Preorder Traversal)

--

--

//++

**aa

bb

cc dd

ee ff

二叉树递归的前序遍历算法二叉树递归的前序遍历算法template <class Type> void BinaryTree<Type> ::PreOrder ( BinTreeNode <Type> * subTree ) { if ( subTree != NULL ) { cout << subTree->data; PreOrder ( subTree->leftChild ); PreOrder ( subTree->rightChild ); }}

后序遍历二叉树算法的框架是:后序遍历二叉树算法的框架是: 若二叉树为空,则空操作;若二叉树为空,则空操作; 否则否则

后序遍历左子树 后序遍历左子树 (L)(L) ;; 后序遍历右子树 后序遍历右子树 (R)(R) ;; 访问根结点 访问根结点 (V)(V) 。。

遍历结果遍历结果 a b c da b c d -- * + * + e fe f / / --

后序遍历 后序遍历 (Postorder Traversa(Postorder Traversal)l)

--

--

//++

**aa

bb

cc dd

ee ff

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

template <class Type> void BinaryTree <Type> ::PostOrder ( BinTreeNode <Type> * subTree ) { if ( subTree != NULL ) { PostOrder ( subTree->leftChild ); PostOrder ( subTree->rightChild ); cout << subTree->data; }}

template <class Type> void BinaryTree <Type>:: InOrder ( ) { InOrder ( root );}

template <class Type>void BinaryTree <Type>:: PreOrder ( ) { PreOrder ( root );}

template <class Type> void BinaryTree <Type>:: PostOrder ( ) { PostOrder ( root );}

利用二叉树利用二叉树后序遍历后序遍历计算二叉树结点个数计算二叉树结点个数template <class Type> int BinaryTree<Type> :: Count ( BinTreeNode <Type> * t ) const { if ( t == NULL ) return 0; else return 1 + Count ( t->leftChild ) + Count ( t->rightChild );}

应用二叉树遍历的事例应用二叉树遍历的事例

template <class Type> int BinaryTree<Type> :: Height ( BinTreeNode <Type> * t ) const { if ( t == NULL ) return -1; else return 1 + Max ( Height ( t->leftChild ), Height ( t->rightChild ) );}

利用二叉树利用二叉树后序遍历后序遍历计算二叉树的高度计算二叉树的高度

以递归方式建立二叉树。以递归方式建立二叉树。 输入结点值的顺序必须对应二叉树结点前输入结点值的顺序必须对应二叉树结点前序遍历的顺序。并约定以输入序列中不可序遍历的顺序。并约定以输入序列中不可能出现的值作为空结点的值以结束递归能出现的值作为空结点的值以结束递归 , , 此值在此值在 RefValueRefValue 中。例如用中。例如用““ @”@” 或用或用““ --1”1” 表示字符序列或正整数序列空结点。表示字符序列或正整数序列空结点。

利用二叉树利用二叉树前序遍历前序遍历建立二叉树建立二叉树

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

A

B

C D

E

G

F@ @

@

@ @

@ @

@

template<class Type> void BinaryTree <Type> :: CreateBinTree ( ifstream& in, BinTreeNode<Type> * & subTree ) {// 私有函数 : 以递归方式建立二叉树。// 输入结点值的顺序必须对应二叉树结点前// 序遍历的顺序。并约定以输入序列中不可// 能出现的值作为空结点的值以结束递归 ,// 此值在 RefValue 中。例如用“ @” 或用“ -1”// 表示字符序列或正整数序列空结点。 Type item; if ( !in.eof ( ) ) {

in >> item; // 读入根结点的值 if ( item != RefValue ) { subTree = new BinTreeNode<Type> ( item ); // 建立根结点 if ( subTree == NULL ) { cerr << “ 存储分配错 !” << endl; exit (1); } CreateBinTree ( in, subTree->leftChild ); CreateBinTree ( in, subTree->rightChild ); } else subTree = NULL; // 封闭叶结点 }}

利用栈的前序遍历非递归算法利用栈的前序遍历非递归算法a

b c

d ec

dc c

访问a

进栈c

左进b

访问b

进栈d

左进空

退栈d

访问d

左进空

退栈c

访问c

左进e

访问e

左进空

栈空结束

template <class Type> void BinaryTree<Type> :: PreOrder( ) { stack <BinTreeNode<Type>* > S; BinTreeNode<Type> * p = root; // 初始化 S.Push ( NULL ); while ( p != NULL ) { cout << p->data << endl; if ( p->rightChild != NULL ) S.Push ( p->rightChild ); // 预留右子树指针在栈中

利用栈的前序遍历非递归算法利用栈的前序遍历非递归算法

if ( p->leftChild != NULL ) p = p->leftChild; // 进左子树 else { p = S.getTop( ); S.Pop( ); } // 左子树为空 , 进右子树 }}

利用栈的中序遍历非递归算法利用栈的中序遍历非递归算法template <class Type> void BinaryTree<Type> :: InOrder ( ) { stack <TreeNode<Type>* > S;

利用栈的中序遍历非递归算法利用栈的中序遍历非递归算法a

b c

d e

ba a

da a

左空 退栈访问

左空 退栈访问

退栈访问

左空

ec

退栈访问c c

右空退栈访问 栈空结束

BinTreeNode<Type> * p = root; // 初始化 do { while ( p != NULL ) { S.Push(p); p = p->leftChild; } if ( !S.IsEmpty( ) ) { // 栈非空 p = S.getTop( ); S.Pop( ); // 退栈 cout << p->data << endl; // 访问根 p = p->rightChild; // 向右链走 } } while ( p != NULL || !S.IsEmpty( ) );}

利用栈的后序遍历非递归算法利用栈的后序遍历非递归算法后序遍历时使用的栈的结点定义后序遍历时使用的栈的结点定义template <class Type> struct stkNode { BinTreeNode<Type> *ptr; enum tag { L, R }; // 该结点退栈标记 stkNode ( BinTreeNode<Type>* N = NULL ) : ptr (N), tag ( L ) { } // 构造函数};

根结点的 tag = L ,表示从左子树退出 , 访问右子树。 tag = R, 表示从右子树退出 , 访问根。

ptr tag{L,R}

a

b c

d eaL

bLaL

bRaL

dLbRaL

dRbRaL

bRaL aL aR

eLcLaR

eRcLaR

cLaR

cRaR aR

template <class Type> void BinaryTree<Type> :: PostOrder ( ) { stack <stkNode<Type> > st; stkNode<Type> w; BinTreeNode<Type> * p = root; do {

while ( p != NULL ) { // 向左子树走 w.ptr = p; w.tag = L; st.Push ( w );

p = p->leftChild;

} int continue = 1; // 继续循环标记

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

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

p = p->rightChild; break; case R : cout << p->data << endl;

} }} while ( p != NULL || !S.IsEmpty( ) );

cout << endl; }

二叉树遍历的游标类二叉树遍历的游标类 (Tree Iterator)(Tree Iterator)

二叉树的游标抽象基类二叉树的游标抽象基类template <class Type> class TreeIterator {public: TreeIterator ( const BinaryTree <Type> & BT ) : T (BT), current (NULL) { } virtual ~TreeIterator ( ) { } virtual void First ( ) = 0; virtual void operator ++ ( ) = 0; int operator + ( ) const {return current != NULL; }

const Type& operator ( ) ( ) const;protected: const BinaryTree <Type> & T; BinTreeNode <Type> * current; private: TreeIterator ( const TreeIterator & ) { } TreeIterator & operator = ( const TreeIterator & ) const;};

template <class Type>const Type& TreeIterator<Type> :: operator ( )( ) const { if ( current == NULL ) { cout << " 非法访问非法访问 " << endl; exit (1); } return current->GetData ( );}

结点地址 *Node 退栈次数 S.PopTim

后序游标类后序游标类 为记忆走过的结点,设置一个工作栈:为记忆走过的结点,设置一个工作栈:

S.PopTim = 0S.PopTim = 0, 11 或或 22 ,表示第几次退栈,表示第几次退栈,用以判断下一步向哪一个方向走。用以判断下一步向哪一个方向走。

后序遍历时,每遇到一个结点,先把它推后序遍历时,每遇到一个结点,先把它推入栈中,让 入栈中,让 S.PopTim = 0S.PopTim = 0 。。

在遍历其左子树前,改结点的 在遍历其左子树前,改结点的 S.PopTimS.PopTim = = 11 ,将其左子女推入栈中。在遍历完左子树,将其左子女推入栈中。在遍历完左子树后,还不能访问该结点,要继续遍历右子后,还不能访问该结点,要继续遍历右子

a

b c

d ea0

b0a1

b1a1

d0b2a1

d2b2a1

b2a1 a1 a2

e0c1a2

e2c1a2

c1a2

c2a2 a2

d1b2a1

c0e1c1a2

树,此时改结点的树,此时改结点的 S.PopTimS.PopTim = 2= 2 ,并把其右,并把其右子女推入栈中。在遍历完右子树后,结点才子女推入栈中。在遍历完右子树后,结点才退栈访问。退栈访问。

后序遍历游标类的定义后序遍历游标类的定义template <class Type> struct stkNode {// 后序遍历使用的递归工作栈 结点定义 const BinTreeNode <Type> *Node; // 结点指针 int PopTim; // 退栈次数 stkNode ( BinTreeNode <Type> *N = NULL ) : Node (N), PopTim (0) { }}

template <class Type> class PostOrder : public TreeIterator <Type> {public:

PostOrder ( const BinaryTree <Type> & BT ); ~PostOrder ( ) { } void First ( ); // 定位到后序次序下第一个结点 void operator ++ ( ); // 定位到后序次序下的下一个结点protected: Stack < StkNode<Type> > st; // 工作栈}

后序游标类成员函数的实后序游标类成员函数的实现现

template <class Type>PostOrder <Type> ::PostOrder ( const BinaryTree <Type> &BT ) : TreeIterator <Type> (BT) { st.Push ( StkNode<Type>( T.GetRoot( ) ) );}

template <class Type> void PostOrder <Type> ::First ( ) { st.MakeEmpty ( ); if ( T.GetRoot ( ) != NULL ) st.Push ( StkNode <Type>( T.GetRoot ( ) ) ); operator ++ ( );}

template <class Type> void PostOrder <Type> ::operator ++ ( ) { if ( st.IsEmpty ( ) ) { if ( current == NULL ) { cout << " 已经遍历结束 " << endl; exit (1); } current = NULL; return; // 遍历完成 } StkNode <Type> Cnode; for ( ; ; ) { // 循环 ,找后序下的下一个结点 Cnode = st.Pop ( ); if ( ++Cnode.PopTim == 3 ) // 从右子树退出 { current = Cnode.Node; return; }

st.Push ( Cnode ); // 否则加一进栈 if ( Cnode.PopTim == 1 ) { // 左子女进栈 if ( Cnode.Node->GetLeft ( ) != NULL ) st.Push ( StkNode <Type> ( Cnode.Node->GetLeft ( ) ) ); } else { // =2, 右子女进栈 if ( Cnode.Node->GetRight ( ) != NULL ) st.Push ( StkNode <Type> ( Cnode.Node->GetRight ( ) ) ); } }}

中序游标类中序游标类 中序遍历与后序遍历走过的路线一样,只中序遍历与后序遍历走过的路线一样,只是在从左子树退出后立即访问根结点,再遍历是在从左子树退出后立即访问根结点,再遍历右子树。右子树。中序遍历也要使用一个递归工作栈 中序遍历也要使用一个递归工作栈 stst 。。

中序游标类的定义中序游标类的定义template <class Type> class InOrder : public PostOrder <Type> {public: InOrder ( BinaryTree <Type> & BT ) : PostOrder <Type> ( BT ) { }

void First ( ); void operator ++ ( );};

中序游标类中序游标类 operator ++( )operator ++( ) 操作的实现操作的实现

template <class Type> void InOrder <Type>:: operator ++ ( ) { if ( st.IsEmpty ( ) ) { if ( current == NULL ) { cout << " 已经遍历完成已经遍历完成 " << endl; exit (1); } current = NULL; return; } StkNode <Type> Cnode;

for ( ; ; ) { // 循环 ,找中序下的下一个结点 Cnode = st.Pop ( ); // 退栈 if ( ++Cnode.PopTim == 2 ) { // 从左子树退出,成为当前结点 current = Cnode.Node; if ( Cnode.Node->GetRight ( ) != NULL ) st.Push ( StkNode <Type> ( Cnode.Node->GetRight ( ) ) ); return; } st.Push ( Cnode ); // 否则加一进栈

if ( Cnode.Node->GetLeft ( ) != NULL ) st.Push ( StkNode <Type> ( // 左子女进栈

Cnode.Node->GetLeft ( ) ) ); }}

层次序游标类层次序游标类

从根开始逐从根开始逐层访层访问,用 问,用 FIFO FIFO 队队列列实现。实现。

遍历顺序遍历顺序

层次序游标类的定义层次序游标类的定义template <class Type> class LevelOrder : public TreeIterator <Type> {public: LevelOrder ( const BinaryTree <Type> & BT ); ~LevelOrder ( ) { } void First ( ); void operator ++ ( );protected: Queue < const BinTreeNode <Type> * > qu;};

层次序游标类的层次序游标类的 operator ++ ( )operator ++ ( ) 操作操作template <class Type> LevelOrder <Type>:: LevelOrder ( const BinaryTree <Type> & BT ) : TreeIterator <Type> ( BT ) { qu.EnQueue ( T.GetRoot ( ) ); }

template <class Type> viod LevelOrder<Type> :: First ( ) {// 初始化:只有根结点在队列中 qu.MakeEmpty ( ); if ( T.GetRoot ( ) ) qu.EnQueue ( T.GetRoot ( ) ); operator ++ ( );}

template <class Type>void LevelOrder<Type> :: operator ++ ( ) { if ( qu.IsEmpty ( ) ) { if ( current == NULL ) { cout << " 已经遍历完成已经遍历完成 " << endl; exit (1);} current = NULL; return; } current = qu.DeQueue ( ); // 退队 if ( current->GetLeft ( ) != NULL ) // 左子女 qu.EnQueue ( current->GetLeft ( ) ); // 进队列 if ( current->GetRight ( ) != NULL ) // 右子女 qu.EnQueue ( current->GetRight ( ) ); // 进队列}

二叉树的计数二叉树的计数 由二叉树的前序序列和中序序列可唯由二叉树的前序序列和中序序列可唯一地确定一棵二叉树。一地确定一棵二叉树。 例例 , , 前序序列 前序序列 { ABHFDECKG } { ABHFDECKG } 和和中序序列 中序序列 { HBDFAEKCG }, { HBDFAEKCG }, 构造二叉树构造二叉树过程如下:过程如下:

HBDF EKCG

A

EKCG

A

B

H DF

KCG

前序序列 前序序列 { ABHFDECKG{ ABHFDECKG } }

EKCG

A

B

H DF

EKCG

A

B

H F

D

E

A

B

H F

D

E

A

B

H F

D

C

K G

如果前序序列固定不变,给出不同的中如果前序序列固定不变,给出不同的中序序列,可得到不同的二叉树。序序列,可得到不同的二叉树。

6

1

2

3 4

5

7

8 9

6

1

2

3 7

5

8

4 9

固定前序排列固定前序排列 ,, 选择所有可能的中序排选择所有可能的中序排列列 ,, 可以可能构造多少种不同的二叉树?可以可能构造多少种不同的二叉树?

例如例如 , , 有 有 3 3 个数据 个数据 { 1, 2, 3 }{ 1, 2, 3 } ,可得 ,可得 5 5 种不同的二叉树。它们的前序排列均为种不同的二叉树。它们的前序排列均为 123 ,中序序列可能是 中序序列可能是 123123 ,, 132132 ,, 213213 ,,231231 ,, 321321 。。

1

2

3

1

2

3

1

2 3

1

2

3

1

2

3

有有 00 个个 , 1, 1 个个 , 2, 2 个个 , 3, 3 个结点的不同二叉个结点的不同二叉树如下树如下

b0 =1 b1 =1 b2 =2b3 =5

b4 =14

!!

)!(2

1

1

1

12 nn

n

nnb C

n

nn

计算具有 计算具有 n n 个结点的不同二叉树的棵数个结点的不同二叉树的棵数

CatalanCatalan 函数函数bbii bbn-i-n-i-11

1

1

01

n

iinin bbb

5123

456

4

1

13

1 3

63

Cb

141234

5678

5

1

14

1 4

84

Cb

线索化二叉树 线索化二叉树 (Threaded Binary Tree)(Threaded Binary Tree)

线索线索 (Thread)(Thread)

增加 增加 Pred Pred 指针和 指针和 Succ Succ 指针的二叉指针的二叉树树

线索化二叉树及其二叉链表表示线索化二叉树及其二叉链表表示

LeftThreadLeftThread=0=0, , LeftChildLeftChild 为为左子女指针左子女指针LeftThreadLeftThread=1=1, , LeftChildLeftChild 为为前驱线索前驱线索RightThreadRightThread=0=0, , RightChildRightChild 为为右子女指针右子女指针RightThreadRightThread=1=1, , RightChildRightChild 为为后继指针后继指针

LeftChildLeftChild RightChildRightChilddatadata

LeftThreadLeftThread RightThreadRightThread

中序线索化二叉树的类定义中序线索化二叉树的类定义template <class Type> class ThreadTree;

template <class Type> class ThreadNode { friend class ThreadTree<Type>;private: int leftThread, rightThread; ThreadNode<Type> *leftChild, *rightChild; Type data; public: ThreadNode ( const Type item ) : data (item), leftChild (NULL), rightChild (NULL), leftThread (0), rightThread (0) { }};

template <class Type> class ThreadTree {private: ThreadNode<Type> * root; // 根 InThread ( ThreadNode <Type> * current, ThreadNode <Type> * pre ); // 建树public: ThreadTree ( ) : root (NULL) { }; // 构造函数 ThreadNode<Type> * First ( ThreadNode <Type> * current ); ThreadNode<Type> * Last ( ThreadNode <Type> * current );

ThreadNode<Type> * Next ( ThreadNode <Type> * current ); ThreadNode<Type> * Prior ( ThreadNode <Type> * current ); …………}

通过中序遍历建立中序线索化二叉树通过中序遍历建立中序线索化二叉树template <class Type> void ThreadTree<Type> ::InThread ( ThreadNode<Type> * current, ThreadNode<Type> *& pre ) { if ( current != NULL ) {

InThread ( current->leftChild, pre ); // 递归 , 左子树线索化

if ( current->leftChild == NULL ) { current->leftChild = pre; current->leftThread = 1; } // 建立当前结点的前驱线索

if ( pre != NULL && pre->rightChild == NULL ) { pre->rightChild = current; pre->rightThread = 1; } // 建立前驱结点的后继线索

pre = current; // 前驱跟上当前指针InThread ( current->rightChild, pre ); // 递归 , 右子树线索化

}}

template <class Type> void ThreadTree<Type> :: CreateInThread ( ) { ThreadNode<Type> *pre = NULL;

// 前驱指针 if ( root != NULL ) { // 非空二叉树 , 线索化

InThread ( root, pre ); // 中序遍历线索化二叉树

pre->rightChild = NULL; pre->rightThread = 1; // 后处理 , 中序最后一个结点 }}

0 A 0

0 B 0 0 C 0

0 D 0 0 E 0

rootpre == NULL current

0 A 0

1 B 0 0 C 0

0 D 0 0 E 0

rootpre == NULL

current

0 A 0

1 B 0 0 C 0

1 D 0 0 E 0

root

pre

current

0 A 0

1 B 0 0 C 0

1 D 1 0 E 0

root

pre

current

0 A 0

1 B 0 0 C 0

1 D 1 1 E 0

rootpre

current

0 A 0

1 B 0 0 C 0

1 D 1 1 E 1

root

pre

current

0 A 0

1 B 0 0 C 1

1 D 1 1 E 1

root

pre后处理

if (current->rightThread ==1) 后继为 current->rightChildelse //current->rightThread != 1 后继为当前结点右子树 的中序下的第一个结点

寻找当前结点在中序下的后继寻找当前结点在中序下的后继

AA

BB

DD EE

CC

FF

HH II

KK

GG

JJ

if (current->leftThread==1) 前驱为 current->leftChild else //current->leftThread==0 前驱为当前结点左子树 中序下的最后一个结点

寻找当前结点在中序下的前驱寻找当前结点在中序下的前驱

AA

BB

DD EE

CC

FF

HH II

KK

GG

JJ

LL

在中序线索化二叉树中部分成员函数的实现在中序线索化二叉树中部分成员函数的实现

template <class Type> ThreadNode<Type> * ThreadTree <Type> :: First ( ThreadNode<Type> * current ) {// 函数返回以 *current 为根在线索化二叉树中// 的中序序列下的第一个结点 ThreadNode<Type> * p = current; while ( p->leftThread == 0 ) p = p->leftChild; // 最左下的结点 return p;}

template <class Type> ThreadNode<Type> * ThreadTree <Type> :: Next ( ThreadNode<Type> * current ) {// 函数返回在线索化二叉树中结点 *current 在// 中序下的后继结点 ThreadNode<Type> *p = current->rightChild; if ( current->rightThread == 0 ) return First ( p ); //rightThread == 0, 表示有右子女 else return p;

//rightThread == 1, 直接返回后继线索}

template <class Type> void ThreadTree <Type> :: Inorder ( ) {// 线索化二叉树的中序遍历 ThreadNode<Type> * p; for ( p = First ( ); p != NULL; p = Next ( ) ) cout << p->data << endl;}

前序线索化二叉树前序线索化二叉树

前序序列前序序列 A B D C EA B D C E

在前序线索化二叉树中在前序线索化二叉树中 寻找当前结点的后继寻找当前结点的后继

p->leftThread==1?

前驱线索 = 左子女pp->->rightChildrightChild == NULL== NULL

后继为p->leftChild

= 无后继 后继为

p->rightChild

A

B C

ED

后序线索化二叉树后序线索化二叉树

后序序列后序序列 D B E C AD B E C A

在后序线索化二在后序线索化二叉树中寻找当前叉树中寻找当前结点的后继结点的后继

p->rightThread==1?

后继线索 = 右子女

后继为后继为 qq 的右子树中的右子树中后序下第一个结点后序下第一个结点

后继为p->rightChild

=

无后继

后继为 q

A

B C

ED

q=p->parentq==NULL?

Q->rightThread==1 ||q->rightChild==p?

=

堆 堆 ( Heap )( Heap )

template <class Type> class MinPQ {public: Virtual void Insert ( const Type & ) = 0; Virtual Type * Remove ( Type & ) = 0;}

最小优先级队列类的定义

优先级队列优先级队列 每次出队列的是优先权最高的元素每次出队列的是优先权最高的元素

完全二叉树完全二叉树 顺序表示顺序表示KKii KK22ii+1+1 && KKii KK22ii+2+2

完全二叉树完全二叉树 顺序表示顺序表示KKii KK22ii+1+1 && KKii KK22ii+2+2

堆的定义堆的定义09

09

87

87

78

7845 45

65

65 31

3153 23

23

53

17

17

最小堆的类定义最小堆的类定义#define DefaultSize 10template <class Type> class MinHeap : public MinPQ <Type> {private: Type * heap; // 存放最小堆元素的数组 int CurrentSize; // 最小堆当前元素个数 int MaxHeapSize; // 最多允许元素个数 void FilterDown ( int i, int m ); // 从 i 到 m 自顶向下进行调整成为最小堆

void FilterUp ( int i ); // 从 i 到 0 自底向上进行调整成为最小

堆public: MinHeap ( int sz ); // 构造函数 : 建立空堆 MinHeap ( Type arr[ ], int n ); // 构造函数 MinHeap ( const MinHeap& R ); ~MinHeap ( ) { delete [ ] heap; } int Insert ( const Type& x ); // 插入 int Remove ( Type& x ); // 删除

int IsEmpty ( ) const // 判堆空否 { return CurrentSize == 0; } int IsFull ( ) const // 判堆满否 { return CurrentSize == MaxHeapSize; } void MakeEmpty ( ) { CurrentSize = 0; }}

堆的建立堆的建立template <class Type> MinHeap <Type> ::MinHeap ( int maxSize ) {// 根据给定大小 maxSize, 建立堆对象

MaxHeapSize = DefaultSize < maxSize ? maxSize : DefaultSize; // 确定堆的大小 heap = new Type [MaxHeapSize]; if ( heap == NULL ) { cerr << “ 存储分配错 !” << endl; exit(1); } CurrentSize = 0; }

template <class Type> MinHeap <Type> :: MinHeap ( Type arr[ ], int n ) {// 根据给定数组中的数据和大小 , 建立堆对象

MaxHeapSize = DefaultSize < n ? n : DefaultSize; heap = new Type [MaxHeapSize]; if ( heap == NULL ) { cerr << “ 存储分配错 !” << endl; exit(1); } for ( int i = 0; i < n; i++ ) // 数组传送 heap[i] = arr[i]; CurrentSize = n; // 当前堆大小 int currentPos = (CurrentSize-2)/2; // 找最初调整位置 :最后的分支结点号

while ( currentPos >= 0 ) { // 从下到上逐步扩大 ,形成堆 FilterDown ( currentPos, CurrentSize-1 ); // 从 currentPos 开始 ,到 CurrentSize止 , // 调整 currentPos--; }}

自下向上逐步调整为最小堆自下向上逐步调整为最小堆

将一组用数组存放的任意数据调整成堆将一组用数组存放的任意数据调整成堆

53 53

17 1778 78

09

23 45 65 87

i

09

23

45 65 87

currentPos = i = 3 currentPos = i = 2

i

53 53

17

1778 7809

23

45

65

87

i

09

23

45

65

87

currentPos = i = 1

i

53

53

17 1778 78

09

23

45

65

87

i

09

23

45

65

87

currentPos = i = 0

i

09

53

53

17 17

78 78

09

23

45

65

87

i

09

23 45

65

87i17

最小堆的向下调整算法最小堆的向下调整算法template <class Type> void MinHeap<Type> :: FilterDown ( int start, int EndOfHeap ) { int i = start, j = 2*i+1; // j 是 i 的左子女 Type temp = heap[i]; while ( j <= EndOfHeap ) { if ( j < EndOfHeap && heap[j] > heap[j+1] ) j++; // 两子女中选小者

if ( temp <= heap[j] ) break; else { heap[i] = heap[j]; // 下面的上浮 i = j; j = 2*j+1; // 向下滑动 } } heap[i] = temp;}

堆的插入堆的插入template <class Type> int MinHeap<Type> :: Insert ( const Type &x ) {

// 在堆中插入新元素 x if ( CurrentSize == MaxHeapSize ) // 堆满 { cerr << "堆已满 " << endl; return 0; } heap[CurrentSize] = x; // 插在表尾 FilterUp (CurrentSize); // 向上调整为堆 CurrentSize++; // 堆元素增一 return 1;}

最小堆的向上调整算法最小堆的向上调整算法

template <class Type> void MinHeap<Type> :: FilterUp ( int start ) {// 从 start 开始 ,向上直到 0, 调整堆 int j = start, i = (j-1)/2; // i 是 j 的双亲 Type temp = heap[j]; while ( j > 0 ) { if ( heap[i] <= temp ) break; else { heap[j] = heap[i]; j = i; i = (i -1)/2;} } heap[j] = temp;}

53

17 17

78 78

09

23 45

65

87

i

09

23 45

65

87

j

11

在堆中插入新元素 11

53j

11

23

i

最小堆的向上调整最小堆的向上调整

53

17 11

78 78

09

45

65

87

09

23 45

65

87

j

11

53 23

i

23

17

j

i

最小堆的删除算法最小堆的删除算法template <class Type> int MinHeap <Type> ::Remove ( Type &x ) { if ( !CurrentSize ) { cout << “ 堆已空 " << endl; return 0; } x = heap[0]; // 最小元素出队列 heap[0] = heap[CurrentSize-1]; CurrentSize--; // 用最小元素填补 FilterDown ( 0, CurrentSize-1 ); // 调整 return 1;}

树的存储表示树的存储表示 广义表表示广义表表示

树的广义表表示树的广义表表示 ( 结点的 utype域没有画出 )

树与森林树与森林

A

B C D

E F G

A

B E F

C

D G

双亲表示双亲表示

A

B C D

E F G

data

parent

A B C D E F G

-1 0 0 0 1 1 3

0 1 2 3 4 5 6

左子女左子女 -- 右兄弟表示右兄弟表示法法第一种解决方案第一种解决方案

data data child1 child2 child3 childd

A

B C D

E F G

第一种解决方案 第一种解决方案 等数量的链域等数量的链域 data data child1 child2 child3 childd

A

B C D

E F G

空链域 2n+1 个空链域 2n+1 个

第二种解决方案第二种解决方案

树的左子女 树的左子女 -- 右兄弟表右兄弟表示示

data data firstChild nextSibling

A

B C D

E F G

A

B

C

D

G

F

E

用左子女用左子女 -- 右兄弟表示实现的树的类定义右兄弟表示实现的树的类定义template <class Type> class Tree;

template <class Type> class TreeNode {friend class<Type> Tree;private: Type data; TreeNode<Type> *firstChild, *nextSibling;public: TreeNode ( Type value=0, TreeNode<Type> *fc=NULL, TreeNode<Type> *ns=NULL )

: data (value), firstChild (fc), nextSibling (ns) { }};

template <class Type> class Tree {private: TreeNode<Type> *root, *current; // 根指针及当前指针 void PreOrder ( ostream& out, TreeNode<Type> *p ); // 先根次序遍历并输出以 p 为根的树 int Find ( TreeNode<Type> *p, Type value );

void RemovesubTree ( TreeNode<Type> * p); // 删除以 p 为根的子树 int FindParent ( TreeNode<Type> * t, TreeNode<Type> * p );

// 在树 t 中搜索结点 p 的双亲public: Tree ( ) { root = current = NULL; } int Root ( ); int FirstChild ( ); int NextSibling ( ); int Parent ( );

int Find ( Type target ); // 树的其它公共操作

……}

template <class Type> int Tree <Type> :: Root ( ) {// 让树的根结点成为树的当前结点 if ( root == NULL ) { current = NULL; return 0; } else { current = root; return 1; }}

template <class Type> int Tree<Type> :: Parent ( ) {// 在树中找当前结点的双亲 , 成为当前结点 TreeNode<Type> * p = current, *t; if ( current == NULL || current == root ) { current = NULL; return 0; } // 空树或根为当前结点 , 返回 0 t = root; int k = FindParent ( t, p ); // 从 t 找 p 双亲 return k;}

template <class Type>int Tree<Type> :: FindParent ( TreeNode<Type> *t, TreeNode<Type> *p ) {// 在根为 t 的树中找 p 的双亲 , 成为当前结点 TreeNode<Type> *q = t->firstChild; while ( q != NULL && q != p ) { // 循根的长子的兄弟链 ,递归在子树中搜索 if (( int i = FindParent (* q,* p) ) != 0 ) return i; q = q->nextSibling; }

if ( q != NULL && q == p ) { current = t; return 1; } else return 0; // 未找到双亲 ,当前结点不变}

template <class Type> int Tree <Type> :: FirstChild ( ) {// 在树中找当前结点的长子 ,成为当前结点 if (current != NULL && current->firstChild != NULL ) { current = current->firstChild; return 1; } current = NULL; return 0;}

template <class Type> int Tree <Type> :: NextSibling ( ) {// 在树中找当前结点的兄弟 ,成为当前结点 if ( current != NULL && current->nextSibling != NULL ) { current = current->nextSibling; return 1; } current = NULL; return 0;}

template <class Type> int Tree<Type> :: Find ( Type value ) { if ( IsEmpty ( ) ) return 0;

return Find ( root, value );}

template <class Type> int Tree <Type>::Find ( TreeNode <Type> * p, Type value ) {// 在根为 p 的树中找值为 target 的结点 , 找到// 后该结点成为当前结点 , 否则当前结点不变。// 函数返回成功标志: =1, 成功 ; =0, 失败 int result = 0; if ( p->data == value ) { result = 1; current = p; } // 搜索成功 else // 搜索失败

{ TreeNode<Type> *q = p->firstChild; while ( q != NULL && ! ( result = Find ( q, value ) ) ) q = q->nextSibling; } return result;}

森林与二叉树的转换森林与二叉树的转换

森林与二叉树的对应关系森林与二叉树的对应关系

T1 T2 T3A F H

T1 T2 T3A

B C D G I J

E K

F

BC

D

E

G

H

I

K J

A

BC

E

DH

I

K J

F

G3 棵树的森林

各棵树的二叉树表示

森林的二叉树表示

(1) (1) 森林转化成二叉树的规则森林转化成二叉树的规则

若 若 FF 为空为空,即 ,即 nn = 0 = 0 ,则,则 对应的对应的二叉树 二叉树 BB 为为空二叉树空二叉树。。 若 若 FF 不空不空,则,则 对应对应二叉树二叉树 BB 的根的根 root (B) root (B) 是是 FF 中中第一棵树 第一棵树 TT1 1 的根的根 rootroot (T (T11)) ;; 其其左子树左子树为为 B (TB (T1111, T, T1212, …, T, …, T1m1m)) ,,其中,其中, TT1111, T, T1212, …, T, …, T1m 1m 是 是 root (Troot (T11) ) 的子树;的子树; 其其右子树右子树为为 B (TB (T22, T, T33, …, T, …, Tnn)) ,,其中,其中, TT22, T, T33, …, T, …, Tnn 是除 是除 TT1 1 外外其它树构其它树构成的森林成的森林。。

((2) 2) 二叉树转换为森林的规则二叉树转换为森林的规则

如果 如果 BB 为空,则对应的为空,则对应的森林 森林 FF 也为空。也为空。 如果 如果 BB 非空,则非空,则 FF 中中第一棵树第一棵树 TT11 的根为 的根为 rootroot ;; TT11 的根的子树森林 的根的子树森林 { T{ T1111, T, T1212, …, T, …, T1m 1m }}

是由是由 root root 的的左子树左子树 LBLB 转换而来,转换而来, F F 中中除了 除了 TT1 1 之外之外其余的树组成的森林其余的树组成的森林 { T{ T22, T, T33,,

…, T …, Tn n }} 是由是由 rootroot 的的右子树右子树 RBRB 转换而转换而成的森林。成的森林。

树的二叉树表示树的二叉树表示

树的遍历树的遍历 深度优先遍历深度优先遍历

先根次序遍历先根次序遍历 后根次序遍历后根次序遍历

广度优先遍历广度优先遍历A

B

CE

D

A

B C D

E F G G

F

树的树的先根次序先根次序遍历遍历 当树非空时当树非空时

访问根结点;访问根结点; 依次先根遍历根的各棵依次先根遍历根的各棵 子树。子树。

树先根遍历 树先根遍历 ABEFCDGABEFCDG 。。 对应二叉树前序遍历 对应二叉树前序遍历 ABEFCDGABEFCDG 。。 树的先根遍历结果与其对应二叉树树的先根遍历结果与其对应二叉树 表示的前序遍历结果相同。表示的前序遍历结果相同。 树的先根遍历可以借助对应二叉树的前序遍历算法实现。树的先根遍历可以借助对应二叉树的前序遍历算法实现。

A

B

CE

D

G

F

树的树的后根次序后根次序遍历遍历 当树非空时当树非空时

依次后根遍历根的各棵依次后根遍历根的各棵 子树;子树; 访问根结点。访问根结点。

树后根遍历 树后根遍历 EFBCGDAEFBCGDA 。。 对应二叉树中序遍历 对应二叉树中序遍历 EFBCGDAEFBCGDA 。。 树的后根遍历结果与其对应二叉树树的后根遍历结果与其对应二叉树 表示的中序遍历结果相同。表示的中序遍历结果相同。 树的后根遍历可以借助对应二叉树的中序遍历算法实现。树的后根遍历可以借助对应二叉树的中序遍历算法实现。

A

B

CE

D

G

F

(1) (1) 树的先根次序遍历的递归算法。树的先根次序遍历的递归算法。template <class Type> void Tree<Type> :: PreOrder ( ) {// 以当前指针 current 为根 , 先根次序遍历 if ( IsEmpty ( ) == 0 ) { visit ( ); // 访问根结点 TreeNode<Type> *p = current; current = current ->firstChild; // 第一棵子树 while ( current != NULL ) { PreOrder ( ); // 递归先根遍历子树 current = current ->nextSibling; }

current = p; // 恢复当前指针 }}

(2) (2) 树的后根次序遍历的递归算法。树的后根次序遍历的递归算法。template <class Type> void Tree<Type> :: PostOrder ( ) {// 以当前指针 current 为根 , 按后根次序遍历树 if ( IsEmpty ( ) == 0 ) { TreeNode<Type> *p = current; current = current ->firstChild;

while ( current != NULL ) { PostOrder ( ); current = current->nextSibling; } current = p; visit ( );

// 恢复当前指针 , 最后访问根结点 }}

(3) (3) 广度优先广度优先 (( 层次次序层次次序 )) 遍历。遍历。按广度优先次序遍历树的结果。按广度优先次序遍历树的结果。 ABCDEFG

A

B

CE

D

G

F

广度优先遍历算法广度优先遍历算法template <class Type> void Tree<Type> :: LevelOrder ( ) {// 按广度优先次序分层遍历树 , 树的根结点是// 当前指针 current 。算法中用到一个队列。 Queue<TreeNode<Type>*> Qu(DefaultSize); TreeNode<Type> *p; if ( current != NULL ) { // 当前指针不空 p = current; // 保存当前指针 Qu.EnQueue ( current ); while ( Qu.IsEmpty ( ) == 0 ) {

current = Qu.getFront( ); Qu.DeQueue ( ); visit ( ); // 队列中取一个并访问之 current = current ->firstChild ;// 待访问结点的子女结点进队列

while ( current != NULL ) { Qu.EnQueue ( current ); current = current->nextSibling;

} } current = p; // 恢复算法开始的当前指针 }}

满 满 k k 叉树的性质叉树的性质 一棵高度为 h 的满 k 叉树有如下性质 : 第 h 层上的结点都是叶结点 , 其余各层上每个结点都有 k 棵非空子树 , 如果按层次自顶向下 , 同一层自左向右 , 顺序从 0 开始对全部结点进行编号 : 第 i 层的结点个数是 ki 个 ( i = 0, 1, …, h ) 编号为 i 的结点的父结点 ( 若存在 ) 的编号是

k

1ii = 0 无父结点

0

1

9 10 11 12 13 14 15 16 17 18 19 205 6 7 8

2 3 4

2 level

1 level

0 level

编号为 i 的结点的第 m 个孩子结点 ( 若存在 )的编号是 i*k+m ( 1 ≤ m≤ k )

编号为 i 的结点有右兄弟的条件是 i % k 0 有右兄弟时,其右兄弟结点的编号是 i+1 。 编号为 i 的结点所在层次为: log k (i*(k-1)+1)

设满 k 叉树的结点个数为 n ,又设满 k 叉树的高度为 h ( 根在第 0 层 ), 则

n = k0 + k1 + + kh = (kh+1-1) / (k-1)

则 kh+1 = (k-1)*n + 1

取对数 h+1 = logk( (k-1)*n+1))

h = logk( (k-1)*n+1)) - 1

森林的遍历森林的遍历

森林的二叉树表示森林的二叉树表示ABCDE FG HIKJABCDE FG HIKJ

(1) (1) 先根次序遍历的规先根次序遍历的规则则::

若森林 若森林 F F 为空为空 ,, 返回;否则返回;否则 访问 访问 F F 的第一的第一棵棵树的根结点;树的根结点; 先根次序遍历第先根次序遍历第一棵树的子树森林;一棵树的子树森林; 先根次序遍历其先根次序遍历其它树组成的森林。它树组成的森林。

A

BC

E

DH

I

K J

F

G

森林的遍历森林的遍历

森林的二叉树表示森林的二叉树表示EDCB GKJIHFAEDCB GKJIHFA

(3) (3) 后根次序遍历的规则:后根次序遍历的规则:

若森林 若森林 F F 为空,为空,返回;否则返回;否则 后根次序遍历第后根次序遍历第一棵树的子树森林;一棵树的子树森林; 后根次序遍历其后根次序遍历其它树组成的森林;它树组成的森林; 访问 访问 F F 的第一的第一棵棵树的根结点。树的根结点。

A

BC

E

DH

I

K J

F

G

森林的遍历森林的遍历

森林的二叉树表示森林的二叉树表示

(4) (4) 广度优先遍历广度优先遍历 (( 层次层次序遍历序遍历 ) ) ::

若森林 若森林 F F 为空,为空,返回;否则返回;否则 依次遍历各棵树依次遍历各棵树的根结点;的根结点; 依次遍历各棵树依次遍历各棵树根结点的所有子女;根结点的所有子女; 依次遍历这些子依次遍历这些子女结点的子女结点女结点的子女结点。

A

BC

E

DH

I

K J

F

G

Huffman 树路径长度 路径长度 (Path Length)(Path Length)

两个结点之间的路径长度 两个结点之间的路径长度 PLPL 是连是连接两结点的路径上的分支数。树的路径长接两结点的路径上的分支数。树的路径长度是各叶结点到根结点的路径长度之和。度是各叶结点到根结点的路径长度之和。 树的外部路径长度是各叶结点树的外部路径长度是各叶结点 (( 外结外结点点 )) 到根结点的路径长度之和 到根结点的路径长度之和 EPLEPL 。。 树的内部路径长度是各非叶结点树的内部路径长度是各非叶结点 ((内内结点结点 )) 到根结点的路径长度之和到根结点的路径长度之和 IPLIPL 。。 树的路径长度 树的路径长度 PL = EPL + IPLPL = EPL + IPL

1 1

2 23 3

4 45 56

6

7

78

8树的路径长度树的路径长度PL = 0+1*2+

+2*4+3*1 = 13树的路径长度树的路径长度

PL = 0+1*2+2*2++3*2+4*1 = 16

n n 个结点的二叉树的路径长度不小于个结点的二叉树的路径长度不小于下述数列前 下述数列前 n n 项的和,即项的和,即

其路径长度最小者为其路径长度最小者为

1

02 1)(log

n

i

iPL

带权路径长度带权路径长度 ((Weighted Path Length, WPL)Weighted Path Length, WPL)

树的带权路径长度是树的各叶结点所带的权树的带权路径长度是树的各叶结点所带的权值与该结点到根的路径长度的乘积的和。值与该结点到根的路径长度的乘积的和。

1

0

n

iii lwWPL

1

02 1)(log

n

i

iPL

332222110

具有不同带权路径长度的扩充二叉树具有不同带权路径长度的扩充二叉树

WPL = 2*2+ WPL = 2*1+ WPL = 7*1+WPL = 2*2+ WPL = 2*1+ WPL = 7*1+ 4*2+5*2+ 4*2+5*3+ 5*2+2*3+4*2+5*2+ 4*2+5*3+ 5*2+2*3+ 7*2 = 36 7*3 = 46 4*3 = 35 7*2 = 36 7*3 = 46 4*3 = 35

带权路径长度达到最小带权路径长度达到最小

2

2

24

4

45

5

5

7

7

7

HuffmanHuffman 树树 带带权路径长度达到最小的扩充二叉树即为权路径长度达到最小的扩充二叉树即为

HuffmanHuffman 树。树。 在在 HuffmanHuffman 树中,权值树中,权值大的结点离根最近。大的结点离根最近。

HuffmanHuffman 算法算法 (1)(1) 由给定的由给定的 nn 个权值个权值 {{ww00, , ww11, , ww22, , ……, , wwnn--

11}} ,,构造具有构造具有 nn 棵扩充二叉树的森林棵扩充二叉树的森林 FF = { = {

TT00, , TT11, , TT22, , ……, , TTnn-1-1 }} ,,其中每棵扩充二叉树其中每棵扩充二叉树 TTi i

只有一 个带权值只有一 个带权值 wwi i 的根结点的根结点 , , 其左、右子其左、右子树均为空。树均为空。

(2)(2) 重复以下步骤重复以下步骤 , , 直到 直到 FF 中仅剩下一中仅剩下一棵树为止:棵树为止: ① ① 在 在 FF 中选取两棵中选取两棵根结点的权值最根结点的权值最小小的扩充二叉树的扩充二叉树 , , 做为左、右子树做为左、右子树构造一构造一棵新的二叉树。置新的二叉树的棵新的二叉树。置新的二叉树的根结点的权根结点的权值为其左、右子树上根结点的权值之和值为其左、右子树上根结点的权值之和。。 ② ② 在 在 FF 中删去这两棵二叉树。中删去这两棵二叉树。 ③ ③ 把新的二叉树加入 把新的二叉树加入 FF 。。

HuffmanHuffman 树的构造过程树的构造过程

F : {7} {5} {2} {4} F : {7} {5} {6}

7 5 2 4

初始合并 {2} {4}

F : {7} {11}

7 5

2 4

7 5

2 4

6

6

11

合并 {5} {6}

F : {18}

5合并 {5} {6}

2

7

4

6

11

18

扩充二叉树的类定义扩充二叉树的类定义const int DefaultSize = 20; template <class Type> class ExtBinTree;

template <class Type> class Element {friend class ExtBinTree;private: Type data; Element<Type> * leftChild, * rightChild;};

template <class Type> class ExtBinTree {

public: ExtBinTree (ExtBinTree<Type>& bt1, ExtBinTree<Type> & bt2 ) { root->leftChild = bt1.root; root->rightChild = bt2.root; root->data.key = bt1.root->data.key + bt2.root->data.key; } protected: Element<Type> *root; // 二叉树的根}

建立建立 HuffmanHuffman 树的算法树的算法template <class Type> void HuffmanTree (Type *fr, int n, ExtBinTree <Type> & newTree ) { ExtBinTree <Type> & first, & second; ExtBinTree <Type> Node[DafualtSize]; MinHeap < ExtBinTree <Type> > hp; if ( n > DefaultSize ) { cerr << “ 大小 n ” << n << “ 超出了数组 边界 ” << endl; return; }

for ( int i = 0; i < n; i++ ) { Node[i].root->data = fr[i]; Node[i].root->leftChild = Node[i].root ->rightChild = NULL; } hp.MinHeap ( Node, n ); // 建立存储扩充二叉树森林的最小堆 for ( int i = 0; i < n-1; i++ ) { // 做 n-1 趟 , 逐步形成霍夫曼树 hp.Remove ( first ); // 选择最小 hp.Remove ( second ); // 选择次小

newTree = new ExtBinTree<Type> (first, second); // 合并 hp.Insert ( newTree );

// 重新插入到最小堆中 }}

采用静态链表的采用静态链表的 HuffmanHuffman 树树 可以采用静态链表方式存储 Huffman 树。为查找在构造过程中森林里的树根,为每个结点设置双亲指针。

const int n = 20;const int m = 2*n -1;

typedef struct { float weight; int parent, lchild, rchild;} HTNode;

typedef HTNode HuffmanTree[m];

建立 Huffman 树的过程如图所示:

HuffmanHuffman 树的定义树的定义

5 27 4

Weight parent lchild rchild

7 -1 -1 -1

5 -1 -1 -1

2 -1 -1 -1

4 -1 -1 -1

-1 -1 -1

-1 -1 -1

-1 -1 -1

0

1

2

3

4

5

6

5

2

7

4

6

Weight parent lchild rchild

7 -1 -1 -1

5 -1 -1 -1

2 -1 -1 -1

4 -1 -1 -1

6 -1 -1 -1

-1 -1 -1

-1 -1 -1

0

1

2

3

4

5

6

p1

p24

4

2 3i

5

2

7

4

6

11

Weight parent lchild rchild

7 -1 -1 -1

5 -1 -1 -1

2 4 -1 -1

4 4 -1 -1

6 -1 2 3

11 -1 -1 -1

-1 -1 -1

0

1

2

3

4

5

6

p1

p2

5

5

1 4i

5

2

7

4

6

11

Weight parent lchild rchild

7 -1 -1 -1

5 5 -1 -1

2 4 -1 -1

4 4 -1 -1

6 5 2 3

11 -1 1 4

18 -1 -1 -1

0

1

2

3

4

5

6

p1

p2

6

6

0 5i

18

建立建立 HuffmanHuffman 树的算法树的算法void CreateHuffmanTree ( HuffmanTree T, float fr[ ], int n ) { for ( int i = 0; i < n; i++ ) T[i].weight = fr[i]; for ( i = 0; i < m; i++ ) { T[i].parent = -1; T[i].lchild = -1; T[i].rchild = -1; } for ( i = n; i < m; i++ ) {

int min1 = min2 = MaxNum; int pos1, pos2; for ( int j = 0; j < i; j++ ) if ( T[j].parent == -1 ) if ( T[j].weight < min1 ) { pos2 = pos1; min2 = min1; pos1 = j; min1 = T[j].weight; } else if ( T[j].weight < min2 ) { pos2 = j; min2 = T[j].weight; } T[i].lchild = pos1; T[i].rchild = pos2;

T[i].weight = T[pos1].weight + T[pos2].weight; T[pos1].parent = T[pos2].parent = i; }}

最佳判定树最佳判定树考试成绩分布表考试成绩分布表 [0, 60 ) [60, 70 ) [70, 80 ) [80, 90 ) [90, 100 )

不及格 及格 中 良 优0.10 0.15 0.25 0.35 0.15

判定树判定树

不及格不及格

及格及格

中中

良良 优优

<60?

<70?

<80?

<90?

0.10

0.15

0.25

0.35 0.15

≥≥

≥≥

≥≥

≥≥<<

<<

<<

<<

WPL = 0.10*1+0.15*2+0.25*3+0.35*4+0.15*4

= 3.15

最佳判定树最佳判定树

不及格不及格 及格及格

中中 良良 优优<60?

<70?

<80?

<90?

0.10 0.15

0.25 0.35 0.15

≥≥

≥≥

≥≥

≥≥<<

<<

<<

<<

WPL = 0.10*3+0.15*3+0.25*2+0.35*2+0.15*2

= 0.3+0.45+0.5+0.7+0.3 = 2.25

HuffmanHuffman 编码编码主要用途是实现数据压缩。主要用途是实现数据压缩。 设给出一段报文:设给出一段报文:

CAST CAST SAT AT A TASACAST CAST SAT AT A TASA 字符集合是 字符集合是 { C, A, S, T }{ C, A, S, T } ,各个字符,各个字符出现的频度出现的频度 (( 次数次数 )) 是 是 WW == { 2, 7, 4, 5 }{ 2, 7, 4, 5 } 。。 若给每个字符以等长编码若给每个字符以等长编码 AA : 00 T : 10 C : 01 S : 11 : 00 T : 10 C : 01 S : 11

则总编码长度为则总编码长度为 ( 2+7+4+5 ) * 2 = 36( 2+7+4+5 ) * 2 = 36 。 。

若按各个字符出现的概率不同而给予不若按各个字符出现的概率不同而给予不等长编码,可望减少总编码长度。等长编码,可望减少总编码长度。 各字符出现概率为各字符出现概率为 { 2/18, 7/18, 4/18, 5/1{ 2/18, 7/18, 4/18, 5/188 }},, 化整为化整为 { 2, 7, 4, 5 }{ 2, 7, 4, 5 } 。以它们为各叶结。以它们为各叶结点上的权值点上的权值 , , 建立建立 HuffmanHuffman 树。树。左分支左分支赋赋 00 ,,右分支右分支赋赋 11 ,得,得 HuffmanHuffman 编码编码 (( 变长编变长编码码 )) 。。

7 2 5 4

0 1

00 11

AA CC TT SS

AA : 0 T : 10 C : 110 S : 111 : 0 T : 10 C : 110 S : 111

它的总编码长度它的总编码长度:: 7*1+5*2+( 2+4 )*3 = 357*1+5*2+( 2+4 )*3 = 35 。。比等长编码的情形要短比等长编码的情形要短。。 总编码长度正好等于总编码长度正好等于 HuffmanHuffman 树的带树的带权路径长度权路径长度 WPLWPL 。。 HuffmanHuffman 编码是一种无编码是一种无前缀编码。解码时不会混前缀编码。解码时不会混淆。淆。

Huffman 编码树

0

0

0

1

1

1

2 4

5

7