三、树和森林
1、树的存储结构
常用的链表结构:
a.双亲表示法
以一组连续空间存储树的结点,同时,结点结构中附设一个指示其双亲结点在链表中的位置的指示器。
#define MAX_TREE_SIZE 50
typedef struct PTNode{
TElemType data;
int parent; //双亲位置
}PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n; //根的位置和结点数
}PTree;
这种存储结构利用了除根结点以外的每个结点只有一个双亲的性质,Parent(T,x)函数可找到其双亲。反复调用Parent函数,直到遇到无双亲的结点时,便找到了树的根。
在这种表示法中,求结点的孩子时很不方便。
2、孩子表示法
这种表示方法是把每个结点的孩子结点排列起来,看成是一个线性表,每个孩子结点以单链表作为存储结构,则n个结点有n个孩子链表(叶子结点的孩子链表为空表)。而n个头指针又组成一个线性表,为了便于查找,采用顺序存储结构。
typedef struct CTNode{ //孩子结点
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct{
TElemType data;
ChildPtr firstchild; //孩子链表头指针
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n,r; //结点数和根的位置
}CTree;
这种表示法便于实现关于结点孩子的操作。
可以把双亲表示法和孩子链表结合起来,可以实现关于两头的操作。
c.孩子兄弟表示法
此又称为二叉链表表示法。链表中每个节点有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟结点。
这样,将树转化为二叉树,以二叉链表形式存储。
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling; //两个链域
}CSNode,*CSTree;
给定一棵树,可以找到唯一的一棵二叉树与之对应。从物理结构方面,它们的二叉链表是相同的,只是域的名称不同、解释不同而已。
任何一颗和树对应的二叉树,其右子树必空。
按照树与二叉树的对应关系,将森林中第二棵树的根结点看成是第一棵树的根结点的兄弟,那么,森林也可转化为二叉树。
这时的二叉树有右子树。
森林和树的操作可转换为二叉树的操作。
3.树和森林的遍历
两种遍历树的方法:
一种是先根(次序)遍历树。即先访问树的根结点,然后依次先根遍历根的每棵子树。
另一种是后根(次序)遍历,即先依次后根遍历每棵子树,再访问根结点。
森林由三部分组成:①第一棵树的根节点;②第一棵树的子树森林;③除第一棵树以外的其他树构成的森林。
按照森林和树相互递归的定义,可推出森林的两种遍历方法:先序遍历和中序遍历。
遍历对应关系:
树的先根遍历<——>二叉树先序遍历<——>森林先序遍历
树的后根遍历<——>二叉树中序遍历<——>森林中序遍历
注意:森林的中序遍历指依次从左至右对森林中每棵树进行后根遍历。
借用二叉树的先序和中序遍历算法可实现遍历树和森林。因此二叉树的遍历是很重要的。
四、赫夫曼树
赫夫曼树又称最优树,是一类带权路径长度最短的树。
1、最优二叉树
路径上的分支数目称为路径长度。
树的路径长度:从树根到每一结点的路径长度之和。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
带权路径长度最小的二叉树称作最优二叉树或赫夫曼树。
2、赫夫曼编码
进行快速远距离通信时,将需传送的文字转换成由二进制的字符组成的字符串,并且总长尽可能短,以提高传送效率。
可以对每个字符设计长短不同的编码,让电文中出现次数角度的字符采用尽可能短的编码,则电文总长可大大减少。
要设计长短不等的编码,必须是任一个字符的编码都不是另一个字符的编码的前缀,这种编码称作前缀编码。
设计电文总长最短的二进制前缀编码,即为设计一颗赫夫曼树的问题,权值为n种字符出现的频率。每条路径形成的编码为赫夫曼编码。
总结:二叉树的遍历是重中之重。学会树和森林与二叉树的转换。