哈夫曼树的编码与解码

DS二叉树——Huffman编码与解码

哈夫曼树简介

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
在这里插入图片描述
在这里插入图片描述

  • 根据数据出现的频率,使用不同长度的编码

一段文字由x个字符组成,每个字符的出现频率不同,比如a出现114514次,而c只出现了810次,如果对a使用短长度的编码(比如01),对b使用较长的编码(比如01011),相比于使用同样长度的编码(00001 与 01011),可使编码效率变高

题目

  • 给定n个字符及其对应的权值,构造Huffman树,并进行huffman编码和译(解)码。
  • 构造Huffman树时,要求左子树根的权值小于、等于右子树根的权值。
  • 进行Huffman编码时,假定Huffman树的左分支上编码为‘0’,右分支上编码为‘1’。

输入
第一行测试次数
第2行:第一组测试数据的字符个数n,后跟n个字符
第3行:第一组测试数据的字符权重 待编码的字符串s1 编码串s2

输出
第一行~第n行,第一组测试数据各字符编码值
第n+1行,串s1的编码值
第n+2行,串s2的解码值,若解码不成功,输出error!

样例输入
2 5 A B C D E
15 4 4 3 2
ABDEC
00000101100
4 A B C D
7 5 2 4
ABAD
1110110

样例输出
A :1
B :010
C :011
D :001
E :000
1010001000011
error!
A :0
B :10
C :110
D :111
0100111
DAC

哈夫曼树的构造规则

共有 n 个不同的字符需要编码,编码规则如下:

  • 每次合并,选择没有被选择的,两个权值最小的根节点:x1, x2,将他们的合并
  • 合并之后的根节点的权值是 x1的权值 + x2的权值
  • 合并之后的根节点仍然可以作为新的根节点被选择
  • 需要合并 n-1 次,最后剩下一个根,就是哈夫曼树的根

使用顺序结构(数组)构造哈夫曼树

初始状态:
在这里插入图片描述

第一次合并,选择 2,3 合并 在这里插入图片描述 在这里插入图片描述

第二次合并,选择 4, 4 在这里插入图片描述 在这里插入图片描述

第三次合并,选择 5, 8
在这里插入图片描述
在这里插入图片描述

第四次合并,选择 15, 13
在这里插入图片描述
在这里插入图片描述

哈夫曼树结构实现与创建:

#define maxlen 100

// 哈夫曼树节点类
class hft_node
{
public:
	hft_node();
	int weight;
	int parent;
	int lchild;
	int rchild;
	char ch;		// 该节点代表的字符
	string code;	// 该节点字符对应的编码
};

hft_node::hft_node()
{
	this->lchild = -1;
	this->rchild = -1;
	this->parent = -1;
}

// 哈夫曼树类
class hft
{
public:
	hft();
	int n;					// 需要编码的字符个数
	hft_node nodes[maxlen];	// 节点
	int visited[maxlen];	// 选择标识数组
	
	// 找两个未被选择的最小权值节点
	void min2(int &index1, int &m1, int &index2, int &m2, int range);
	void encode(string s);	// 获得字符的01编码
	void decode(string s);	// 对01编码解码获得字符串
};

创建哈夫曼树

  • 找两个最小权值且未被选择的节点:函数实现
void hft::min2(int &index1, int &m1, int &index2, int &m2, int range)
{
	int i;
	int min = 114514;
	int min_index = 0;
	
	// min
	for(i=0; i<range; i++)
	{
		if(visited[i]==0 && min>nodes[i].weight)
		{
			min = nodes[i].weight;
			min_index = i;
		}
	}
	visited[min_index] = 1;
	m1 = min;
	index1 = min_index;
	
	// sub min
	min = 114514;
	min_index = 0;
	for(i=0; i<range; i++)
	{
		if(visited[i]==0 && min>nodes[i].weight)
		{
			min = nodes[i].weight;
			min_index = i;
		}
	}
	visited[min_index] = 1;
	m2 = min;
	index2 = min_index;
}
  • 两两合并节点
// build hfm tree
	for(i=n; i<2*n-1; i++)
	{
		int idx1, m1, idx2, m2;
		min2(idx1, m1, idx2, m2, i);
		
		nodes[i].weight = m1 + m2;
		nodes[i].lchild = idx1;
		nodes[i].rchild = idx2;
		nodes[idx1].parent = i;
		nodes[idx2].parent = i;
	}

哈夫曼树编码

在这里插入图片描述 在这里插入图片描述

// hft coding
#define push push_back
#define pop pop_back
#define top back
for(i=0; i<n; i++)	// 对每个叶子,下标: 0~n-1 进行编码
{
	deque<char> s;
	int x = i;
	while(1)
	{	
		int pa = nodes[x].parent;
		if(pa == -1)
		{
			break;
		}
		
		if(nodes[pa].lchild == x)
		{
			s.push('0');
		}
		else
		{
			s.push('1');
		}
		x = pa;
	}
	
	// 因为是由叶子向根回溯,需要逆序输出才是真正的编码结果
	while(!s.empty())
	{
		nodes[i].code += s.top();
		s.pop();
	}
}// end of hft coding

对给定的字符串,得到它的01编码

void hft::encode(string s)
{
	for(int i=0; i<s.length(); i++)
	{
		char c = s[i];
		for(int j=0; j<n; j++)
		{
			if(c == nodes[j].ch)
			{
				cout<<nodes[j].code;
				break;
			}
		}
	}
	cout<<endl;
}

给定一串01串,得到解码后的字符串

void hft::decode(string s)
{
	deque<char> q;	// 队列保存解码结果
	
	int x = 2*n-2;	// 从根节点出发
	for(int i=0; i<s.length(); i++)
	{
		if(s[i] == '0')
		{
			x = nodes[x].lchild;
		}
		else 
		{
			x = nodes[x].rchild;
		}
		
		// 如果是叶子,解码完成
		if(nodes[x].lchild==-1 && nodes[x].rchild==-1)
		{
			q.push_back(nodes[x].ch);
			x = 2*n-2;	// 下一次循环再次从根出发
		}
	}
	
	// 如果读取01串之后,没有到达叶子节点,出错
	if(n<=x && x<2*n-2)
	{
		cout<<"error!"<<endl;
	}
	else 
	{
		while(!q.empty())
		{
			cout<<q.front();
			q.pop_front();
		}
		cout<<endl;
	}
}

完整代码

#include <iostream>
#include <string>
#include <deque>

using namespace std;
#define maxlen 100

class hft_node
{
public:
	hft_node();
	int weight;
	int parent;
	int lchild;
	int rchild;
	char ch;
	string code;
};

hft_node::hft_node()
{
	this->lchild = -1;
	this->rchild = -1;
	this->parent = -1;
}

class hft
{
public:
	hft();
	int n;
	hft_node nodes[maxlen];	
	int visited[maxlen];
	
	void min2(int &index1, int &m1, int &index2, int &m2, int range);
	void encode(string s);
	void decode(string s);
};

void hft::min2(int &index1, int &m1, int &index2, int &m2, int range)
{
	int i;
	int min = 114514;
	int min_index = 0;
	
	// min
	for(i=0; i<range; i++)
	{
		if(visited[i]==0 && min>nodes[i].weight)
		{
			min = nodes[i].weight;
			min_index = i;
		}
	}
	visited[min_index] = 1;
	m1 = min;
	index1 = min_index;
	
	// sub min
	min = 114514;
	min_index = 0;
	for(i=0; i<range; i++)
	{
		if(visited[i]==0 && min>nodes[i].weight)
		{
			min = nodes[i].weight;
			min_index = i;
		}
	}
	visited[min_index] = 1;
	m2 = min;
	index2 = min_index;
}

hft::hft()
{
	cin>>n;
	int i;
	for(i=0; i<n; i++)
	{
		cin>>nodes[i].ch;
	}
	
	for(i=0; i<n; i++)
	{
		cin>>nodes[i].weight;
	}
	
	for(i=0; i<2*n; i++)
	{
		visited[i] = 0;
	}
	
	// build hfm tree
	for(i=n; i<2*n-1; i++)
	{
		int idx1, m1, idx2, m2;
		min2(idx1, m1, idx2, m2, i);
		
		nodes[i].weight = m1 + m2;
		nodes[i].lchild = idx1;
		nodes[i].rchild = idx2;
		nodes[idx1].parent = i;
		nodes[idx2].parent = i;
	}
	
	// hft coding
	#define push push_back
	#define pop pop_back
	#define top back
	for(i=0; i<n; i++)
	{
		deque<char> s;
		int x = i;
		while(1)
		{	
			int pa = nodes[x].parent;
			if(pa == -1)
			{
				break;
			}
			
			if(nodes[pa].lchild == x)
			{
				s.push('0');
			}
			else
			{
				s.push('1');
			}
			x = pa;
		}
		
		while(!s.empty())
		{
			nodes[i].code += s.top();
			s.pop();
		}
	}// end of hft coding
	
	// hft display
	for(i=0; i<n; i++)
	{
		cout<<nodes[i].ch<<" :"<<nodes[i].code<<endl;
	}
}

void hft::encode(string s)
{
	for(int i=0; i<s.length(); i++)
	{
		char c = s[i];
		for(int j=0; j<n; j++)
		{
			if(c == nodes[j].ch)
			{
				cout<<nodes[j].code;
				break;
			}
		}
	}
	cout<<endl;
}

void hft::decode(string s)
{
	deque<char> q;
	
	int x = 2*n-2;
	for(int i=0; i<s.length(); i++)
	{
		if(s[i] == '0')
		{
			x = nodes[x].lchild;
		}
		else 
		{
			x = nodes[x].rchild;
		}
		
		if(nodes[x].lchild==-1 && nodes[x].rchild==-1)
		{
			q.push_back(nodes[x].ch);
			x = 2*n-2;
		}
	}
	
	if(n<=x && x<2*n-2)
	{
		cout<<"error!"<<endl;
	}
	else 
	{
		while(!q.empty())
		{
			cout<<q.front();
			q.pop_front();
		}
		cout<<endl;
	}
}

int main()
{
	int n;
	cin>>n;
	while(n--)
	{
		hft t;
		string s;
		cin>>s;
		t.encode(s);
		cin>>s;
		t.decode(s);	
	}

	return 0;
}
发布了18 篇原创文章 · 获赞 0 · 访问量 139

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/103648629
今日推荐