第六章 树和森林 1、树、森林的概念 2、二叉树及其表示 3、遍历二叉树和线索二叉树 4、堆 5、树和森林 6、二叉树的计数 7、霍夫曼树

Slides:



Advertisements
Similar presentations
二叉树在树结构的应用中起着非常重要的作用,因为对二叉树的许多操作算法简单,而任何树都可以与二叉树 相互转换,这样就解决了树的
Advertisements

主讲:计算机工程学院 李兰 答疑地点:主教学楼B区213
数据结构学习考 复习课(2) 主要内容: 第三部分:树、二叉树、森林.
引入树 线性表、堆栈和队列都是线性结构,它们的共同特点:一对一; 计算机对弈中的一个格局可能有多个后继格局,用线性结构难以描述。
第6章 树和二叉树 树型结构是一类非常重要的非线性结构。直观地,树型结构是以分支关系定义的层次结构。
实用数据结构基础 第6章 树.
第6章 树 数据结构(C++描述).
第5章 树( Tree )和二叉树 5.1 树的基本概念 5.2 二叉树 5.3 遍历二叉树和线索二叉树 5.4 树和森林
数据结构与算法 Data Structure Algorithms
第五章 树 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
第六章 二叉树和树 6.1 二叉树 6.2 二叉树的基本操作与存储实现 6.3 二叉树的遍历 6.4 线索二叉树 6.5 树和森林
树.
树(三) 2012初赛知识点梳理.
树和二叉树(四).
Chap4 Tree.
Tree(樹) 什麼是「樹」? 「樹」的範例 「樹」的定義 「樹」的表示法.
第五章 树及二叉树 1.树的定义和术语 2.二叉树:定义、性质、存储 3.二叉树的遍历 4. 二叉树遍历的迭代器类 *5. 中序穿线树 6. 最优二叉树及其应用 7. 树和森林.
Chapter 5 Tree & Binary Tree
其他类型的链表主要内容 静态链表 循环链表 双向链表.
第6章 树和二叉树 6.1 树的有关概念 6.2 二叉树 6.3 二叉树的遍历 6.4 遍历的应用 6.5 线索二叉树 6.6 树和森林
第六章 树和二叉树.
赵海燕 软件研究所 14 Apr 第5章 树与二叉树 之三 赵海燕 软件研究所 14 Apr
Chapter8 Binary and Other Trees
第五章 树与二叉树 树和森林的概念 二叉树 二叉树遍历 线索化二叉树 树与森林 堆 Huffman树.
树和二叉树(三).
强连通分量 无向图 1、任意两顶点连通称该图为连通图 2、否则将其中的极大连通子图称为连通分量 A D C B E 有向图
第七章 搜索结构 静态搜索结构 二叉搜索树 AVL树.
·线性表的定义及ADT ·线性表的顺序存储结构 ·线性表的链接存储结构 · 单向循环链表 · 双链表、双向循环链表 · 一元多项式的加法
二叉树 二叉树 遍历 递归.
哈夫曼编码.
第六章 树与二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林
湖北大学知行学院 教师:涂晓帆 Sunday, December 09, 2018
第六章 树和二叉树.
第8章 树和二叉树 树 二叉树 二叉树设计 二叉树遍历 线索二叉树 哈夫曼树 等价问题 树与二叉树的转换 树的遍历 主要知识点.
数据结构与算法
五、链 表 1、单链表 2、双向链表 3、链表应用.
第5章 树和二叉树 北京师范大学 教育技术学院 杨开城.
数据结构 Data Structure 主讲人:王国军,郑瑾 CSU 中南大学信息院计科系
第5章 树和二叉树 5.1树 5.2二叉树 5.3二叉树的遍历 5.4线索二叉树 5.5树、森林与二叉树的转换 5.6哈夫曼树.
数据结构 Data Structure 主讲人:王国军,郑瑾 CSU 中南大学信息院计科系
第11讲 树和二叉树(二).
第六章 树 2019/1/14.
第六章 树与森林 树和森林的概念 二叉树 (Binary Tree) 二叉树的表示
第五章 树 5.1 树的定义 树是一类重要的非线性数据结构,是以分支关系定义的层次结构 定义
数据结构概论 第6章 树和二叉树 董黎刚 浙江工商大学信电学院.
第7章 树和二叉树 7.1 树 7.2 二叉树 7.3 以结点类为基础的二叉树设计 7.4 二叉树类 7.5 二叉树的分步遍历
第5到第6章的勘误表 注意:这个 c 应改为 d 1、第 92 页 倒数第 7 行:
Tree & Binary Tree.
樹 2 Michael Tsai 2013/3/26.
第三章 链表 单链表 (Singly Linked List) 循环链表 (Circular List) 多项式及其相加
Tree & Binary Tree.
无向树和根树.
二叉树的遍历.
第三章 链表 单链表 循环链表 多项式及其相加 双向链表 稀疏矩阵.
顺序表的删除.
第四章 栈和队列 栈 ( Stack ) 队列 ( Queue ) 优先队列 (Priority Queue) 小结.
資料結構使用Java 第9章 樹(Tree).
第六章 树和二叉树.
3.16 枚举算法及其程序实现 ——数组的作用.
第六章 二叉树和树 6.1二叉树 6.2二叉树遍历 6.3树和森林 6.4树的应用.
第5节 树 前两章学习的栈和队列属于线性结构。在这种结构中,数据元素的逻辑位置之间呈线性关系,每一个数据元素通常只有一个前件(除第一个元素外)和一个后件(除最后一个元素外)。在实际生活中,可以用线性结构描述数据元素之间逻辑关系的问题是很广泛的,但也有很多问题不能依靠线性结构来解决,例如家谱、行政组织机构等都是非线性的数据结构。其中树就是一种非线性的数据结构。
树和图 tree and graph 蔡亚星.
树和二叉树(四).
十二、堆 堆的定义.
#define RBTREE_RED 0 #define RBTREE_BLACK 1 typedef int dataType; typedef struct treeNode{ dataType key; int color; //red:0.
基于列存储的RDF数据管理 朱敏
第七章 搜索结构 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
本节内容 1. 平衡二叉树的定义 2.平衡化旋转 3.平衡二叉排序树的插入操作 昆山爱达人信息技术有限公司
最小生成树 最优二叉树.
第五章 树和二叉树.
Presentation transcript:

第六章 树和森林 1、树、森林的概念 2、二叉树及其表示 3、遍历二叉树和线索二叉树 4、堆 5、树和森林 6、二叉树的计数 7、霍夫曼树 目录 第六章 树和森林 1、树、森林的概念 2、二叉树及其表示 3、遍历二叉树和线索二叉树 4、堆 5、树和森林 6、二叉树的计数 7、霍夫曼树

1、树、森林的概念 基本概念 树:n > o 个结点的集合,根、其余结点分为 m >= 0 个集合,每一个集合本身又是一 棵树(子树) 度、叶子、父结点、儿子结点、祖先结点、层次、高度(深度和高度不同,书上定义错) 有序树及无序树 森林 m >= 0 棵互不相交树的集合 A B C D E F G H I L E、G:高度为 3 、度为 3 的树 其它表示方法: ( A(B(L,E),C(F),D(G(I),H)) 书上还介绍了两种,请参考。

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

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

2、二叉树 完全二叉树: 1、满二叉树 2、从满二叉树最底层从右向左依次去掉若干结点形成的 B C D E F L A P S Q R X U W V 满二叉树: 每层结点 数最多 B C D E F L A P S Q R U 性质4:具有 n 个结点的完全二叉树的高度为 log2(n +1) - 1 证:根据性质 2 和完全二叉树的定义: 2k-1 < n <= 2k+1 -1 2k < n + 1 <= 2k+1 k < log2(n +1) <= k+1 故:k + 1 = log2(n +1) ; 原命题得证。

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

2、二叉树 ·二叉树的 ADT: Template <class Type> class BinaryTree { public: BinaryTree( ); // 构造空二叉树 BinaryTree ( BinTreeNode < Type> * lch, BinTreeNode < Type> * rch, Type item ); int IsEmpty( ); // 二叉树为空返回1,否则为 0 BinTreeNode < Type> * Parent( BinTreeNode < Type> * ); BinTreeNode < Type> * leftChild( BinTreeNode < Type> * ); BinTreeNode < Type> * RightChild( BinTreeNode < Type> * ); int Insert ( const Type & item ); int Find ( const Type & item ); Type GetData ( ) const; }

2、二叉树 3、二叉树的存储结构: 顺序存储结构: 一般情况: rchild lchild data A B C D E F G H I L 1 2 3 4 5 6 7 8 9 rchild lchild data A B C D E F G H I L 应用范围:适用于二叉树上的结点个数已知,或不支持动态存储分配的高级语言。

2、二叉树 3、二叉树的存储结构: 顺序存储结构: 特殊情况: data lchild rchild A B C D E F G H I L 1 2 3 4 5 6 7 8 9 A 1 2 B 3 4 C 5 6 D 7 8 E 9 -1 F -1 -1 G -1 -1 H -1 -1 I -1 -1 L -1 -1 A B C D E F G H I L 应用范围:适用于完全二叉树,且结点个数已知。

2、二叉树 3、二叉树的存储结构: 顺序存储结构: 特殊情况:若不是完全二叉树,许多数据场为空,不合算。如下例所示: A B D H I A 1 2 3 4 5 6 7 8 A B ∧ D H I A B D H I

2、二叉树 data leftchildrightchild data leftchildrightchild Parent 3、二叉树的存储结构: 链接存储结构: 仅定义结点的类型即可。 A B C D E F G H I L 标准形式:(二叉链表) data leftchildrightchild 广义标准形式:(三叉链表) data leftchildrightchild Parent

2、二叉树 ·二叉树的 ADT: Template <class Type> class BinaryTree; Template <class Type> class BinTreeNode { friend class BinaryTree < Type>; 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 lfetChild; } 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; } private: BinTreeNode < Type> * leftChild, * rightChild; } Type Data; }

