6.6树和森林的表示方法
树的三种存储结构
一、双亲表示法
22_001 |
#define MAX_TREE_SIZE 100 //结点结构 typedef struct PTNode { Elem data; int parent; //双亲位置域 }PTNode; |
Data | parent |
树结构
22_002 |
typedef struct { PTNode nodes[MAX_TREE_SIZE]; int r,n; //根结点的位置和结点个数 }PTree; |
二、孩子链表表示法:
孩子结点结构:
22_003 |
typedef struct CTNode { int child; struct CTNode *next; }*ChildPtr; |
双亲结点结构:
22_004 |
typedef struct { Elem data; ChildPtr firstchild; //孩子链的头指针 }CTBox; |
树结构:
22_005 |
typedef struct { CTBox nodes[MAX_TREE_SIZE]; int n,r; //结点数和根结点的位置 } |
三、树的二叉链表(孩子-兄弟)存储表示法
22_006 |
typedef struct CSNode { Elem data; struct CSNode *firstchild, *nextsibling; }CSNode, *CSTree; |
森林和二叉树的对应关系
设森林F=(T1.T2,…Tn) T1=(root, t11,t12,…t1m);二叉树B=(LBT,Node(root),RBT)
由森林转换成二叉树的转换规则为:
若F=Φ则B=Φ;否则,由ROOT(T1)对应得到Node(root);由(t11,t12,…t1m)对应得到LBT;由(T2,T3,…Tn)对应得到RBT;
由二叉树转换为森林的转换规则为:
若B=Φ,则F=Φ;否则,Node(root)对应得到ROOT(T1);由LBT对应得到(t11,t12,…,t1m)由RBT对应得到(T2,T3,…,Tn)
由此,树的各种操作均可对应二叉树的操作来完成。
应当注意的是,和树对应的二叉树,其左、右子树的概念已改变为:左是孩子,右是兄弟
6.7 树和森林的遍历
树的遍历可有三条搜索路径:
先根(次序)遍历:
若树不空,则先访问根结点,然后依次先根遍历各棵子树
后根(次序)遍历:
若树不空,则先依次后根遍历各棵子树,然后访问根结点。
按层次遍历:
若树不空,则自上而下自左至右访问树中每个结点。
森林由三部分构成:
1. 森林中第一棵树的根结点
2. 森林中第一棵树的子树森林
3. 森林中其它树构成的森林
森林的遍历
先序遍历(依次从左至右对森林中的每一棵树进行先根遍历)
若森林不空,则访问森林中第一棵树的根结点。
先序遍历森林中第一棵树的子树和森林;先序遍历森林中(除第一棵树之外)其余树构成的森林。
中序遍历(依次从左至右对森林中每一棵树进行后根遍历)
若森林不空,则中序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点,中序遍历森林中(除第一棵树之外)其余树构成的森林。
树的遍历和二叉树遍历对应关系?
树的先根遍历的对应二叉树的先序遍历
树遍历算法的应用
一、求树的深度的算法:
22_007 |
int TreeDepth(CSTree T) { if(! T) return 0; else { h1 = TreeDepth(T->firstchild); h2 = TreeDepth(T->nextsibling); } }//TreeDepth |
二、输出树中所有从根到叶子结点的路径的算法
23_001 |
void AllPath(Bitree T, Stack &S) { if(T) { Push(S, T->data; if(! T->Left && ! T->Right) PrintStack(S); else { AllPath(T->Left, S); AllPath(T->Right, S); } Pop(S); } }//AllPath |
23_002 |
void OutPath(Bitree T, Stack &S) {//输出森林中所有从根到叶的路径 while(! T) { Push(s, T->data); if(! T->lchild) Printstack(s); else OutPath(T->lchild, s); Pop(s); T = T->rchild; }//while }//OutPath |
三、建树的存储结构的算法
23_003 |
void CreateTree(CSTree &T) { T = NULL; for(scanf(&fa, &ch); ch != '#'; scanf(&fa, &ch);) { p = GetTreeNode(ch); EnQueue(Q, p); if(fa == '#') T = p; else { GetHead(Q, s); while(s->data != fa) { DeQueue(Q, s); GetHead(Q, s); } if(! (s->firstchild)) { s->firstchild = p; r = p; } else { r->nextsibling = p; r = p; } } } }//CreateTree |
6.8哈夫曼树与哈夫曼编码
最优树的定义
如何构造最优树
前缀编码
一、最优树的定义
结点的路径长度定义为:
从根结点到该节点的路径上分支的数目。
树的路径长度定义为:
树中每个结点的路径长度之和。
树的带权路径长度定义为:
树中所有叶子结点的带权路径长度之和WPL(T)= (对所有叶子结点)
例如:
在所有含n个叶子结点,并带有相同权值的m叉树中,必存在一棵其带权路径长度取得最小值的树,称为“最优树”。
二、如何构造最优树
哈夫曼最早研究出一个带有一般规律的算法:
以二叉树为例:
(1)根据给定的n噶权值{w1,w2,…wn},构造n棵二叉树的集合F={T1,T2,…Tn},其中每棵二叉树中均只含一个带权值为wi的根结点,其左、右子树为空树。
(2)在F中选取其根结点的权值为最小的两棵二叉树,分别作为左,右子树构造一棵新的二叉树,并置这棵新的二叉树根结点的权值为其左、右子树根结点的权值之和。
(3)在F中删去这两棵树,同时加入刚刚生成的新树。
(1) 重复(2)和(3)两步,直至F中只含一棵树为止。
三、前缀编码
指的是,任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。
哈夫曼编码是一种最优前缀编码。
1. 熟练掌握二叉树的结构特性,了解相应的证明方法
2. 熟悉二叉树的各种存储结构的特点及适用范围
3. 遍历二叉树是二叉树各种操作的基础。实现二叉树遍历的具体算法与所采用的存储结构有关。掌握各种遍历策略的递归和非递归算法,灵活运用遍历算法实现二叉树的其它操作。层次遍历是按另一种搜索策略进行的遍历。
4. 理解二叉树线索化的实质是建立结点与其在相应序列中的前驱或后继之间的直接联系,熟练掌握二叉树的线索化过程以及在中序线索化树上找给定结点的前驱和后继的方法。二叉树线索化过程是基于对二叉树进行遍历,而线索二叉树上的线索又为希艾娜供应的遍历提供方便。
5. 熟悉树的各种存储结构及其特点,掌握树和森林与儿茶素的转换方法。建立存储结构是进行其它操作的前提,因此读者应掌握1至2种建立二叉树和树的存储结构的方法。
6. 学会编写实现树的各种操作的算法。
了解最优树的特性,掌握建立最优树和哈夫曼编码的方法