树与森林,哈希曼树

/*
*
*
*		【树与森林】
*	
*		树: 可以分为有序树和无序树,我们主要研究无序树: 树的各种问题都能通过转换成二叉树来解决
		(如果二叉树表示的是树: 那么左边不再是左子树,而是孩子,右边不在是右子树,而是兄弟)

*	【一】树的表示方法:
		①双亲表示法:【以顺序存储为例】
			
			节点结构:
			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, 最后得到一个节点时停止重复
	【见下方图片】


	③前缀编码【哈希曼编码:使得总的电文编码长度最小, 称为最优前缀编码】

	  等长编码:每个字符的电文编码长度相等
	不等长编码:每个字符的电文编码长度不相等,但任何一个编码不能是另一个码的前缀!否则在翻译的时候会有二义性
	
		定义: 任何一个字符的编码都不是同一个字符集中另一个字符编码的前缀
		【哈夫曼编码是一种最优前缀编码】(下方有图)
*
*
*
*/

 

树的遍历:

 

 

 

森林的遍历:

 

 

森林的先根遍历:

 

 

森林的中序遍历:

树的带权路径长度讲解图:

 

 


哈希曼树生成举例:

 

前缀编码例子:

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值