2、二叉树 ·二叉树的 ADT: Template <class Type> class BinaryTree { public: BinaryTree( ) : root( NULL) { } // 构造空二叉树 BinaryTree ( Type value) : RefValue( value ). root( NULL) { } virtual ~ BinaryTree ( ) { destroy ( root ); } virtual int IsEmpty ( ) { return root == NULL? 1: 0; } virtual BinTreeNode < Type> * LeftChild( BinTreeNode < Type> * current ) { return root ! = NULL ? Current->leftChild : NULL; } virtual BinTreeNode < Type> * RightChild( BinTreeNode < Type> * current ) { return root ! = NULL ? Current->rightChild : NULL; } virtual BinTreeNode < Type> * Parent( BinTreeNode < Type> * current ) { return root = = NULL || root = = current ? NULL : Parrent ( root, current ); } vitual int Insert ( const Type & item ); vitual int Find ( const Type & item ); const BinTreeNode < Type > * GetRoot ( ) const { return root; } // 接下页

2、二叉树 ·二叉树的 ADT: // 接上页 friend istream & operator >> ( istream & in, BinaryTree < Type > &Tree); friend ostream & operator << ( ostream & out, BinaryTree < Type > &Tree); private: BinTreeNode < Type> * root; Type RefValue; BinTreeNode < Type> * Parent( BinTreeNode < Type> * start, BinTreeNode < Type> * current ); int Insert ( BinTreeNode < Type> * & current, const Type & item ); void traverse ( BinTreeNode < Type> * current, ostream & out ) const; void find ( BinTreeNode < Type> * current, const Type & item ) const; void destroy ( BinTreeNode < Type> * current ); }

2、二叉树 ·二叉树的 ADT:部分函数的实现。 Template <class Type> void BinaryTree < Type > :: Traverse ( BinaryTree < Type > * current, ostream & out ) const { // 对以 current 为根的子树进行遍历,并输出数据场之值。 If ( current != NULL ) { out << current->data << ‘ ‘ ; Traverse ( current->leftChild, out ); Traverse ( current->rightChild, out );\ }

2、二叉树 ·二叉树的 ADT:部分函数的实现。 Template <class Type> istream & operator >> ( istream & in, BinaryTree < Type > & Tree ) { // 对 >> 进行重载,输入数据并建立该二叉树。 Type item; cout << “Construct Binary Tree:\ n” cout << “Input data ( end with ” << Tree.RefValue << “ ):”; in >> item; while ( item != Tree.RefValue ) { Tree.Insert ( item ); } // while }

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

3、遍历二叉树和线索二叉树 A A 前序:A、L、B、E、C、D、W、X L C 中序:B、L、E、A、C、W、X、D L C 1、遍历二叉树:续 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

3、遍历二叉树和线索二叉树 1、根结点进栈 2、结点出栈,被访问 3、结点的右、左儿子(非空)进栈 4、反复执行 2、3 ,至栈空为止。 1、遍历二叉树:续 前序的程序实现: 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。找不到结束。

3、遍历二叉树和线索二叉树 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; // 返回当前指针 current 所指向的结点的值。 protected: const BinaryTree <Type > & T; const BinaryTree <Type > & current; private: TreeIterator ( const TreeIterator & ) { } TreeIterator & operator = ( const TreeIterator & ) const; }

3、遍历二叉树和线索二叉树 1、遍历二叉树:二叉树的迭代器,即本书所讲的游标类( Tree Iterator ) :前序的实现 Template <class Type> class Preorder : public TreeIterator < Type > { public: Preorder( const BinaryTree < Type > & BT ); ~Preorder( ) { } void First( ); void operator ++ ( ); protected: Stack < const BinTreeNode <Type > * > st; }; Template <class Type > Preorder <Type>:: Preorder( const BinaryTree <Type> & BT ): TreeIterator< Type > ( BT ) { st.Push( T.Getroot( ) ); } Template <class Type > void Preorder <Type> :: First ( ) { // 得到第一个访问的结点地址 st.MakeEmpty ( ); if ( T.GetRoot( ) ) st.Push( T.Getroot( ) ); operator ++ ( ); } Template <class Type > void Preorder <Type>:: operator ++ ( ) { if ( st.IsEmpty() ) { if ( current == NULL ) { cerr << “Advanced past end ” << endl; exit( 1 ); } current = NULL; rturn; } current = st.Pop( ); if ( current ->GetRight ( ) != NULL ) st.Push( current->GetRight( ) ); if ( current ->GetLeft ( ) != NULL ) st.Push( current->GetLeft( ) ); }

3、遍历二叉树和线索二叉树 A e.g: L C D E 分析:1、二叉树最先被访问的结点是二叉树中最左方的叶子。 1、遍历二叉树:设根结点初始时非空 后序算法: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。

3、遍历二叉树和线索二叉树 Template <class Type> struct stkNodere { 1、遍历二叉树:二叉树的迭代器,即本书所讲的游标类( Tree Iterator ) :后序的实现 Template <class Type> struct stkNodere { const BinaryTree < Type > * Node; int PopTim; stkNodere ( const 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;

3、遍历二叉树和线索二叉树 1、遍历二叉树:二叉树的迭代器,即本书所讲的游标类( Tree Iterator ) :后序的实现 Template <class Type > Preorder <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( ) ) st.Push ( stkNode<Type>( T.Getroot( ) ); operator ++ ( ); } //根结点地址及标志 0 进栈,去去寻找第一个被访问的最左方的叶子。 Template <class Type > void Postorder <Type>:: operator ++ ( ) { if ( st.IsEmpty() ) { // 当栈空且 current 也为空时,遍历结束。 if ( current == NULL ) { cerr << “Advanced past end ” << 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 { if ( Cnode.node -> GetRight ( ) != NULL ) st.Push(stkNode<Type>( Cnode.Node -> GetRight ( ) ) ); } // 访左结束,有右儿子进栈 }

3、遍历二叉树和线索二叉树 1、结点(初始时是根结点)进栈,沿左指针查找左儿子。 2、若有左儿子,返回第一步。 1、遍历二叉树:续 中序的程序实现: 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

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

3、遍历二叉树和线索二叉树 e.g: n = 6 的二叉树 无前驱结点 无后继结点 A A 中序穿线树 L C L C D D B E B 2、线索二叉树: 中序穿线树(中序线索二叉树)的实现: 在二叉树的结点的标准形式中,增加两个标志域,用于区别是否是穿线。如下所 示: 其中 data 为数据场,leftThread = 0 时,leftchild 场指向本结点的真正的左儿子 leftThread = 1 时,leftchild 场指向本结点的按中序遍历序列的前驱结点(穿线) rightThread = 0 时,rightchild 场指向本结点的真正的右儿子 rightThread = 1 时,rightchild 场指向本结点的按中序遍历序列的后继结点(穿线) leftchild leftThread data rightThread rightrchild e.g: n = 6 的二叉树 无前驱结点 无后继结点 A A 中序穿线树 L C L C D D B E B E

3、遍历二叉树和线索二叉树 1:穿线 0:实际儿子 A L C 表示 D B E 2、线索二叉树: 中序穿线树中的结点的数据结构: 1 1 root: 新增头结点 1 root: 新增头结点 1 无前驱结点 无后继结点 A A L C 表示 D B E L 1 C 1:穿线 0:实际儿子 1 B 1 1 E 1 1 D 1

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

3、遍历二叉树和线索二叉树 Template < class Type > 2、线索二叉树:在中序穿线树上进行中序遍历、不用堆栈的程序实现。声明部分很简单,请自己看一下。 Template < class Type > ThreadNode < Type > * ThreadNodeIterator < Type > :: First ( ) { // 得到第一个访问的结点 while ( current->leftThread == 0 ) current = current ->leftChild; return current; } ThreadNode < Type > * ThreadNodeIterator < Type > :: Next ( ) { // 得到下一个访问的结点 ThreadNode < Type > * p = current->rightChild; if ( current ->rightThread == 0 ) while ( p->leftThread == 0 ) p = current ->leftChild; current = p; if ( current == T.root ) return NULL; else return current; void ThreadNodeIterator < Type > :: InorderFirst ( ) { // ThreadNodeIterator < Type > * p; for ( p = first; p != NUPP; p = Next( ) ) cout << p->data << endl; T 1 A L C D B E 无后继结点

3、遍历二叉树和线索二叉树 G D A A C X C E B B X D F G D 2、父结点有 右儿子 A A C C X B B X 2、线索二叉树:在中序穿线树上插入新的结点的操作。删除部分请自看一下。 仅举新插入的结点为父结点的右儿子的情况。新插入的结点为父结点的左儿子的情况,类似。 G D A A C X C E B B X D F G D 2、父结点有 右儿子 A A C C X B B X E 1、父结点无右儿子 D F

4、堆 1、 堆 堆的定义:n 个元素的序列 { k0, k1,…… , kn-1 },当且仅当满足以下关系时,称 之为堆: ki <= k2i+1 ki >= k2i ki <= k2i+2 ki >= k2i+1 ( i = 0,1,2, …… , (n-2)/2 ) 且分别称之为最小化堆和最大化堆。 e.g: 序列 { 96,83,27,11,9} 最大化堆 { 12,36,24,85,47,30,53,91} 最小化堆 或 96 12 2 1 1 2 83 27 36 24 3 4 5 6 3 4 11 9 85 47 30 53 7 91

The largest element in a maximum-heap is always found in the root node 70 60 12 40 30 8 10

The heap can be stored in an array [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 70 60 12 40 30 8 10 70 60 1 12 2 30 4 10 6 40 3 8 5

4、堆 [ 0 ] [ 1 ] 70 [ 2 ] 60 [ 3 ] 12 [ 4 ] 40 [ 5 ] 30 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 70 60 12 40 30 8 10 70 60 1 12 2 30 4 10 6 40 3 8 5

4、堆 [ 0 ] [ 1 ] 30 [ 2 ] 60 [ 3 ] 8 [ 4 ] 40 [ 5 ] 70 [ 6 ] 12 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 30 60 8 40 70 12 10 不合堆的定义,需调整。即建立小堆。建立的第一个小堆的堆顶的下标为 (n-2)/2 30 60 1 8 2 70 4 10 6 40 3 12 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 30 [ 2 ] 60 [ 3 ] 12 [ 4 ] 40 [ 5 ] 70 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 30 60 12 40 70 8 10 不合堆的定义,需调整。即建立小堆。建立的第一个小堆的堆顶的下标为 (n-2)/2 30 60 1 12 2 70 4 10 6 40 3 8 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 30 [ 2 ] 60 [ 3 ] 12 [ 4 ] 40 [ 5 ] 70 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 30 60 12 40 70 8 10 不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最大化小堆。 30 60 1 12 2 70 4 10 6 40 3 8 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 30 [ 2 ] 70 [ 3 ] 12 [ 4 ] 40 [ 5 ] 60 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 30 70 12 40 60 8 10 不合堆的定义,需调整。建立以 L[ 1 ] 为堆顶的正确的最大化小堆。 30 70 1 12 2 60 4 10 6 40 3 8 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 30 [ 2 ] 70 [ 3 ] 12 [ 4 ] 40 [ 5 ] 60 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 不合堆的定义,需调整。建立以 L[ 0 ] 为堆顶的正确的最大化小堆。 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 30 70 12 40 60 8 10 30 70 1 12 2 60 4 10 6 40 3 8 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 70 [ 2 ] 30 [ 3 ] 12 [ 4 ] 40 [ 5 ] 60 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 不合堆的定义,需调整。建立以 L[ 0 ] 为堆顶的正确的最大化小堆。 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 70 30 12 40 60 8 10 70 30 1 12 2 60 4 10 6 40 3 8 5 叶子结点,符合堆的的定义。

4、堆 [ 0 ] [ 1 ] 70 [ 2 ] 60 [ 3 ] 12 [ 4 ] 40 [ 5 ] 30 [ 6 ] 8 10 root 2、 建堆 时间复杂性的分析: 建堆的时间耗费:比较次数 - 4n 次 O(n) 级 不合堆的定义,需调整。建立以 L[ 0 ] 为堆顶的正确的最大化小堆。 [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] root 70 60 12 40 30 8 10 70 60 1 12 2 10 6 40 3 30 4 8 5 叶子结点,符合堆的的定义。

4、堆 时间复杂性的分析: 建堆的时间耗费:设树根处于第 0 层,最下面是 h 层。建堆从第 h-1 层开始进行。只 要知道了每一层的结点数(建的小堆的个数),和每建一个小堆所 需的比较次数,就可以求得建堆的时间耗费。 建的小堆的性质:第 i 层上小堆的个数 = 第 i 层上结点的个数 = 最多 2i 第 i 层上小堆的最大层数 = h-i+1 建第 i 层上每个小堆最多所需的比较次数 = 2×(h-i)次 建堆的时间耗费: S(n) <= ∑ 2i×(h-i)×2 = ∑ 2i+1×(h-i) = ∑ 2h-j+1×j = 2h+1 ∑ j/2j 注意:高度为 h 的完全的二叉树,满足以下式子: 2h <= n 故: 2h+1 <= 2n 又: ∑ j/2j < 2 所以:建堆的时间复杂性为 4n=O(n) 级 h i=h-1 i=h-1 j=1 h j=1 ∞ j=1

4、堆 Template < class Type > 3、堆:建立一个最小化堆(建立最大化堆同样道理),堆即最小化堆的声明,请自看。 Template < class Type > MinHeap < Type > :: MinHeap ( int maxSize ) { // MaxHeapSize = DefaultSize < maxSize ? maxSize : DefaultSize; heap = New Type [ MaxHeapSize]; CurrentSize = 0; } MinHeap < Type > :: MinHeap ( Type arr [ ], int n ) { // heap = New Type [ MaxHeapSize]; heap = arr; CurrentSize = n; int currentPos = ( CurrentSize - 2 ) / 2; while (currentPos >= 0 ) { FiltDown ( currentPos, Currentsize -1); currentPos --; } // while end }; 无后继结点

4、堆 Template < class Type > 3、堆:建立一个最小化堆(建立最大化堆同样道理),堆即最小化堆的声明,请自看。 Template < class Type > void MinHeap < Type > :: FiltDown( const int start, const int EndOfHeap ) { // int i = start; j = 2 * i + 1; Type temp = heap[ i ]; while ( j <= EndofHeap ) { if ( j < EndofHeap && heap [ j ]. key > heap [ j +1 ]. key ) j ++; if ( temp. key < heap [ j +1 ]. key ) break; else { heap [ i ] = heap [ j ]; i = j; j = 2 * i + 1; } // while end heap [ i ] = temp; }; 无后继结点

4、堆 1、插入一个新的结点:插在最后,然后向父结点 方向调整。如插入优先级为 15 的结点在序号 16 处。 12 3、堆:插入一个新的结点,删除一个新的结点,改变结点的优先级。 1、插入一个新的结点:插在最后,然后向父结点 方向调整。如插入优先级为 15 的结点在序号 16 处。 12 1 2 2、结点的优先级改变。如:结点的优先级 从 85 改变为 8 或 93。 变小:向根结点方向调整。 变大:向叶子方向调整。 36 24 3 4 5 6 85 47 30 53 10 7 8 9 11 12 13 14 86 89 48 130 53 90 124 120 3、删除任意一个结点,如:删结点 47。 是 2、中的变小的特殊情况。当成优先 数无穷小的情况,上升到根结点,然后 和最大序号的结点交换,实现删除。在 从根结点开始进行调整。 15 136 4、时间复杂性分析:插入、删除代价都是 O(log2n)级的。建堆是O(n)级的。 是表示优先队列的理想的数据结构。

5、树和森林 ………… 1、树的存储结构 标准形式:树中的每个结点除有数据场之外,还有 K 个指针场;其中 K 为树的度数。1、 第一个儿子结点的地址 第二个儿子结点的地址 第 K 个儿子结点的地址 父亲结点的地址 数据场 ………… 广义标准形式:在标准形式的基础上,再增加指向父亲结点的指针场。 E.g: 度数 K = 3 的树 用数组表 示左图的 树 -1 表示空 缺点:空指针场太多,多达 ( K -1 )× n + 1 个。 改进:结点中设立度数场, 指针场依度数定。但 操作麻烦。 采用左儿子、兄弟表 示法。 A 1 2 3 4 5 6 7 8 9 A 1 2 3 -1 B 9 4 -1 0 C 5 -1 -1 0 D 6 7 -1 0 E -1 -1 -1 1 F -1 -1 -1 2 G 8 -1 -1 3 H -1 -1 -1 3 I -1 -1 -1 6 P -1 -1 -1 -1 B C D L E F G H I

5、树和森林 1、树的存储结构 左儿子、兄弟表示法:树中的每个结点有数据场、指向它的第一棵子树树根的指针场、 指向它的兄弟结点的指针场。实质上是用二叉树表示一棵树。 第一棵子树根结点的地址 下一个亲兄弟结点的地址 数据场 A A ∧ B C D B C D ∧ L E F G H ∧ L E ∧ ∧ F ∧ G ∧ H ∧ I E.g: 度数 K = 3 的树 ∧ I ∧

5、树和森林 2、树、森林于二叉树的转换 树转换成相对应的二叉树: 1、保留每个结点的最左面的分支,其余分支都被删除。 2、同一父结点下面的结点成为它的左方结点的兄弟。 E.g: 度数 K = 3 的树 A B C D E F G H I L A A B C D B C D L E F G H L E F G H I I

5、树和森林 2、树、森林于二叉树的转换 森林转换成相对应的二叉树: 增加一个虚拟的根结点,它的儿子为各棵树的根。 那么,就变成了树转换成二叉树的问题。 A E.g: 3 棵分别以B、 C、D为根的树 B A 相应的二叉树 C L B C D B C D D F E L E F G H L E F G H G I I I H

5、树和森林 2、树、森林于二叉树的转换 森林转换成相对应的二叉树: 增加一个虚拟的根结点,它的儿子为各棵树的根。 那么,就变成了树转换成二叉树的问题。 E.g: 3 棵分别以B、 C、D为根的树 B A 相应的二叉树 C L B C D B C D D F E L E F G H L E F G H G I I I H

5、树和森林 R T1 T2 …… T3 3、树、森林的遍历 树的前序、后序遍历: 1、类似于二叉树的前序遍历:NLR;N:根;L:左子树(第一棵子树), R:其余的那些子树,遍历方向由第二棵子树至最后一棵子树 2、类似于二叉树的后序遍历:LRN:L:左子树(第一棵子树),R:其 余的那些子树,遍历方向由第二棵子树至最后一棵子树, N:根 N B C D E F G H I L A T1 T2 …… T3 R 前序:A、B、L、E、C、F、D、G、I、H 后序:L、E、B、F、C、I、G、H、D、A

5、树和森林 3、树、森林的遍历 森林的前序、中序遍历: 前序遍历类似于树的前序遍历。增加一个虚拟的根结点,它的儿子为各棵树的 根。那么对这棵树进行前序遍历,即得到森林的前序序列(不含树根结点) 中序遍历类似于树的后序遍历。增加一个虚拟的根结点,它的儿子为各棵树的 根。那么对这棵树进行后序遍历,即得到森林的中序遍历(去掉树根结点) A B C D B C D L E F G H L E F G H I I 前序:B、L、E、C、F、D、G、I、H 中序:L、E、B、F、C、I、G、H、D

5、树和森林 3、树、森林的遍历 A B A C L D F B C D E 以 C 为例: 在树中: 树的前序、后序遍历序列和相应的二叉树的前序、中序遍历序列一一对应: 前序序列和对应的二叉树的前序序列完全一致。 E、G:左图的树的根 A,及它的儿子结点 B、C、D 在树的 的前序序列和相应的二叉树中前序序列中的序号。 根 A: 1 1 结点 B: 2 2 结点 C: 5 5 结点 D: 7 7 A B A C L D F B C D E 以 C 为例: 在树中: 序号= 根节点数 + 第一棵子树的结点数 + 1 = 5 在二叉树中: 序号= 根节点数 + 左儿子数+左儿子子树结点数 + 1 = 5 G L E F G H I H I

5、树和森林 3、树、森林的遍历 A B A C L D F B C D E 以 C 为例: 在树中: 树的前序、后序遍历序列和相应的二叉树的前序、中序遍历序列一一对应: 后序序列和对应的二叉树的中序序列完全一致。 E、G:左图的树的根 A,及它的儿子结点 B、C、D 在树的 的后序序列和相应的二叉树的中序序列中的序号。 根 A: 10 10 结点 B: 3 3 结点 C: 5 5 结点 D: 9 9 A B A C L D F B C D E 以 C 为例: 在树中: 序号= 第一棵子树的结点数 + C 的子树的结点数 + 1 = 5 在二叉树中: 序号= B 的左子树的结点数 +B 的结点数 + C 的左子树结点数 + 1 = 5 G L E F G H I H I 后序:L、E、B、F、C、I、G、H、D、A 中序:L、E、B、F、C、I、G、H、D、A

5、树和森林 3、树、森林的遍历 E.g: 3 棵分别以B、 C、D为根的树 A B A 相应的二叉树 C L B C D B C D D 森林的前序、中序(当作后序更好理解)和相应的二叉树的前序、中序遍历序列一一对应 E.g: 3 棵分别以B、 C、D为根的树 A B A 相应的二叉树 C L B C D B C D D F E L E F G H L E F G H G I I I H 注意:本书介绍了森林的后序,一般用的很少。 另外:本书介绍了层次访问,也很简单。只需采用队列,即可实现。

6、树的计数 前序: A、B、D、E、F、C 中序: D、B、E、F、A、C 1、二叉树的确定 前序(后序) + 中序 唯一确定一棵二叉树 e.g: A 前序: A、B、D、E、F、C 中序: D、B、E、F、A、C C D、B、E、F 确定过程: 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 D、E、F B C 前序: B、D、E、F 中序: D、B、E、F A B C E、F D 前序: E、F 中序: E、F

6、树的计数 前序: A、B、D、E、F、C 中序: D、B、E、F、A、C 1、二叉树的确定 前序(后序) + 中序 唯一确定一棵二叉树 e.g: A 前序: A、B、D、E、F、C 中序: D、B、E、F、A、C C D、B、E、F 确定过程: 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 D、E、F B C 前序: B、D、E、F 中序: D、B、E、F A B C 前序: E、F 中序: E、F D E F

6、树的计数 1、互不相似的二叉树的棵数的计算 实例: e.g: 当 二叉树的结点个数 n = 3 前序序列为 1、2、3 时 计算要点: 设二叉树的前序的序列 为 1、2、3 ……… n 不同的中序序列的对应 着不同树形的二叉树 不同的中序序列的总数 是不同树形的二叉树的 总和 不同的中序序列在中序 周游时和相应的进出栈 序列一一对应。 不同的进出栈序列的总 数是不同树形的二叉树 的总和 1 1 1 1 1 2 2 2 3 2 2 3 3 3 3 1、进栈 2、进栈 3、进栈 3、出栈 2、出栈 1、出栈 1、进栈 2、进栈 2、出栈 3、进栈 3、出栈 1、出栈 1、进栈 2、进栈 2、出栈 1、出栈 3、进栈 3、出栈 1、进栈 1、出栈 2、进栈 3、进栈 2、出栈 3、出栈 1、进栈 1、出栈 2、进栈 2、出栈 3、进栈 3、出栈

6、树的计数 1、互不相似的二叉树的棵数的计算 进出栈序列总数的计算为 2n 个方格中放置 3 个 1 的组合数 - 不合理的序列总数 - 不合理的序列总数 e.g: n = 3 时,6格放置3个 1 的序列 情况:1 代表进栈,0 表示出栈 实例: e.g: 当 二叉树的结点个数 n = 3 前序序列为 1、2、3 时 1 1 1 1 1 1 1 1 0 0 0 2 2 2 3 2 2 1 1 0 1 0 0 合 理 3 3 3 3 1 1 0 0 1 0 1、进栈 2、进栈 3、进栈 3、出栈 2、出栈 1、出栈 1、进栈 2、进栈 2、出栈 3、进栈 3、出栈 1、出栈 1、进栈 2、进栈 2、出栈 1、出栈 3、进栈 3、出栈 1、进栈 1、出栈 2、进栈 3、进栈 2、出栈 3、出栈 1、进栈 1、出栈 2、进栈 2、出栈 3、进栈 3、出栈 1 0 1 1 0 0 1 0 1 0 1 0 0 0 0 1 1 1 不合理 1 0 0 1 1 0

6、树的计数 1、互不相似的二叉树的棵数的计算 进出栈序列总数的计算为 2n 个方格中放置 n 个 1 的组合数 - 不合理的序列总数 - 不合理的序列总数 e.g: n = 3 时,6格放置3个 1 的序列 情况:1 代表进栈,0 表示出栈 n = 3 时,6 格放置 3 个 1 的序列情况: 3 序列总数 = 2 × 3 3 + 1 不合理的序列总数: 因此,n = 3 时进出栈序列的总数为: 3 3 + 1 2 × 3 2 × 3 推广到结点数为 n 时的进出栈序列总数: n n + 1 2 × n 2n 1 1 1 0 0 0 1 1 0 1 0 0 合 理 1 1 0 0 1 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

6、树的计数 b0 = 1 bn = bi × b0-i-1 n-1 i= 0 1、互不相似的二叉树的棵数的计算 的总数为: 结点数为 n + 1 时的互不相似的有序树 总数为: n n + 1 2 × n 2 × n n n + 1 2 × n 2 × n 或 或 n n - 1 2 × n 2 × n n n + 1 2 × n 2 × n 2、书上有一种采用递推公式计算的办法,结果一样。 b0 = 1 bn = bi × b0-i-1 n-1 i= 0

6、树的计数 2、推论:结点数为 n + 1 时的互不相似的有序树总数和结点数为 n 时的互不相似的二叉树的总数相等。 E.g: n = 4 E.g: n = 4 的所有树 形 1 1 1 3 1 2 1 2 3 2 2 3 2 3 3 1 1 1 3 1 2 1 2 3 2 2 3 2 3 3 1 1 1 1 1 只需考虑 以下二叉 树的棵数即 可 2 2 2 2 2 3 3 3 3 3

7、赫夫曼树及其应用 1、最优二叉树(赫夫曼树) G L 2 7 E L E G H H 5 7 5 2 4 4 G H L E 2 4 7 路径长度:结点之间的树枝的总数 树的路径长度:从根到每一结点的路径长度之和 树的带权路径长度:叶子结点的带权路径长度之和。设有 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 最小的二叉树。

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

7、赫夫曼树及其应用 1 赫夫曼编码: A:0 T:10 C:110 S :111 2、赫夫曼编码 赫夫曼算法用于通信中的字符的编码。将权值当着使用概率。赫夫曼树的左分支 标记为 1,而右分枝标记为 0;这从根到每一个叶子结点(字符)的路经上标记的字符组成的字 符串,即为该字符的赫夫曼编码。 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 b1,6 A, 7 S, 4 C, 2 b2,11 T, 5 b3,18 1

7、赫夫曼树及其应用 3、 赫夫曼算法的实现:扩充二叉树的声明。 Template < class Type > class ExtBinTree; Template <class Type> class Elelement { friend class ExtBinTree < Type>; private: Type data; // 保存本结点的使用频度和其它数据。 Elelement < Type > * leftChild, * rightChild; }; Template < class Type > class ExtBinTree { public: ExtBinTree ( ) { root = new Elelement < Type>; } 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: const int DefaultSize = 20; Elelement < Type > * root; }

7、赫夫曼树及其应用 3、 赫夫曼算法的实现:赫夫曼树的构造。// 下述函数应作为类 ExtBinTree <Type>的公有成员,否则, Template < class Type > // 无法访问私有成员 DefaultSize。保存使用频度的数组为fr[0]~fr[n-1] void ExtBinTree <Type> :: HuffmanTree ( Type * fr, int n, ExtBinTree < Type > & newtree ) { ExtBinTree < Type > & first, &second; ExtBinTree < Type > Node[ DefaultSize ]; MinHeap < ExtBinTree < Type > > hp; if ( n > DefaultSize ) { cerr << “Size n” << n << “exceed the Boundary of Array ” << endl; return; } for ( int i = 0; i < n; i++ ) { Node[i].root->data.key = fr[i]; Node[i].root->leftChild = Node[i].root->rightChild = NULL; } hp.Minheap ( Node, n ) // 建立具有 n 个结点最小化堆。数组名为 Node。 for ( int i = 0; i < n-1; i++ ) { hp.DeleteMin ( first ); hp.DeleteMin ( second ); newtree = new ExtBinTree < Type > ( first, second ) } hp.Insert ( * newtree ); } // 根据 NO193 的说明,应加 * 号。 } 0 1 2 ……. n-2 n-1 b1,6 A, 7 S, 4 C, 2 b2,11 T, 5 b3,18