1 / 86

第五章 树及二叉树

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

tanuja
Download Presentation

第五章 树及二叉树

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

  2. 高度为 4 、度为 3 的树 A B C D L E F G H I 树和森林 • 树:n > 0 个结点的集合,根+其余结点分为 m >= 0 个集合,每一个集合本身又是一棵树(子树) • 结点的度:该结点的子树数目 • 树的度:树中各结点度数的最大值 • 叶子、父结点、儿子结点、兄弟结点 • 祖先结点:从根结点到该结点的路径上所有结点 • 层次、高度:根为1, 依次往下数 • 有序树:规定所有结点的儿子结点次序,否则为无序树 • 森林:m >= 0 棵互不相交树的集合 其它表示方法: 1. ( A(B(L,E),C(F),D(G(I),H)) 2. 类似于书籍的目录表示法。 高度定义为层数或层数-1,都可以;本书定义为层数。

  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。

  4. B 例: 二叉树 C L D F E 例:结点总数为 3 时的所有二叉树的树的形状 二叉树的定义 • 空或有一个根,根有左子树、右子树;而左右子树本身又是二叉树。 • 和树的区别:可为空 • 左右子树有序,不可颠倒

  5. A • 性质2:高度为 k 的二叉树至多有2 k- 1 个结点 C • 证:利用性质 1 ,将第 1 层至第 k 层的最多的结点数进行相加: • 1 + 2 + 22 + ………… + 2k-2 + 2k-1 = 2k - 1 L E F 二叉树的性质 • 性质1:在二叉树的第 i 层上至多有 2 i-1个结点 1层:结点个数 21-1=20个 2层:结点个数 22-1=21个 3层:结点个数 23-1=22个 A C L D B E F • 证:k = 1 时成立,设 k = i-1 时成立 则当 k = i 时在二叉树的第 i 层上至多有 • 2 i-1-1 * 2 = 2 i-1 个结点 • 性质3:二叉树的叶子结点数 n0 等于度为 2 的结点数n2 + 1 • 证:设度为 1 的结点数为 n1,树枝的总数为 B 则:B = n-1=n0+n1+n2-1 • 又 B = n1+2×n2; 原命题得证。

  6. 满二叉树: • 每层结点 • 数最多 A A C C L L B E F D B E F D P Q R S U V W X P Q R S U 完全二叉树 • 完全二叉树: • 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 ; 原命题得证。

  7. 完全二叉树 • 性质5:对一棵有 n 个结点的完全二叉树按照从第一层(根所在的层次〕到最后一层,并且 • 每一层都按照从左到右的次序进行编号。根结点的编号为 1,最后一个结点的编号 • 为 n。 • 1:对任何一个编号为 i 的结点而言,它的左儿子的编号为 2i( 若 2i <= n) ,而右儿子的 编号为 2i+1(若 2i +1 <= n)。 • 2:对任何一个编号为 j 的结点而言,它的父亲结点的的编号为 j/2 。根结点无父结 点。 • 证:对编号归纳 A 1 C L 3 2 B E F D 4 6 7 5 P Q R S U 8 10 11 12 9

  8. data left right 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 A B C L D F E G I H 二叉树的顺序存储 • 一般情况: • 应用范围:适用于二叉树上的结点个数已知,或不支持动态存储分配的高级语言。

  9. 二叉树的顺序存储 • 特殊情况:完全二叉树 data left right 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 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 A B C D E F G H I A B C D E F G H I L L • 应用范围:适用于完全二叉树,且结点个数已知。

  10. 二叉树的顺序存储 • 特殊情况:若不是完全二叉树,许多数据场为空,不合算。如下例所示: • 考虑浪费空间最多的情况,是一种什么情况? 浪费 2^k - 1 – k 个单元 0 1 2 3 4 5 6 7 8 9 A B ∧ D ∧ ∧ ∧ H I A B D H I

  11. A B C L D F E G I H 二叉树的链接存储 • 仅定义结点的类型即可。结点之间的关系通过指针实现。 标准形式:(二叉链表) data left right 广义标准形式:(三叉链表) data left right Parent

  12. A A B C B C ∧ ∧ D E ∧ D /\ E G F ∧ F ∧ ∧ G ∧ 二叉树的链接存储 • 例:

  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 ( ) { } TypeGetData ( ) 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; } // 设置二叉树结点的右儿子地址。

  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; // 结点的数据信息 };

  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( ); } }

  16. 二叉树的ADT • ·二叉树的 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)); }

  17. 二叉树的ADT • ·二叉树的 ADT:求二叉树的结点个数和高度以及删除一棵二叉树。 template <class Type> void BinaryTree<Type> :: DelTree ( BinaryNode < Type> * T ) { // 删除以T 为根的二叉树的所有结点。 if ( T != NULL ) { DelTree( T->left); DelTree( T->right); delete T; } }

  18. A A L R L C L C L D B E R D B E R W L W R X X 二叉树遍历 • 设 N 代表根节点,L 代表左子树,R 代表右子树。 • a. 前序(或先序):如果二叉树为空,则操作为空:否则访问根结点;前序遍历左子树; • 前序遍历右子树。记为:NLR。 • b. 中序: 如果二叉树为空,则操作为空:否则中序遍历左子树;访问根结点; • 中序遍历右子树。记为:LNR。 • c. 后序: 如果二叉树为空,则操作为空:否则后序遍历左子树;后序遍历右子 • 树;访问根结点。记为:LRN。 前序:A、L、B、E、 C、D、W、X 中序:B、L、E、A、 C、W、X、D 后序:B、E、L、X、 W、D、C、A

  19. 二叉树遍历 • a. 前序分析:结点的左儿子、左孙子、左后代、…… 将连续输出。结点的右儿子将在结点、结点的左 子树全部输出之后才输出。 • b. 中序分析:最先输出的结点是根结点的最左的左后代。将二叉树中的结点投影到水平轴线上,则得到 中序遍历的序列。 • c. 后序分析:根结点(或子树的根结点)将在它的左、右子树的结点输出之后。因此,根结点(或子树 的根结点)在后序序列中的序号等于它的左右子树的结点个数 +左右子树中的最先被访问 的结点的序号。注意,结点、右父亲、右祖父、…… 将连续输出。 A A 前序:A、L、B、E、C、D、W、X L C 中序:B、L、E、A、C、W、X、D L C 后序:B、E、L、X、W、D、C、A B E D D B E W W X X B L E A C W X D

  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( ); }

  21. 非递归前序遍历 • 前序的程序实现(非递归): • 1、根结点进栈 • 2、结点出栈,被访问 • 3、结点的右、左儿子(非空)进栈 • 4、反复执行 2、3 ,至栈空为止。 e.g: 前序:A、L、B、E、C、D A L C D B E D出栈访问后,栈空结束 C、L 进栈 E、B 进栈 A进栈 A出栈访问 L出栈访问 B出栈访问 E出栈访问 C出栈访问 D进栈 <B> <L> <E> <E> <A> <C> <C> <C> <C> <C> <D> 分析:p的前序的直接后继q: 1、p有左儿子,则q=该左儿子; 2、p无左儿子,有右儿子,则q=该右儿子; 3、 p无左儿子、右儿子 ,搜索其祖先结点的右儿子送q。找不到结束。

  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非空,则根结点进栈,并得到当前结点。

  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 ++( ); // 使前序次序下的下一个结点成为当前结点。 } }

  24. 非递归后序遍历 • 遍历二叉树:设根结点初始时非空 • 后序算法:1、结点(初始时为根)进栈(标志0),沿左指针查找左儿子(标志1) • 2、左儿子非空,返回1。 • 3、退栈。若为标志1转 4 ,否则标志 2,转 5。 • 4、进栈转向右子树(标志2),如右儿子非空,则转向 1;空转向 3。 • 5、访问该结点,返回 3 • 6、反复 1 到 5 ,至栈空为止。 A e.g: 后序:E、L、D、C、A L C D E 分析:1、二叉树最先被访问的结点是二叉树中最左方的叶子。 2、p的后序的直接后继q: 1、p是父结点的右儿子,则q=该父结点。2、p是父结点的左 儿子,如果有右子树,则q=该右子树中最左的叶子。如果,无右子树,q=父结点。 3、p无父结点,那么q == NULL。

  25. <B> <C> <B> <E> <B> <C> <B> <E> <D> <C> <B> <D> <E> <C> <B> <D> <C> <C> 1 2 2 0 1 2 2 0 1 2 2 1 1 0 1 2 1 0 <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> <A> 2 1 1 2 1 1 1 1 0 2 1 2 2 2 2 非递归后序遍历 • 后序遍历时的实现实例;注意堆栈中保存的是当前结点的祖先。 A (1) (2) (3) (4) (5) B C (6) (7) (8) (9) (10) D E (11) (12) (13) (14) (15)

  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 进栈,去寻找第一个被访问的最左方的叶子。

  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 ( ) ) ); } } }

  28. 非递归中序遍历 • 中序的程序实现: • 1、结点(初始时是根结点)进栈,沿左指针查找左儿子。 • 2、若有左儿子,返回第一步。 • 3、若无左儿子,判栈空?空则结束。非空,栈顶结点出栈 • 访问。转向右子树,返回1。 中序:B、L、E、A、C、W、X、D e.g: A 分析:1、二叉树最先被访问的结点是二叉树中最左方的 的左后代。 2、p的中序的直接后继q: 1、p有右子树,则q=p 的右子树的最左方的左后代。 2、p无右子树,如p是父结点的左儿子,则q=该 父结点。 3、p无右子树,如p是父结点的右儿子 则以p 的父结点为根的子树访问完毕。如该子树为其祖 先结点的左子树,那么q=其根。否则,继续寻 找,找不到结束。 L C D B E W X

  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() ) ); } }

  30. A L C A D B E L C W D B E X W X 中序穿线树 • 为什么采用中序穿线树:二叉树中的空指针场多达 n + 1 个。 • 简单证明:设二叉树中结点的总数为 n,度为 2 的结点总数为 n2 。空指针场用 方框代表。增加了方 • 框结点的二叉树称之为扩展二叉树。在该二叉树之中,原来的 n 个结点全部成为度为 2 的结 • 点,方框结点成为叶子。根据二叉树的性质 3 ,原命题得证。 e.g: n = 8 的二叉树 扩展二叉树 • 怎样利用这 n + 1 个空指针场? • 中序穿线树 • 后序穿线树

  31. 中序穿线树的链接存储 • · • 中序穿线树(中序中序穿线树)的实现: • 在二叉树的结点的标准形式中,增加两个标志域,用于区别是否是穿线。如下所 • 示: ( 注意:在顺序存储一颗二叉树时,可不必使用额外的空间;见下页) • 其中 data 为数据场,leftThread = 0 时,leftchild 场指向本结点的真正的左儿子 • leftThread = 1 时,leftchild 场指向本结点的按中序遍历序列的前驱结点(穿线) • rightThread = 0 时,rightchild 场指向本结点的真正的右儿子 • rightThread = 1 时,rightchild 场指向本结点的按中序遍历序列的后继结点(穿线) leftchild leftThread data rightThread rightright e.g: n = 6 的二叉树 无前驱结点 无后继结点 A A 中序穿线树 L C L C D D B E B E

  32. 中序穿线树的链接存储 • · • 中序穿线树中的结点的数据结构: Root: 根结点指针 无前驱结点 无后继结点 A 0 A 0 表示 L C D 0 L 0 1 C 0 B E 1:穿线 0:实际儿子 1 B 1 1 E 1 1 D 1 ∧ ∧

  33. data left right 0 A 1 A 2 5 C L 空 空 L 3 4 2 3 B 0 -2 B E D 4 E -2 -1 5 C 0 6 W 6 D 7 0 W -5 8 7 X X -7 -6 8 中序穿线树的顺序存储 • 中序穿线树的结点的顺序存储结构:

  34. 中序穿线树的中序遍历 • 在中序穿线树上进行中序遍历、不用堆栈的的算法及程序要点: • 1、最先输出最左的左后代结点 • 2、结点无右儿子,则该结点的中序的后继结点,由右儿子的指针场给出。但要注意以下情况: • 3、结点有右儿子,则该结点的中序的后继结点,是它的右子树中的最左的左后代结点。 • 4、最先输出的结点无前驱结点,最后输出的结点无后继结点。 无前驱结点 无后继结点 L A E B L C A D B E 指向当前结点 D 指向后继结点

  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; } root A L C D B E 无后继结点

  36. 中序穿线树 • · 中序穿线树在中序穿线树上插入新的结点的操作 • 本书中建立的实际上是中序穿线二叉排序树,所以新插入结点时必须寻找他的插入位置,即其父结点。考虑新插入的结点为父结点的右儿子的情况。新插入的结点为父结点的左儿子的情况,类似。 E E Q A U C 122 S D B D 250 99 新插入结点 D 作为叶子结点,其父结点的右儿子指针场应为非正数。 新插入结点 D 作为叶子结点,其父结点的左儿子指针场应为非正数。 300 200 110 E E Q A 105 230 U C 216 S B D D

  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; }

  38. 最优二叉树 • 路径长度:结点之间的树枝的总数 • 树的路径长度:从根到每一结点的路径长度之和 • 树的带权路径长度:叶子结点的带权路径长度之和。设有 n 片叶子,它们的权值分别 • 为 w1、w2、…….wn, 相应的路径长度分别为 L1、L2、…….Ln。 • 则树的带权路径长度可记为: n • WPL =∑ wklk • k=1 G L 2 7 E L E G H H 5 7 5 2 4 4 WPL=46 WPL=35 G H WPL=36 L E 2 4 7 5 • 最优二叉树或赫夫曼树:树的带权路径长度 WPL 最小的二叉树。

  39. 4、A= b3,18 3、A= A, 7 b2,11 2、A= b1,6 A, 7 T, 5 T, 5 b1,6 C, 2 S, 4 C, 2 S, 4 赫夫曼算法 • 赫夫曼算法(产生最优二叉树的算法)的实现: • 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 } 已知。 • 利用赫夫曼算法产生最优二叉树。 1、A= C, 2 A, 7 S, 4 T, 5

  40. b3,18 0 1 b2,11 A, 7 0 1 T, 5 b1,6 1 0 C, 2 S, 4 赫夫曼编码 • 赫夫曼算法用于通信中的字符的编码。权值为使用概率。赫夫曼树的左分支 标记为 • 0,而右分枝标记为 1;这从根到每一个叶子结点的路经上标记的字符组成的字符串,即为该字符的赫夫曼编码。 • e.g: 权值(此处为使用概率)分别为 { 2, 7, 4, 5 } 的结点集合 F= { C, A, S, T } 已知。 • 请利用赫夫曼算法产生最优二叉树。 赫夫曼编码: 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

  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} 最小化堆 或 1 1 12 96 2 3 2 3 36 24 83 27 4 5 6 7 4 5 85 47 30 53 11 9 8 91

  42. 7 最小化堆的最小元素 root 62 16 24 50 88 77

  43. 7 1 50 5 77 7 最小化堆的顺序存储 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] root 7 16 62 24 50 88 77 62 3 16 2 24 4 88 6

  44. 7 1 50 5 77 7 建堆的复杂性 • 建堆的时间耗费:比较次数 -4n 次 O(n) 级 (课本第 241页) [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 7 16 62 24 50 88 77 root 62 3 16 2 24 4 88 6

  45. 50 1 7 5 77 7 建最小化堆 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 数组 h 50 16 62 24 7 88 77 root 符合堆的定义,不需调整。即最小化小堆已符合定义。建立的第一个小堆的堆顶的下标为 n/2 62 3 16 2 24 4 88 6 叶子结点,符合堆的的定义。

  46. 50 1 7 5 77 7 建最小化堆 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 数组 h 50 16 62 24 7 88 77 root 不合堆的定义,需调整。建立以 L[ 2 ] 为堆顶的正确的最小化小堆。 62 3 16 2 24 4 88 6

  47. 50 1 16 5 77 7 建最小化堆 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 数组 h 50 7 62 24 16 88 77 root 不合堆的定义,需调整。建立以 L[ 2 ] 为堆顶的正确的最小化小堆。 62 3 7 2 24 4 88 6

  48. 50 1 16 5 77 7 建最小化堆 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最小化堆。 数组 h 50 7 62 24 16 88 77 root 62 3 7 2 24 4 88 6

  49. 7 1 16 5 77 7 建最小化堆 [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] 不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最小化堆。 数组 h 7 50 62 24 16 88 77 root 62 3 50 2 24 4 88 6

  50. 1 10 1 2 3 60 12 2 6 4 7 5 40 30 8 70 3=h 建堆的复杂性 • 时间耗费的代价:建堆的时间耗费 + 排序的时间耗费 • 建堆的时间耗费:设树根处于第 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) 级 h-1 1 1 i=h-1 j=1 i=h-1 h-1 j=1 ∞ j=1

More Related