180 likes | 315 Views
数据结构. 第六章 树与森. §6.6 树与森林. firstchild. data. NextSibling. root. A. B. G. A. I. C. D. ∧. ∧. ∧. B. C. D. ∧. E. E. F. G. H. I. J. ∧. F. ∧. K. L. M. ∧. K. ∧. H. ∧. L. ∧. ∧. M. ∧. ∧. J. ∧. 一、树的第一个孩子和下一个兄弟存贮表示. 1 、结点结构. 2 、存贮示例.
E N D
数据结构 第六章 树与森
§6.6 树与森林 firstchild data NextSibling root A B G A I C D ∧ ∧ ∧ B C D ∧ E E F G H I J ∧ F ∧ K L M ∧ K ∧ H ∧ L ∧ ∧ M ∧ ∧ J ∧ 一、树的第一个孩子和下一个兄弟存贮表示 1、结点结构 2、存贮示例 ☆因每个结点有且仅有两个指针域,所以也称为二叉链或二叉树表示方法
3、类声明 template <class Type> class Tree; template <class Type> class TreeNode{ friend class <Type> Tree; private: Type data; TreeNode <Type> * firstchild, * nextSibling; 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; …… public: Tree ( ) {root = current = NULL;} void PreOrder ( ); //先根遍历树 void PostOrder ( ); //后根遍历树 void LevelOrder ( ); //广度优先遍历树 int IsEmpty ( ) {return root = = NULL; } ……}
root A B C E D F G H K I L J M 二、树的遍历(二叉链存贮下) 1、深度优先遍历 ▲先根遍历 ☆先根遍历的顺序 访问树的根结点 先根遍历第一棵子树,第二棵子树等 ☆对上例给定树的树先根遍历输出序列: A B E F C G K L D H I M J
1、深度优先遍历 ☆先序遍历算法 template <class Type> void Tree<Type> :: PreOrder ( ){ if (! IsEmpty ( )) { //对以current为根的树先序遍历 cout <<current -> data << " "; TreeNode <Type> * p = current; current = current -> firstchild; //找根的第一棵子树 while (current != NULL) { //先序遍历所有子树 PreOrder( ); current = current -> nextSibling;} current = p; } } ☆先序遍历结果与对应二叉树的先序遍历结果相同
root A B C E D F G H K I L J M 1、深度优先遍历 ▲后根遍历 ☆后根遍历的顺序 按后根遍历第一棵子树,第二棵子树等 访问树的根结点 ☆对上例给定树的树后根遍历输出序列: E F B K L G C H M I J D A
1、深度优先遍历 ☆后序遍历算法 template <class Type> void Tree<Type> :: PostOrder ( ){ if (! IsEmpty ( )) { //对以current为根的树后序遍历 TreeNode <Type> * p = current; current = current -> firstchild; //找第一棵子树 while (current != NULL) { PostOrder( ); current = current -> nextSibling;} current = p; cout <<p -> data << " "; } } ☆后序遍历结果与对应二叉树的中序遍历结果相同
第0层 A 第1层 A B 第2层 B C D E 第3层 C F D E F G H I J G K H K L M L I M J 2、广度优先遍历 ▲广度优先即层次优先,同层中先左后右 ▲示例 输出序列:A B C D E F G H I J K L M
2、广度优先遍历 ▲广度优先遍历算法 template <class Type> void Tree<Type> :: LevelOrder ( ){ Queue <TreeNode <Type> *> Qu; TreeNode <Type> *p; if (current != NULL) { p = current; Qu.EnQueue (current); while (! Qu.IsEmpty ( )) { current = Qu.GetFront ( ); Qu.DeQueue ( ); cout << current -> data <<" "; current = current -> firstchild; while (current != NULL) { Qu.EnQueue (current); current = current -> nextSibling; }} current = p; }}
T1 T2 T3 A H F T1 T2 T3 A H F B I G B C D G I J J C E K E K D (a)3棵树的森林 A (b) 树的二叉树表示 T1 B F T2 E C G H T3 D I J (c) 森林的二叉树表示 K 三、森林与二叉树的转换 1、森林是树的有限集合 2、若将每棵树分别用二叉树表示,然后通过下一个兄弟域顺序链接在一起,就得到了森林的二叉树表示 3、森林与二叉树互换的示例
0 0 1 1 2 2 3 3 4 5 6 4 7 5 6 7 (a) PL = 13 (b) PL = 15 §6.7 霍夫曼树 一、树的路径长度(PL) 1、PL是指从根到其它各结点的路径长度(分支数)之和
一、树的路径长度(PL) 2、具有n个结点的路径长度分析 完全二叉树各结点的路径长度分别是数列 0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4, …的前n项之和,具有最小值 若n个结点的高度为h的二叉树,从根到 h – 1 层都有最多结点数 2h – 1 ,其余结点分布在第 h 层的任意位置上,也具有最小 PL ,这种树称为理想平衡二叉树。
带权的叶子结点又名外结点,具有外结点的树叫扩充二叉树 具有n个外结点,其中任意结点的权值为Wi,到根的路径长度为 li,则该扩充二叉树的带权路径长度为: 具有最小 WPL 的扩充二叉树叫霍夫曼树 2 7 4 5 2 4 5 7 7 4 5 2 (a) WPL = 36 (b) WPL = 46 (c) WPL = 35 (霍夫曼树) 二、霍夫曼树 1、树的带权(Weighted)路径长度 WPL
18 11 11 7 6 5 6 , , , , , , 7 5 7 6 7 2 5 4 5 2 4 2 4 (a) 2 4 (b) (c) (d) 二、霍夫曼树 2、霍夫曼树的构造方法 将n个权值视为具有n棵扩充二叉树的森林F,然后重复以下步骤,直到F中只有一棵树为止: ①在F中选根的权值最小的两棵作为左右子树构造一棵新的二叉树,其根的权为左右子树根的权值之和。 ②在F中删除那两棵树,并把新的二叉树加入。
二、霍夫曼树 3、扩充二叉树类的声明 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 { private : Element <Type> * root; 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; }}
二、霍夫曼树 4、构造霍夫曼树的算法 template <class Type> void HuffmanTree (Type *fr, int n, ExtBinTree <Type> & newtree) { //以权值fr[0]~fr[n-1]构造霍树 ExtBinTree <Type> first, second; ExtBinTree <Type> Node[DefaultSize]; //n棵树组成的森林 MinHeap <ExtBinTree <Type>> hp; //算法中使用的最小堆,存放森林 if (n > DefaultSize) {cerr <<"n out of range" <<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); //构造最小堆 for (int i = 0; i < n – 1 ; i + +) { hp.RemoveMin (first); //选权值最小的树 hp.RemoveMin (second); //选权值次小的树 newtree = new ExtBinTree <Type> (first.second); //构造新的二叉树 hp.Insert (newtree); }} //插入到最小堆中
三、霍夫曼编码——霍夫曼树应用事例 1、最小冗余编码问题 ☆设用0,1码来对一串字符信息进行等长编码: T —— 00,A —— 01,D —— 10,S —— 11 ☆对于信息串“ ATTSTATADT ”所得到的编码为 01,00,00,11,00,01,00,01,10,00 共20位编码 ☆字母集合{T,A,D,S}出现的频度 W = {5,3,1,1}, 若采用不等长编码表示 T —— 0,A —— 10,D —— 110,S —— 111 所得到的 编码是 10,0,0,111,0,10,0,10,110,0 共17位,这是最小冗 余编码。
10 0 1 5 T 5 0 1 2 A 3 0 1 D S 1 1 三、霍夫曼编码——霍夫曼树应用事例 2、霍夫曼树编码 ☆以字符的频度为权构造霍夫曼树 ☆左分支表示0,右分支表示1 ☆从根到各外结点路径上经由的数字序列构成各字符的编码 3、霍夫曼树编码的优越性 ☆是最小冗余码 ☆非前缀码——码 Ci 不是码 Cj 的前缀 ☆译码简单唯一——不断从根开始沿霍夫曼编码树查找。 10001110100101100 译码得到的只能是报文串: ATTSTATADT