【数据结构C/C++】C语言实现哈夫曼树的译码以及编码

19 篇文章 2 订阅
13 篇文章 4 订阅

个人秋招总结
网关项目推荐
C端项目


先附上代码效果图:
在这里插入图片描述
哈夫曼树编码讲解

前言

先来吐个槽,相对于编码,解码应该是更加烧脑的一个过程,在不使用string类型,纯c语言的方法下,解码效率本身就低,还需要用到大量的for循环去得到编码是真的心累,就一个解码花了我两个小时,晕倒,但是最后解码成功是真的让人喜悦,不多bb,开始教程。
首先如果想要理解接下来的代码,那么先看一眼我之前的哈夫曼编码的代码,因为我是基于编码上修改的。

密文输入

密文的输入很简单。直接贴代码,相信大家都能看得懂
可能比较难以理解的就是if里面for循环的部分,其实意思也就是先判断当前是哪一个字符,得到当前字符之后去得到这个字符对应的编码,比如a字符对应的编码是110,那么就通过for循环把1,1,0这三个字符依次循环复制到bcode这个存放密文的数组,最后最后一定要记得在密文的最后补上一个’\0’哦!!!

//输入ABCDABCD得出对应的编码 编码已知 
void Code(char**arr,int num)
{
	int length = 0; char code[100]; char bcode[300];
	cout << "输入你明文(明文应该只包含你之前输入的字符)" << endl;
	cin >> code; 
	for (int i = 0; code[i] != '\0'; i++)//对每一个字符进行编码操作
	{
		for (int j = 0; j < num; j++)//对当前字符进行判断,判断当前字符是哪一个编码
		{
			if (code[i] == arr[j][0])//如果是当前编码 那就取出对应的编码然后放入编码数组
			{
				for (int h = 1; arr[j][h] != '\0'; h++)//编码数组的结尾是结束符
				{
					bcode[length++] = arr[j][h];//将密文数组赋值编码
				}
			}
		}
	}
	bcode[length] = '\0';		//密文结尾放上结束标识符
	cout << "密文是:";
	for(int i=0;bcode[i]!='\0';i++)
		cout << bcode[i];
	cout << endl;
	Decode(arr, bcode, num); //调用解码函数
}

解码

相对于简单的密文输入,解码的过程是真的烧脑,半个小时搞出思路,一个小时实现,半个小时debug,哭T-T。
那么接下来附上密文解密代码。
大致思路我用一张图来给大家呈现。
在这里插入图片描述
首先由于哈夫曼是非前缀码,所以我完全不需要担心误识别的情况,那么我就可以一个字符一个字符的与每个字符(假设为a)的编码去进行比对,比如我的密文第一位是1,那么我就可以与a的编码110去比对,发现不对,就退出一个循环,然后把b的编码放入数组,在进行一次比对也没有成功,循环往复,发现1不与任何字符能成功比对,那么我就在增加一个密文位,我用密文的前两个位与abcd四个字符的编码去比对,还是没有比对成功,那么我取到第三个密文位110的时候,我可以成功与a字符比对,那么我就输出a,循环往复,直到所有密文位都被我访问到,遇到了密文末尾的’\0’之后我就停止。
我创建了cmp和arrcmp两个数组,cmp用于存放当前的密文位(1,2,3…位),cmp用于存放字符abcd对应的编码,然后通过strcmp进行比较,比较成功就输出对应的字符,失败就继续for循环遍历下一个字符对应的编码。最后,解码成功


//已知字符对应的编码 已知密文 已知字符个数,字符个数-1可以得到最长的字符对应的编码
//strcmp函数可以用来比较两个字符串是否相同,我先取出num-1个字符串,然后逐一比较编码
//如果相同那么下标+num-1,不同则num-2,比较是否相同,循环往复
/*
需要引用#include <string.h>
比较字符串s1和s2: int strcmp(const char *s1, const char *s2);
比较字符串s1和s2前n个字符: int strncmp(const char *s1, const char *s2, size_t n);
如果两个字符一样大,返回值为 0, 如果s1>s2,则返回正值, 如果s1<s2,则返回负值
	int i = 0;
	char a[10] ;
	for (i = 0; i < 3; i++)
		cin >> a[i];
	a[i] = '\0';
	char b[10] = "asd";
	cout << strcmp(a, b);  返回0 说明可以使用这种方法,截取一部分的字符然后比较
	可以先取长度为1,也就是只取密文中的1个字符,然后用这个字符去遍历二维编码字符
	如果相同就输出对应的数据,然后数组后移去得到下一个,如果遍历完一次没有一样的,
	那么就增加这个数组中的数据,从密文中多取1一个数据,循环往复
*/
//解码函数 通过传递密文进去,解密
void Decode(char**arr,char*bcode,int num)
{
	int i; int j;
	int np = 0;//记录当前位置
	int flag = 0; //标志位 判断是否成功解码了一个字符
	int len = 1;//当前数据个数 初始为一个字符
	char cmp[10];//比较数组,用于比较编码是否一样
	char arrcmp[10];//二维数组中的编码
	cout << "收到的密文是:" << bcode << endl;
	while (bcode[np]!='\0')
	{
		for (len=1; flag != 1&&len<= num - 1; len++)
		{
			cmp[len - 1] = bcode[np];
			np++;
			cmp[len] = '\0';
			for (i = 0; i < num; i++)//二维数组中每一个行的遍历
			{
				for (j = 1; arr[i][j]!='\0'; j++)//二维数组中编码的遍历从1开始才是编码
				{
					arrcmp[j - 1] = arr[i][j]; //这个for用于先获取当前字符对应的编码
				}
				arrcmp[j - 1] = '\0'; //结束封顶 获取编码后下一步比较
				if (!strcmp(cmp, arrcmp))//两个数组内容一样
				{
					cout << arr[i][0];
					flag = 1;
				}
			}
		}
		flag = 0;
	}
}

完整代码

#include<iostream>
#include <cstdio>
#include<cstdlib>
#include <string.h>
#include<iomanip>
typedef int Status;
typedef int ElemType;
typedef char cElemType;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define MAXSIZE 20
#define make (struct student*)malloc(sizeof(struct student));
using namespace std;

typedef struct HuffmanTree
{
	int weight;				   //权值
	char character;			   //字符
	int parent, lchild, rchild;//双亲以及左右孩子
}HuffmanTree,*p_HuffmanTree;
//用于找到两个最小的权值  m1和m2指针传递回最小两个值
void Decode(char** arr, char* bcode, int num);
void FindTwoMin(p_HuffmanTree T, int n, int* m1, int* m2)
{
	int min1, min2; //定义两个存放最小数据的整型
	min1 = min2 = 300;	//最小值初始化
	for (int i = 0; i < n; i++)//第一个for循环遍历一次 得到最小min1
	{
		if (T[i].weight < min1 && T[i].parent == 0)
		{
			min1 = T[i].weight;
			*m1 = i;  //将最小的数据的下标赋值给指针
		}
	}
	for (int i = 0; i < n; i++)//第二个for循环遍历一次,得到最小min2
	{	
		//这个if这样写的原因是由于如果我输入4 3 2 1 由于1+2=3 那么就会有两个3
		//如果单纯只有一个T[i].weight != min1
		//那么由于新生成的3不能等于输入的3,那么这个3就会被忽略,而导致3+4=7
		//而本来应该是3+3=6的
		if (T[i].weight < min2 && (T[i].weight != min1 || (i>*m1 && T[i].weight==min1)) && T[i].parent == 0)//判断条件 不能等于最小且不能是初始化值
		{
			min2 = T[i].weight;
			*m2 = i; //将最小的数据的下标赋值给指针
		}
	}
	
}
void InitHuffmanTree(p_HuffmanTree T, int n)
{
	int i;
	int* m1, * m2;
	m1 = (int*)malloc(sizeof(int)); //开辟两个存放最小数据下标的指针
	m2 = (int*)malloc(sizeof(int));
	if (n < 1)
	{
		cout << "初始化错误" << endl;
		exit(-1);
	}
	for (i = 0; i < 2 * n - 1; i++)//初始化为全0
	{
		T[i].lchild = 0; T[i].parent = 0; T[i].rchild = 0;
	}
	cout << "输入权值以及对应字符(先输入权值再输出字符)" << endl;
	for (i = 0; i < n; i++)//初始化节点权值
	{
		cin >> T[i].weight;
		cin >> T[i].character;  //先输入权值再输入字符 只有最初的几个节点有字符
	}
	for (i = n ; i < 2 * n - 1; i++)//这段代码用于将两个最小合并成一个新结点,补充数组
	{
		FindTwoMin(T, i , m1, m2);		//找到最小两个数据的下标此时以及初始化的数据有n个也就是i-1个
		T[*m1].parent = i; T[*m2].parent = i;			//让最小两个节点的双亲指向这个新数组下标
		T[i].lchild = *m1; T[i].rchild = *m2;			//左孩子指向最小数据的下标,右孩子指向第二小的
		T[i].weight = T[*m1].weight + T[*m2].weight;	//让两个数据的权值相加变为新结点权值
		T[i].character = '0';							//两个节点合成的数不需要字符
	}
}
//这段代码用来输出哈夫曼树各节点信息
void ShowHuffmanTree(const p_HuffmanTree T,int n,char**codearr)
{
	cout << "character   weight   parent   lchild   rchild" << endl;
	for (int i = 0; i < 2 * n - 1; i++)//输出各种属性
	{
		cout <<T[i].character << setw(15)<< T[i].weight << setw(10)<< T[i].parent << setw(10)
			<< T[i].lchild << setw(10) << T[i].rchild << endl;
	}
	for (int i = 0; i < n; i++)   //下面就是二维数组的输出数据的方法
	{
		for (int j = 0;  codearr[i][j] != '\0'; j++)
		{
			cout << codearr[i][j];
		}
		cout << endl;
	}
}
//这段代码用来实现哈夫曼编码  编码个数为初始节点个数num(main中定义)
//同时我们需要做到从下向上遍历,左孩子为0右孩子为1这标志放入一个数组(大小为n)
void CreateHuffmanCode(p_HuffmanTree T,char** codearr ,int num)
{
	int p;						//用于指向当前节点的双亲结点在数组的下标位置
	int j;						//j用来表示当前结点所在数组的位置
	int start;					//编码数组的下标,每存放一个数据向前移动一位
	char* code = new char[num + 1]; //生成一个存放编码的数组(倒序编码,从下往上的)
	code[num] = '\0';			//d110'\0' 最长编码的长度应该是n
	for (int i = 0; i < num; i++)
	{
		j = i;			//赋值当前结点位置
		start = num;//初始位置应该是n-1 3
		p = T[i].parent;//当前节点的双亲节点,循环向上得到各双亲结点
		while (p != 0)	//我们直到最后一个结点它的parent是0,因此如果遇到了0说明到了根节点
		{
			--start;
			if(T[p].lchild==j)	//判断是否是左孩子
			{ 
				code[start] = '0';  //左孩子为0
			}
			else
			{
				code[start] = '1'; //右孩子为1
			}
			j = p;			//当前结点变为双亲结点位置
			p = T[p].parent;	//结点变为双亲节点 回溯
		}
		codearr[i] = new char [num-start+1];  //开辟一个刚刚好能存放编码以及字符的数组
		code[--start] = T[i].character;		 //再向前移动一个位置然后存放字符例如abcd
		strcpy(codearr[i], &code[start]);	//把编码数组的其实地址放入到二维数组中去
	}
	delete code;//释放空间
}
//输入ABCDABCD得出对应的编码 编码已知 
void Code(char**arr,int num)
{
	int length = 0; char code[100]; char bcode[300];
	cout << "输入你明文(明文应该只包含你之前输入的字符)" << endl;
	cin >> code; 
	for (int i = 0; code[i] != '\0'; i++)//对每一个字符进行编码操作
	{
		for (int j = 0; j < num; j++)//对当前字符进行判断,判断当前字符是哪一个编码
		{
			if (code[i] == arr[j][0])//如果是当前编码 那就取出对应的编码然后放入编码数组
			{
				for (int h = 1; arr[j][h] != '\0'; h++)//编码数组的结尾是结束符
				{
					bcode[length++] = arr[j][h];//将密文数组赋值编码
				}
			}
		}
	}
	bcode[length] = '\0';		//密文结尾放上结束标识符
	cout << "密文是:";
	for(int i=0;bcode[i]!='\0';i++)
		cout << bcode[i];
	cout << endl;
	Decode(arr, bcode, num);
}
//已知字符对应的编码 已知密文 已知字符个数,字符个数-1可以得到最长的字符对应的编码
//strcmp函数可以用来比较两个字符串是否相同,我先取出num-1个字符串,然后逐一比较编码
//如果相同那么下标+num-1,不同则num-2,比较是否相同,循环往复
/*
需要引用#include <string.h>
比较字符串s1和s2: int strcmp(const char *s1, const char *s2);
比较字符串s1和s2前n个字符: int strncmp(const char *s1, const char *s2, size_t n);
如果两个字符一样大,返回值为 0, 如果s1>s2,则返回正值, 如果s1<s2,则返回负值
	int i = 0;
	char a[10] ;
	for (i = 0; i < 3; i++)
		cin >> a[i];
	a[i] = '\0';
	char b[10] = "asd";
	cout << strcmp(a, b);  返回0 说明可以使用这种方法,截取一部分的字符然后比较
	可以先取长度为1,也就是只取密文中的1个字符,然后用这个字符去遍历二维编码字符
	如果相同就输出对应的数据,然后数组后移去得到下一个,如果遍历完一次没有一样的,
	那么就增加这个数组中的数据,从密文中多取1一个数据,循环往复
*/
//解码函数 通过传递密文进去,解密
void Decode(char**arr,char*bcode,int num)
{
	int i; int j;
	int np = 0;//记录当前位置
	int flag = 0; //标志位 判断是否成功解码了一个字符
	int len = 1;//当前数据个数 初始为一个字符
	char cmp[10];//比较数组,用于比较编码是否一样
	char arrcmp[10];//二维数组中的编码
	cout << "收到的密文是:" << bcode << endl;
	while (bcode[np]!='\0')
	{
		for (len=1; flag != 1&&len<= num - 1; len++)
		{
			cmp[len - 1] = bcode[np];
			np++;
			cmp[len] = '\0';
			for (i = 0; i < num; i++)//二维数组中每一个行的遍历
			{
				for (j = 1; arr[i][j]!='\0'; j++)//二维数组中编码的遍历从1开始才是编码
				{
					arrcmp[j - 1] = arr[i][j]; //这个for用于先获取当前字符对应的编码
				}
				arrcmp[j - 1] = '\0'; //结束封顶 获取编码后下一步比较
				if (!strcmp(cmp, arrcmp))//两个数组内容一样
				{
					cout << arr[i][0];
					flag = 1;
				}
			}
		}
		flag = 0;
	}
}
int main()
{
	int num;
	cout << "输入节点个数" << endl;
	cin >> num;
	char** codearr = new char*[num];  //二维数组 用来存放所有的编码 以便之后输出
	p_HuffmanTree T = new HuffmanTree[2 * num - 1]; //创建二叉树节点数组
	InitHuffmanTree(T, num);  //初始化二叉树
	CreateHuffmanCode(T, codearr, num); //创建二叉树编码
	ShowHuffmanTree(T, num,codearr); //显示编码
	Code(codearr, num);
}

408考研各数据结构C/C++代码(Continually updating)

408考研各数据结构C/C++代码(Continually updating)
这个模块是我应一些朋友的需求,希望我能开一个专栏,专门提供考研408中各种常用的数据结构的代码,并且希望我附上比较完整的注释以及提供用户输入功能,ok,fine,这个专栏会一直更新,直到我认为没有新的数据结构可以讲解了。
目前我比较熟悉的数据结构如下:
数组、链表、队列、栈、树、B/B+树、红黑树、Hash、图。
所以我会先有空更新出如下几个数据结构的代码,欢迎关注。 当然,在我前两年的博客中,对于链表、哈夫曼树等常用数据结构,我都提供了比较完整的详细的实现以及思路讲解,有兴趣可以去考古。

  • 14
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
哈夫曼树是一种用于数据压缩的树形结构,可以通过构建哈夫曼树来得到哈夫曼编码。下面是用C++实现哈夫曼树和哈夫曼编码的示例代码: ```c++ #include <iostream> #include <queue> #include <vector> #include <string> #include <unordered_map> using namespace std; // 哈夫曼树节点 struct HuffmanNode { char ch; // 字符 int freq; // 出现频率 HuffmanNode *left, *right; // 左右子节点 HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {} }; // 用于比较哈夫曼树节点的函数对象 class HuffmanNodeCompare { public: bool operator()(HuffmanNode* a, HuffmanNode* b) { return a->freq > b->freq; // 以出现频率为优先级 } }; // 构建哈夫曼树 HuffmanNode* buildHuffmanTree(string str) { unordered_map<char, int> freqMap; // 统计各字符出现频率 for (char c : str) { freqMap[c]++; } priority_queue<HuffmanNode*, vector<HuffmanNode*>, HuffmanNodeCompare> pq; // 将字符及其出现频率构建成哈夫曼树节点,并加入优先队列 for (auto it = freqMap.begin(); it != freqMap.end(); it++) { HuffmanNode* node = new HuffmanNode(it->first, it->second); pq.push(node); } // 逐步合并哈夫曼树节点,直到只剩下一个节点,即根节点 while (pq.size() > 1) { HuffmanNode* left = pq.top(); pq.pop(); HuffmanNode* right = pq.top(); pq.pop(); HuffmanNode* parent = new HuffmanNode('$', left->freq + right->freq); parent->left = left; parent->right = right; pq.push(parent); } return pq.top(); } // 递归遍历哈夫曼树,得到字符的哈夫曼编码 void getHuffmanCode(HuffmanNode* node, string code, unordered_map<char, string>& codeMap) { if (!node) { return; } if (node->ch != '$') { codeMap[node->ch] = code; } getHuffmanCode(node->left, code + "0", codeMap); getHuffmanCode(node->right, code + "1", codeMap); } int main() { string str = "hello world"; HuffmanNode* root = buildHuffmanTree(str); unordered_map<char, string> codeMap; getHuffmanCode(root, "", codeMap); cout << "Huffman codes:" << endl; for (auto it : codeMap) { cout << it.first << ": " << it.second << endl; } return 0; } ``` 代码中,先统计字符串中各字符的出现频率,然后将它们构建成哈夫曼树节点,并加入优先队列。接着,逐步合并哈夫曼树节点,直到只剩下一个节点,即根节点。最后,递归遍历哈夫曼树,得到各字符的哈夫曼编码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZhangBlossom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值