【数据结构】C++实现之二叉树结构(二)

6.线索二叉树

6.1 概念
1)由前面提到的链式二叉树结构,有些特殊的二叉树会存在大量的空指针且带来浪费,且又二叉树可以轻松知道左右兄弟,但是对于结点前驱与后继的是很不容易知道,只有重复遍历才行,费事费力。因此我们就提出能不能利用这些空的空间,来告诉当前结点的前驱信息【比如中序遍历CDFGHJ,那么D的前驱与后继分别是C 、F】
2)我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)
在这里插入图片描述
3) 所以我们对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化
4)但是还是存在一个问题,那就是我们怎么分的清楚什么时候是指后继等或意思是左右子结点?因此,需添加两个标志
在这里插入图片描述
6.2 线索二叉树的实现

/*二叉树的二叉线索存储结构定义*/
typedef enum{Link,Thread} PointerTag;
//Link==0表示指向左右孩子指针
//Thread==1表示指向前驱与后继的线索
sturct BiTHrNode{
Telmtype data;
BiTHrNode *leftchild,rightchild;
PointerTag lefttag;
PointerTag righttag;
};
typedef BiTHrNode *BiTHrTree;
BiTHrTree pre;//全局变量,始终指向刚访问的结点

//中序遍历进行中序线索化
void InThreading(BiTHrTree p)
{
	if (p)
	{
		InThreading(p->leftchild) //递归左子树线索化
		if(!p->leftchild)   //没有左孩子
		{
			p->lefttag=Thread;   //前驱线索
			p->leftchild=pre;   //左孩子指针指向前驱
		}
		if(!p->rightchild)  //没有右孩子
		{
			p->righttag=Thread;   //后继线索
			p->rightchild=p;    //前驱右孩子指针指向后继线索
		}
		pre = p; //保持pre指向p的前驱
		InThreading(p->rightchild);  //递归右子树线索化
	}
}
		

7.树、森林与二叉树的转换

7.1 树转换为二叉树
1)加线,就是在所有兄弟结点之间加一条连线
2)去线,就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线
3)层次调整。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明 【第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子】

在这里插入图片描述
7.2 森林转换为二叉树
1)先把每棵树转换为二叉树
2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子结点,用线连接起来。当所有的二叉树连接起来后得到的二叉树就是由森林转换得到的二叉树
在这里插入图片描述
7.3 二叉树转化为森林
1)加线,若某结点的左孩子存在,将左孩子的右孩子、……都作为该结点的孩子,将该结点与这些右孩子用线连接起来
2)去线,删除原二叉树中所有结点与其右孩子结点的连线
3)整理使之结构层次分明
在这里插入图片描述
7.4 二叉树转换为森林
1)先把每个结点与右孩子结点的连线删除,得到分离的二叉树;
2)把分离后的每棵二叉树转换为树;
3)整理第(2)步得到的树,使之规范,这样得到森林。
在这里插入图片描述
7.5 树与森林的遍历
【树的遍历】
1)先根遍历:若树非空,则先访问根结点,再按照从左到右的顺序遍历根结点的每一棵子树。这个访问顺序与这棵树对应的二叉树的先序遍历顺序相同
2)后根遍历:若树非空,则按照从左到右的顺序遍历根结点的每一棵子树,之后再访问根结点。其访问顺序与这棵树对应的二叉树的中序遍历顺序相同
注意到我们并没有定义一般树的中根遍历,因为子结点该怎么分两部分并没有定义,所以只定义先、后根。

【森林遍历】
1)前序遍历:访问森林中第一棵树的根结点;前序遍历第一棵树的根结点的子树;前序遍历去掉第一棵树后的子森林。森林的前序遍历与所转换的二叉树的先序遍历相同
2)中序遍历第一棵树的根结点的子树;访问森林中第一棵树的根结点;中序遍历去掉第一棵树后的子森林。森林的中序遍历与所转换的二叉树的中序遍历相同
后续遍历只有数和二叉树才有

8. 赫夫曼树及其应用

8.1 概念
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径WPL长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径WPL长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。
1)路径长度:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。对于二叉树a,根结点到结点D的路径长度就为4,二叉树b中根结点到结点D的路径长度为2。
2)树的路径长度: 树的路径长度就是从树根到每一个结点的路径长度之和
在这里插入图片描述
按照上述所讲,则上图中二叉树a的WPL = 5 * 1 + 15 * 2 + 40 * 3 + 30 * 4 + 10 * 4 = 315。二叉树b的WPL = 5 * 3 + 15 * 3 + 40 * 2 + 30 * 2 + 10 * 2 = 220。315和220这两个结果分别意味着,如果用二叉树 a 的判断方法,10000个学生的成绩需要做31500次比较,而二叉树 b 的判断方法只要做22000次比较。很显然,二叉树b的效率比a高了很多。

8.2 赫曼夫树构造原理
1)对给定的n个权值{W1,W2,W3,…,Wi,…,Wn}构成n棵二叉树的初始集合F={T1,T2,T3,…,Ti,…,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。
2)在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和、
3)从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中
4)重复二和三两步,直到集合F中只有一棵二叉树为止**【具体示例如下】**
在这里插入图片描述
8.3 赫夫曼编码
赫夫曼树最大的成绩是在当前远距离通讯过程中,解决了数据传输最优化问题。比如有ABCDEF六个字母,通过0和1编码用二进制字符发送。编码后的数据为000001010011100101,解码的时候可以按照3位一份来解码。不管是任何文字语言,字母或汉字在某一话题或其他方面出现的频率是不一样的。如汉字中的“的”、“了”、“你”等等,都是频率极高的汉字。所以因为频频的不同,假设出现的概率分别为A 27、B 8、C 15、D 15、E 30、F 5,可以按照赫夫曼树的规律来规划。
在这里插入图片描述
1)原本的编码结果为: 000 001 010 011 100 101(共30字符)
2)现在的编码结果为: 01 1001 101 00 11 100(共25字符)
既然有编码,就必然有解码。简单说说解码,按照赫夫曼树这种编码方法,非0即1,且每个字符编码长短不等很容易混淆。所以若要设计出长短不等的编码,则必须是任意字符的编码都不是另一个字符编码的前缀(这种编码通常称为前缀编码)。自己观察A=01、B=1001、C=101、D=00、E=11、F=1000根本就不存在容易和 1001 、1000混淆的10和100编码,这一点从上面的0和1的左右分支图也能轻易的总结出,因为ABCDEF这几个字符都在输的最末端,所以不会出现这种容易混淆的问题。另外一点,当然是编码和解码都要规定好同样的赫夫曼编码规则

发布了30 篇原创文章 · 获赞 6 · 访问量 5690

猜你喜欢

转载自blog.csdn.net/qq_32643313/article/details/105438865