【哈夫曼编码】二叉树实现以及贪心算法

问题描述:
给定一段文字,求出占用位数最小的前缀码。

名词解释:
前缀码:任何代码都不是其他代码的前缀,比如:
l ----> 0
o ----> 10
v ----> 110
e ----> 1110
那么单词“love”的前缀码就是: 0101101110

问题解释:
求给定文字的最小前缀码,即根据频率来确定每一个字母的变长码长度,我们即可用到哈夫曼编码:
频率高的字母的变长码长度短
哈夫曼编码:
像上图演示的那样,哈夫曼编码我们需要用到二叉树来进行编码,再观察可知,我们是对频率进行了排序,选定最小两个字母来进行组合

(1).因此,我们需要定义一个数据结构,这个数据结构可以根据频率大小(权值)排序,这个数据结构还可以保存左、右叶子节点,可以保存字母的索引编号

struct node{
	int weight;//权值
	int depth;//后面遍历时需要的深度
	int l,r,num;//左右叶子节点、索引编号
	char t;//字母
	node(){};
	node(int w, int n)
	{
		this->weight = w;
		this->num = n;
	}
	bool operator<(const node& a)
	const{
		return this->weight > a.weight;
	}
}Leaf[1001];

(2).接下来我们进行输入操作,输入的时候,我们将输入的N个字母及其频率存储在Leaf【1:N】中,并建立优先队列(注意,优先队列与前面的Leaf【1:N】是分别独立的两组数据)

int main()
{
	int N,i;
	scanf("%d",&N);
	for(i = 1; i <= 2 * N - 1; i++)
		Leaf[i].l = Leaf[i].r = 0;//先初始化l和r
	std::priority_queue <node> Q;
	for(i = 1; i <= N; i++)
	{
		scanf("\n%c %d",&Leaf[i].t, &Leaf[i].weight);
		Leaf[i].num = i;
		Q.push(node(Leaf[i].weight, i));//将该数据的权值和索引编号压入优先队列
	}

(3).再接下来就是核心的建树操作了,前面已经确定了Leaf【1:N】的数据值,下面我们就是根据压入优先队列的数据继续填充Leaf,并建立二叉树:

	for(i = 1; i <= N - 1; i++)//最多继续加N - 1个数据
	{
		node L = Q.top();
		Q.pop();
		if(Q.empty())
			break;//若取一下之后队列空了,证明已经加到头了;
		node R = Q.top();
		Q.pop();//取出最小的两个数据分别作为左节点和右节点
		int ws = L.weight + R.weight;//权值之和
		Q.push(node(ws, i + N));//将求得的数据之和压入优先队列
		//***********建树***********:
		Leaf[i + N].l = L.num;
		Leaf[i + N].r = R.num;
		Leaf[i + N].num = i + N;
	} 

(4).建树之后就是遍历操作了,我们用先序遍历,在遍历的时候,将哈夫曼编码写入提前定义的二维数组中:

	memset(hfm, 0, sizeof(hfm));
	BL(i + N - 1, 0);//从最后存入的数据即根节点开始遍历;
	for(i = 1; i <= N; i++)
	{
		printf("%c: ",Leaf[i].t);
		for(int j = 0; j < Leaf[i].depth; j++)
		{
			printf("%d", hfm[i][j]);
		}
		printf("\n");
	}
	return 0;
}

int函数到此就结束了,接下来给出所用到的遍历函数:

int hfm[1001][1001], sum = -1;//sum用来记录遍历的层数;
void BL(int n, int f)//n代表节点序号,f代表是左叶子还是右叶子
{
	if(sum >= 0)//第二层才开始给出哈夫曼编码;
	{
	Leaf[n].depth = sum;
		if(f == 0)
			hfm[n][sum] = 0;
		else if(f == 1)
			hfm[n][sum] = 1;
		if(Leaf[n].l != 0 && Leaf[n].r != 0)//如果有左右节点,则传值
		{
			for(int i = 0; i <= sum; i++)
			{
				hfm[Leaf[n].l][i] = hfm[n][i];
				hfm[Leaf[n].r][i] = hfm[n][i];
			}
		}
		else // 如果没有,则返回;
			return;
	}
	sum++;
	BL(Leaf[n].l,0);
	BL(Leaf[n].r,1);
	sum--;
	return;
}

到此函数就已经全部给出了(亲测有效吼吼吼)
贴一下完整代码:

#include<iostream>
#include<stdlib.h>
#include<string.h>
#include<queue>

struct node{
	int weight;//权值
	int depth;//后面遍历时需要的深度
	int l,r,num;//左右叶子节点、索引编号
	char t;//字母
	node(){};
	node(int w, int n)
	{
		this->weight = w;
		this->num = n;
	}
	bool operator<(const node& a)
	const{
		return this->weight > a.weight;
	}
}Leaf[1001];

int hfm[1001][1001], sum = -1;//sum用来记录遍历的层数;
void BL(int n, int f)//n代表节点序号,f代表是左叶子还是右叶子
{
	if(sum >= 0)//第二层才开始给出哈夫曼编码;
	{
		Leaf[n].depth = sum;
		if(f == 0)
			hfm[n][sum] = 0;
		else if(f == 1)
			hfm[n][sum] = 1;
		if(Leaf[n].l != 0 && Leaf[n].r != 0)//如果有左右节点,则传值
		{
			for(int i = 0; i <= sum; i++)
			{
				hfm[Leaf[n].l][i] = hfm[n][i];
				hfm[Leaf[n].r][i] = hfm[n][i];
			}
		}
		else // 如果没有,则返回;
			return;
	}
	sum++;
	BL(Leaf[n].l,0);
	BL(Leaf[n].r,1);
	sum--;
	return;
}

int main()
{
	int N,i;
	scanf("%d",&N);
	for(i = 1; i <= 2 * N - 1; i++)
		Leaf[i].l = Leaf[i].r = 0;//先初始化l和r
	std::priority_queue <node> Q;
	for(i = 1; i <= N; i++)
	{
		scanf("\n%c %d",&Leaf[i].t, &Leaf[i].weight);
		Leaf[i].num = i;
		Q.push(node(Leaf[i].weight, i));//将该数据的权值和索引编号压入优先队列
	}
	for(i = 1; i <= N - 1; i++)//最多继续加N - 1个数据
	{
		node L = Q.top();
		Q.pop();
		if(Q.empty())
			break;//若取一下之后队列空了,证明已经加到头了;
		node R = Q.top();
		Q.pop();//取出最小的两个数据分别作为左节点和右节点
		int ws = L.weight + R.weight;//权值之和
		Q.push(node(ws, i + N));//将求得的数据之和压入优先队列
		//***********建树***********:
		Leaf[i + N].l = L.num;
		Leaf[i + N].r = R.num;
		Leaf[i + N].num = i + N;
	}
	memset(hfm, 0, sizeof(hfm));
	BL(i + N - 1, 0);//从最后存入的数据即根节点开始遍历;
	for(i = 1; i <= N; i++)
	{
		printf("%c: ",Leaf[i].t);
		for(int j = 0; j <= Leaf[i].depth; j++)
		{
			printf("%d", hfm[i][j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
以上!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值