树——树和森林及哈夫曼树

树和森林

树与二叉树的转换

把树转化为二叉树进行处理,利用二叉树的算法来实现对树地操作
由于树和二叉树都可以用二叉链表作存储结构,则以二叉链表做媒介导出树与二叉树之间的一个对应关系
在这里插入图片描述
树转化为二叉树步骤:
①**加线:在兄弟之间加一连线
抹线:对每个结点,除了左孩子外,去除其与其余孩子之间的关系
旋转:以树的根结点为轴心,将整树顺时针旋转45°
树变二叉树:兄弟相连留长子
在这里插入图片描述
二叉树转化为树步骤:
加线:若p结点是双亲结点的左孩子,则将p的右孩子、右孩子的右孩子…沿分支找到所有右孩子,都与p的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:**将结点按层次排列,形成树结构
二叉树变树:左孩右右连双亲,去掉原来右孩线
在这里插入图片描述

森林与二叉树的转化

森林转换为二叉树(二叉树与多棵树之间的关系):
①将各棵树分别转换为二叉树
②将每棵树的根结点用线相连
③以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
森林变二叉树:树变二叉根相连
在这里插入图片描述
二叉树转换为森林:
①**抹线:将二叉树中根结点与其右孩子连线,及沿右分支所搜到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:**将孤立的二叉树还原成树
二叉树变森林:去掉全部右孩线,孤立二叉再还原
在这里插入图片描述

树和森林的遍历
树的遍历(三种方式)
  • 先根(次序)遍历:若树不空,则先访问根结点,然后依次先根遍历各子树
  • 后根(次序)遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点
  • 层次遍历:若树不空,则自上而下从左至右访问树中每个结点
    在这里插入图片描述
森林的遍历

将森林看作由三部分构成:

  • 森林中第一棵树的根结点
  • 森林中第一棵树的子树森林
  • 森林中其他树构成的森林
    在这里插入图片描述
  • 先序遍历:若森林不空,则①访问第一棵树的根结点;②先序遍历森林中第一棵树的子树森林;③先序遍历森林中(除第一棵树之外)其余树构成的森林
    即:依次从左至右对森林中的每一棵树进行先根遍历
  • 中根遍历:若森林不空,则①中序遍历森林中第一棵树的子树森林;②访问第一棵树的根结点;③中序遍历森林中(除第一棵树之外)其余树构成的森林
    即:依次从左至右对森林中的每一棵树进行后根遍历
    在这里插入图片描述

哈夫曼树

引子

在这里插入图片描述

基本概念
  1. 路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径
  2. 结点的路径长度:两结点间路径上的分支数
    在这里插入图片描述
  3. 树的路径长度:从树根到每一个结点的路径长度之和,记作TL
    在这里插入图片描述
    结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树,但是路径最短的二叉树不一定是完全二叉树,如b的H和I作为E的孩子时
  4. 权:将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权
  5. 结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积
  6. 树的带权路径长度:树中所有叶子节点的带权路径长度之和,记作WPL
    在这里插入图片描述
  7. 哈夫曼树:最优树,带权路径长度(WPL)最短的树
    “带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树等
  8. 哈夫曼树:最优二叉树,带权路径长的(WPL)最短的二叉树,构造哈夫曼树的算法叫做哈夫曼算法
    在这里插入图片描述
    由第一个图可知:满二叉树不一定是最优二叉树;
    哈夫曼树中权较大的叶子离根越近;
    具有相同带权结点的哈夫曼树不唯一
哈夫曼树的构造算法

哈夫曼算法:构造哈夫曼树的步骤
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法实现

顺序存储结构——一维结构数组

  1. 定义
    typedef struct {
    int weight;
    int parent, lch, rch;
    }HTNode, *Huffman Tree;
初始化
  1. 初始化HT[1…2n-1]:lch = rch = parent = 0
  2. 输入初始n个叶子结点:置HT[1…n]的weight值
    在这里插入图片描述
void CreatHuffmanTree(Huffman Tree HT, int n) {
	if (n <= 1) return;
	m = 2*n-1; //数组共2n-1个元素
	HT = (Huffman Tree)malloc(sizeof(HTNode)*(m+1)); //0号单元未用,HT[m]表示根结点
	for (i = 1; i <= m; i++) {
		HT[i].lch = 0;	HT[i].rch = 0;	HT[i].parent = 0;
	}
	for (i = 1; i <= n; i++) 
		scanf("%d", &HT[i].weight);
}
构造产生新结点

进行n-1次合并,依次产生n-1个结点HT[i],i = n+1…2n-1
①在HT[1…i-1]中选两个未被选过的(从parent==0的结点中选)的weight最小的两个结点HT[s1]和HT[s2],s1、s2为两个最小结点的下标
②修改HT[s1]和HT[s2]的parent的值:HT[s1].parent = i;HT[s2].parent=i
③修改新产生的HT[i]:HT[i].weight = HT[s1].weight + HT[s2].weight; HT[i].lch = s1; HT[i].rch = s2

for (i = n+1; i <= m; i++)  {  //合并产生n-1个结点——构造Huffman树
	Select(HT, i-1, s1, s2); //在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
	HT[s1].parent = i; HT[s2].parent = i; //表示从F中删除s1和s2
	HT[i].lch = s1; HT[i].rch = s2;
	HT[i].weight = HT[s1].weight + HT[s2].weight;
}
哈夫曼编码

左分支0,右分支1
保证是前缀码:因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码不可能是其它叶结点编码的前缀
保证是总长最短:因为哈夫曼树就是带权路径长度最短的,因此字符编码的总长最短
性质:

  1. 哈夫曼编码是前缀码
  2. 哈夫曼编码是最优前缀码
    算法实现:
    在这里插入图片描述
void CreatHuffmanCode (HuffmanTree HT, HaffumanCode &HC, int n) {
	//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
	HC = new char*[n+1]; //分配n个字符编码的头指针矢量
	cd = new char[n]; //分配临时存放编码的动态数组空间
	cd[n-1] = '\0';  //编码结束符
	for (i = 1; i <= n; i++) { //逐个字符求哈夫曼编码
		start = n-1;  c = i; f = HT[i].parent;
		while (f != 0)  {//从叶子结点开始向上回溯,直到根结点
			--start; //回溯一次start向前指一个位置
			if (HT[f].lchild == c) cd[start] = '0';
			else           		   cd[start] = '1';
			c = f; f = HT[i].parent;      //继续向上回溯
		}
		HC[i] = new char[n-start]; //为第i个字符串编码分配空间
		strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
	}
	delete cd; //释放临时空间
}//CreatHuffmanCode
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值