/*
*
*
* 【树与森林】
*
* 树: 可以分为有序树和无序树,我们主要研究无序树: 树的各种问题都能通过转换成二叉树来解决
(如果二叉树表示的是树: 那么左边不再是左子树,而是孩子,右边不在是右子树,而是兄弟)
* 【一】树的表示方法:
①双亲表示法:【以顺序存储为例】
节点结构:
typedef struct PtNode
{
ElemType data; //数据域
int parent; //双亲位置
}PTNode;
树结构:
typedef struct PtTree
{
PTNode nodes[MaxSize]; //顺序表
int RootPos, Num; //根节点的位置,和节点个数
}PTree;
②孩子链表表示法:【以顺序存储为例】
孩子节点结构:
typedef struct CTNode
{
int child; //放孩子节点的下标【顺序结构】
struct CTNode * pNext;
}*ChildPtr; //孩子节点指针,把具有相同双亲的孩子连成一条链
双亲节点结构:
typedef struct
{
Elem data;
ChildPtr firstChild; //孩子链表的头指针
}CTBox; //双亲节点,存放具有相同双亲的孩子链表的头指针
树结构:
typedef struct
{
CTBox nodes[MaxSize]; //顺序存储
int Num, RootPos; //节点个数和根节点位置
}CTree;
③树的二叉链表(孩子-兄弟链表)表示法:【用于树 <=> 二叉树】
typedef struct CSNode
{
Elem data; //数据域
struct CSNode * firstchild; //二叉链表的左指针,指向子树根【如果有左右子树,只指向左子树,如果只有右子树那就指向右子树(指向第一颗子树),如果没有左右子树,那么指向NULL】
struct CSNode * nextsibling;//二叉树的右指针,指向兄弟节点
}CSNode, *CSTree;
1.由森林转换成二叉树的转换规则:
若森林F为空,则二叉树B为空
否则,由Root(T1)对应得到Node(Root):
由森林中的第一个颗树T1,作为二叉树的左子树【递归调用】
而剩下的T2.....Tn对应得到二叉树的右子树
2.由二叉树转换成森林的转换规则:
若二叉树B为空,则森林F为空
否则 由Node(Root)对应得到Root(T1);
由左子树对应得到森林的第一颗树T1,由右子树对应得到森林的剩余树T2,T3.....Tn
④
⑤
【二】树与森林的遍历
树的遍历:【见下图】
①先根遍历【对应二叉树的先序遍历】
②后根遍历【对应二叉树的中序遍历】
③按层次遍历
森林的遍历:【只是对树遍历的引用】
①先序遍历【用到树的先根遍历】
②中序遍历【用到树的后根遍历】
③按层次遍历
【三】树遍历算法的应用
①求树的深度的算法
int TreeDepth(CSTree T) //T指向森林里的第一颗树的根节点,那么就相当于二叉树中的根节点
{
if (!T)
return 0;
else
{
h1 = TreeDepth(T->firstchild); //求的是第一颗树的子树森林的深度
h2 = TreeDepth(T->nextsibling); //求的是除了第一棵树外的其他树的子树森林的深度
return (max(h1+1, h2));
}
}
②输出二叉树上所有从根到叶子节点的路径算法:
【二叉树上的叶子节点:左右指针都为空】
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);
}
}
③输出树中所有从根到叶子节点的路径算法:
【在树转换成的二叉树中,所谓的叶子节点跟原来树的叶子节点(左右指针都空)不一样了,而是左孩子为空就是叶子节点】
void OutPath(Bitree T, Stack & S) //T为指向森林的二叉树根指针
{ //输出森林中所有从根到叶的路径
while (!T)
{
Push(S, T->data);
if (!T->lChild)
PrintStack(S);
else
OutPath(T->lChild, S);
Pop(S);
T = T->rChild;
}
}
④ 建树的存储结构算法【要用队列】:
(‘#’,‘A’)//开始
(‘A’,‘B’)
(‘A’,‘C’)
(‘A’,‘D’)
(‘C’,‘E’)
(‘C’,‘F’)
(‘F’,‘G’)
(‘#’,‘#’)//结束
↓
队列: <- A B C D E F G <-
void CreatTree (CSTree & T)
{
T = NULL; //先是空树
for (scanf(&fa, &ch); ch!='#'; scanf(&fa, &ch))
{
p = GetTreeNode(ch); //得到当前输入字符ch的节点指针
EnQueue(Q, p); //把当前节点的指针入队列
if (fa == '#')
T = p; //结束
esle
{
GetHead(Q, s); //把Q里的队头元素取出来,放在s指针中
while (s->data != fa) //如果s的数据域和此次输入的双亲的字符不相等的话,把s出队列,在从队列里取下一个元素!
{
DeQueue(Q, s);
GetHead(Q, s);
}
if (!(s->firstchild)) //如果该节点的左孩子为空
{
s->firstchild = p; //那么左孩子域赋值为p
r = p; //r始终指向当前双亲节点最后连接的子根节点
}
else
{
r->nextsibling = p; //如果已经有了左孩子,那么赋值到右孩子
r = p;
}
}
}
}
->>【四】树的应用【哈夫曼树(或最优树)(或哈希曼树)与哈夫曼树编码】
①最优树的定义:【树的带权路径长度最短的树,称为“最优树”】
【回忆】节点的路径长度定义: 从根节点到该节点的路径分支的数目
树的路径长度定义为: 树中每个节点的路径长度的和
树的带权路径长度定义为:树中所有叶子节点的带权路径长度之和
WPL(T) = ∑Wk * Lk(所有叶子节点的权值 * 节点路径长度 的和)
【下面有图片加深理解】
最优树: 在所有含n个叶子节点,并带相同权值的m叉树中,必存在一颗其带权
路径长度取最小的树,称为“最优树”!
②如何构造最优树
例如: 给出6个叶子节点分别是: 19 21 10 4 9 27
构造的方法:【以二叉树为例】
1.取叶子中两个最小的(4,9),组成一颗新树,删除两个叶子节点,剩下 19 21 10 13 27
2.重复1操作: (10,13) ---> 剩下 19 21 23 27
3.重复1操作: (19,21) ---> 剩下 40 23 27
4.重复1操作: (23,27) ---> 剩下 40 50
5.重复1操作: (40,50) ---> 剩下90, 最后得到一个节点时停止重复
【见下方图片】
③前缀编码【哈希曼编码:使得总的电文编码长度最小, 称为最优前缀编码】
等长编码:每个字符的电文编码长度相等
不等长编码:每个字符的电文编码长度不相等,但任何一个编码不能是另一个码的前缀!否则在翻译的时候会有二义性
定义: 任何一个字符的编码都不是同一个字符集中另一个字符编码的前缀
【哈夫曼编码是一种最优前缀编码】(下方有图)
*
*
*
*/
树的遍历:
森林的遍历:
森林的先根遍历:
森林的中序遍历:
树的带权路径长度讲解图:
哈希曼树生成举例:
前缀编码例子: