分层次的非线性结构——树05

线索二叉树

二叉链表线索化的实现

【方案一】增加前趋后继指针域
可以增加二叉链表结点中的指针域来存放前趋和后继结点信息,其中Lthread表示前趋结点的地址,Rthread表示后继结点的地址。
在这里插入图片描述
在这里插入图片描述
对树的先序遍历而言,结点的孩子与后继的关系是:任一结点的后继是其左孩子;若左孩子为空,则为右孩子;若右孩子也为空,则为后继。

考虑到先序遍历中,结点的孩子与后继是一样的,因此只用一个孩子结点的指针域即可,可以考虑利用原结点的指针域来存储前趋、后继的线索。
【方案二】利用原结点的指针域
在这里插入图片描述
利用右孩子指针域,存储右孩子地址和后继线索;
利用左孩子指针域,存储左孩子地址和前驱线索。

考虑到利用原结点的指针域的方案是无法区分孩子还是线索;
为解决这个问题,可以设置相应的标志位来标明指针域中数据的属性,比如0代表孩子,1代表线索
在这里插入图片描述
存在结点有左孩子而无法记录其前驱的状况,如图中结点E,其前驱D的地址没有指针域记录,但对于先序遍历而言,只要有结点的后继线索即可,因此,前驱线索的“中断”不影响先序遍历的操作。

对二叉树以某种遍历顺序进行扫描,为每个结点添加前驱或后继线索的过程称为二叉树的线索化,加了线索的二叉树被称为线索二叉树。进行线索化的目的是为了简化并加快查找二叉树中结点的前驱和后继的速度,而且线索化后的二叉树遍历比较方便,不需要递归,程序运行效率高。
在这里插入图片描述
在这里插入图片描述
如何在线索二叉树的中序遍历中找到结点的前驱和后继结点?
在这里插入图片描述

在这里插入图片描述
线索化方法的总结

线索化的过程——在遍历过程中修改空指针

由于线索化的实质是将二叉链表中的空指针改为指向结点前驱或后继的线索,而一个结点的前驱或后继结点的信息只有在遍历时才能得到,因此线索化的过程即为在遍历过程中修改空指针的过程。为了记下遍历过程中访问结点的先后次序,可附设一个指针 pre 始终指向刚刚访问过的结点。当指针 p 指向当前访问的结点时, pre 指向它的前驱。因此也可推知 pre 所指结点的后继为 p 所指的当前结点。这样就可在遍历过程中将二叉树线索化。对于找前驱和后继结点这两种运算而言,线索树优于非线索树。但线索树也有其缺点。在进行插入和删除操作时,线索树比非线索树的时间开销大。原因在于在线索树中进行插入和删除时,除了修改相应的指针外,还要修改相应的线索。

哈夫曼树(Huffman 树)及哈夫曼编码

哈夫曼树是带权路径长度最短的树,又称最优树,用途之一是构造通信中的压缩编码。先了解通信与编码的关系
在这里插入图片描述
译码具有唯一性的必要条件
——任一个字符的编码都不是另一个字符的编码的前缀
在这里插入图片描述
在这里插入图片描述

哈夫曼树的概念
在这里插入图片描述
在这里插入图片描述
哈夫曼树构造算法

根据哈夫曼树的定义,一棵二叉树要使其 WPL 值最小,必须使权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
基本思想如下:
1)根据给定的 n 个叶子权值,可以看成是 n 棵只有一个根结点的二叉树,设 F 是由这 n 棵二叉树构成的集合;
2)在 F 中选取两棵根结点数值最小的树作为左、右子树,构成一棵新的二叉树,置新二叉树根的权值等于左、右子树根结点权值之和;
3)从 F 中删除这两棵树,并将新树加入 F;
4)重复2)、3),直到 F 中只含一棵树为止;
在这里插入图片描述
哈夫曼树的算法实现

用优先队列构造哈夫曼树
在这里插入图片描述

1)算法描述

使用优先队列(Priority Queue)来完成哈夫曼树的构造过程,设结点的权值即是它的优先级(Priority)。
(1)把 n 个叶子结点加入优先队列,则 n 个结点都有一个优先权Pi, 1<=i<=n
(2)如果队列内的结点数>1,则:
①从队列中移除两个最小的 Pi 结点;
②产生一个新结点,此结点为①之移除结点之父结点,而此结点的权重值为①两结点之权值和;
③把②产生之结点加入优先队列中。
(3)最后在优先队列里的点为树的根结点(root)。

2)数据结构描述

typedef struct 	//结点结构体
{
    char data;                  	//结点值
    int weight;                 	//权值
    int parent;                 	//双亲结点
    int lchild;                 	//左孩子结点
    int rchild;                 	//右孩子结点
} HTree_Node;       
typedef struct                      //编码结构体 
{
	char bit[N];                    //存放哈夫曼编码
	int start;                      //从start开始读bit中的哈夫曼码 
 } HuffmanCode_node;                 

3)函数框架设计:
在这里插入图片描述
4)算法实现步骤图示
以权值分别为7、5、2、4的 4 个叶子为例,构造哈夫曼树步骤如图所示。
在这里插入图片描述
(1)初态
初始化:将双亲、左右孩子三个指针均置为-1。
输入:读入 n=4 个叶子的权值存于向量的前 4 个分量中,它们是初始森林中 4 个孤立的根结点上的权值。
(2)合并过程
对森林中的树共进行 n-1 次合并,所产生的新结点依次放入向量 tree 的第 i 个分量中(n<=i<2*n-1)。每次合并分两步:
①在当前森林 tree[n] 的所有结点中,选取权最小和次小的两个根结点 tree[p1].weight 和 tree[p2].weight 作为合并对象,其中,0<=p1,p2<=i-1。
②将根为 tree[p1].weight 和 tree[p2].weight 的两棵树作为左右子树合并为一棵新的树。
双亲:新树的根是新结点 tree[i].weight。因此,应将 tree[p1].weight 和 tree[p2].weight 的 parent 置为 i,孩子:将 tree[i] 的 lchild 和 rchild 分别置为 p1 和 p2 ,权值:新结点 tree[i] 的权值应置为 tree[p1] 和 tree[p2] 的权值之和。

注意:合并后 tree[p1].weight 和 tree[p2].weight 在当前森林中已不再是根,因为它们的双亲指针均已指向 tree[i] ,所以下一次合并时不会被选中为合并对象。

程序实现

/*=======================================
函数功能:创建哈夫曼树
函数输入:(哈夫曼树)
函数输出:(哈夫曼树)
=========================================*/
void create_HuffmanTree(HTree_Node hTree[])
{
    	int i,k,lnode,rnode;
 	int min1,min2;
	printf("data weight parent lchild  rchild\n");
    	for (i=0;i<M;i++)       
        hTree[i].parent=hTree[i].lchild=hTree[i].rchild=-1; //置初值
 	for (i=N;i<M;i++)                   	//构造哈夫曼树
	{
        min1=min2=32767;     			//int的范围是-32768-32767
        lnode=rnode=-1;      			//lnode和rnode记录最小权值的两个结点位置
        for (k=0;k<=i-1;k++)
        {
			if (hTree[k].parent==-1)    		//只在尚未构造二叉树的结点中查找
			{
               	if (hTree[k].weight<min1)  	//若权值小于最小的左结点的权值
				{
                    	min2=min1;  rnode=lnode;
                    	min1=hTree[k].weight;lnode=k;
				}
                	else if (hTree[k].weight<min2)
				{
                    	min2=hTree[k].weight;rnode=k;
				}
			}
		}
  		//两个最小结点的父结点是I
  		hTree[lnode].parent=i;  hTree[rnode].parent=i; 
  		//两个最小结点的父结点权值为两个最小结点权值之和
  		hTree[i].weight=hTree[lnode].weight+hTree[rnode].weight;   
  		//父结点的左结点和右结点赋值
 		hTree[i].lchild=lnode;  hTree[i].rchild=rnode;   
	}
	for (i=0;i<M;i++) 
	{
	printf("%4c%6d%6d%6d%6d\n",hTree[i].data,hTree[i].weight,
  			hTree[i].parent,hTree[i].lchild,hTree[i].rchild);
	}
}

哈夫曼编码

在已建立的哈夫曼树中,沿叶子结点的双亲路径回退到根结点,每回退一步,就走过了哈夫曼树的一个分支,从而得到一位哈夫曼码值,由于一个字符的哈夫曼编码是从根结点到相应叶结点所经过的路径上各分支所组成的0,1序列,因此先得到的分支代码为所求编码的低位码,后得到的分支代码为所求编码的高位码。
在这里插入图片描述

算法描述:
(1)在已建立的哈夫曼树结构中,从叶子结点 L 开始,找其双亲 F,根据 F,判断 L 是 F 的左孩子还是右孩子:左孩子则编码为 0,右孩子则编码为 1 。
(2)设 L = F,重复上述过程,直到 L 为根结点,得到的编码是从低位到高位。

程序实现

/*==========================================
函数功能:哈夫曼符号集编码
函数输入:哈夫曼树、(哈夫曼码编码表)
函数输出:(哈夫曼编码表)
=============================================*/
void create_HuffmanCode(HTree_Node hTree[], HCode_Node hCode[])
{
	int i,f,c;
	HCode_Node hc;
	for (i=0;i<N;i++)          //根据哈夫曼树求哈夫曼编码
	{
		hc.start=N;c=i;
		f=hTree[i].parent;
		while (f!=-1)          //循序直到树根结点结束循环
		{
			if (hTree[f].lchild==c)       //处理左孩子结点
				hc.bit[--hc.start]='0';
			 else                             //处理右孩子结点
				 hc.bit[--hc.start]='1';
			c=f;
			f=hTree[f].parent;
		}
		hCode[i]=hc;
	}
}

哈夫曼树的译码
在这里插入图片描述
算法描述:

从根结点A开始,根据电文找其孩子B:
编码为0,B为左孩子;
编码为1,B为右孩子。
设A=B重复以上过程,直到B为叶子结点。

关键在于从编码中找到与输入数据从初始位置相同的一组数字。
程序实现

/*====================================
函数功能:将哈夫曼码翻译为字符
函数输入:哈夫曼树、哈夫曼编码
函数输出:无
======================================*/
void decode_HuffmanCode(HTree_Node hTree[],HCode_Node hCode[])      
{
	char code[MAXSIZE];
	int i,j,k,m,x;

	scanf("%s",code);        		//把要进行译码的字符串存入code数组中
	while(code[0]!='#')
	for (i=0;i<N;i++)
	{
		m=0;                		//m为相同编码个数的计数器
 		for (k=hCode[i].start,j=0;  k<N;  k++,j++)  //j为记录所存储这个字符的编码个数
		{
			if(code[j]==hCode[i].bit[k]) m++;  //当有相同编码时m值加1
		}
		if(m==j)  //当输入的字符串与所存储的编码字符串个数相等		
	{
			printf("%c",hTree[i].data);
			for(x=0;code[x-1]!='#';x++)  //code数组用过的字符串删除
			{
				code[x]=code[x+j];
			}
		}
	}
}

测试函数:

/*=======================================
函数功能:输出哈夫曼编码表
函数输入:哈夫曼树、哈夫曼码编码表
函数输出:(哈夫曼码编码表)
屏幕输出:哈夫曼树、哈夫曼码编码表
=========================================*/
void display_HuffmanCode(HTree_Node hTree[],HCode_Node hCode[])
{
 	int i,k;
 	printf("  输出哈夫曼编码:\n"); 
 	for (i=0;i<N;i++)               //输出data中的所有数据
 	{
 	printf("      %c:\t",hTree[i].data);               
 	for (k=hCode[i].start; k<N; k++)  //输出所有data中数据的编码
 	{
 	printf("%c",hCode[i].bit[k]);                     
 	}
 	printf("\n");
 	}
}

/*============================================
  函数功能:对给定字符串进行编码
  函数输入:哈夫曼树、哈夫曼码编码表
  函数输出:无
  键盘输入:要编码的字符串
  屏幕输出:输入字符串的哈夫曼编码串
===============================================*/
void edit_HuffmanCode(HTree_Node hTree[],HCode_Node hCode[])   
{
	char string[MAXSIZE];                        
	int i,j,k;
	scanf("%s",string);      			//把要进行编码的字符串存入string数组中
	printf("输出编码结果:\n");
	for (i=0;string[i]!='#';i++)        	//#为终止标志
	{
		for (j=0;j<N;j++)
		{
			if(string[i]==hTree[j].data)	//查找与输入字符相同的编号
			{
				for (k=hCode[j].start; k<N; k++)
				{
               		printf("%c",hCode[j].bit[k]);
				}
			}
		}
	}
}

哈夫曼树应用举例——最佳判定算法

编制一个程序,将百分制转换成优秀、良好、中等、及格、不及格5个等级输出。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值