数据结构与算法(6-5)二叉树的应用--哈夫曼树与哈夫曼编码

目录

哈夫曼编码(最优二叉树)

一、优势:缩短电文长度

二、思想:

三、过程: 

四、图解实现过程: 

五、总代码 


哈夫曼编码(最优二叉树)


一、优势:缩短电文长度

 二、思想:

        获取每个字符出现的频率,用一个大小为[256]的数组保存(因为ASCII码是256个),最后作为每个字符的权重权重越大,意味着出现频率越大,希望它的码长越短,这样总体电文最小。最后把这些字符(不重复部分)、权重依次放入结点中,把这些结点作为一个个元素,从小到大依次组成哈夫曼树。

三、过程: 

先统计出字符出现的频率,作为其权重。
编码保存:把每个不重复的字符频率作为权重,用二叉树进行排序,二叉树上面的字符权重大(出现频率高),编码的码长短,下面的字符权重小(出现频率低),编码码长长。
编码:输入字符,遍历整个二叉树,对比是否和叶子结点相同,是则保存编码。(左0右1)
译码:输入二进制码,在二叉树中走一遍,走到叶子结点即为需要保存的编码。(左0右1)

(向左走为0,向右走为1)

四、图解实现过程: 

五、总代码 

//哈夫曼编码
//优势:缩短电文长度
//先统计出字符出现的频率,作为其权重。
//编码保存:把每个不重复的字符作为权重,用二叉树进行排序,二叉树上面的字符权重高(出现频率高),编码的码长短,
//下面的字符权重小(出现频率低),编码码长长
//编码:输入字符,遍历整个二叉树,对比是否和叶子结点相同,是则保存编码
//译码:输入二进制码,在二叉树中走一遍,走到叶子结点即为需要保存的编码。
#include<iostream>
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
using namespace std;

#define MAXSIZE 100

char str[MAXSIZE];						//要编码保存的电文
char str_en[MAXSIZE];					//编码时输入的电文
char str_de[MAXSIZE];					//解码时输出的电文
int code_de[MAXSIZE];				//解码时存储
int frequency[256] = { 0 };			//统计各字符出现频率
int flag_c[256] = { 0 };					//只存储一次,判断是否第一次出现
int length = 0;								//数组总长度
int LENGTH = 0;							//叶子结点长度
int sumFrequency = 0;					//权重之和
int index_s = 0;							//字符串数组
int code[64];								//编码
int index_c = -1;							//编码数组
int step = 0;									//编码/译码步数(从哈夫曼树的根往后下走)
int sumCode[4 * MAXSIZE];			//总编码
int flag_exit = 0;							//退出符号
int index = 0;								//译码数组
int decode_length;						//译码数组长度

//树元素
typedef struct Tnode
{
	char data;									//存放数据
	int frequency;							//权重(出现的频率)
	struct Tnode* lchild, * rchild;	//左右孩子
	int code[64];							//每个字符的哈夫曼码
	int code_length;						//编码长度
}Tnode;
Tnode T[MAXSIZE];						//存放各结点
Tnode* hfmTree;							//哈夫曼根结点
Tnode* Pre;									//存放前一个结点

//输入字符串
void Input()
{
	cout << "请输入需要编码并保存编码的电文:\n";
	gets_s(str, 100);
}

//输入待编码数组
void Encode_Input()
{
	cout << "请输入需要编码的电文:\n";
	getchar();
	gets_s(str_en, MAXSIZE);
}

//输出编码后的数组
void Encode_Output()
{
	int i = 0;
	//下标
	for (i = 0; i <= index_c; i++)
	{
		cout << sumCode[i];
	}
	cout << endl;
}

//输入待译码数组
void Decode_Input()
{
	int i = 0;
	char ch = ' ';
	step = 0;
	cout << "请输入需要译码的二进制码:\n";
	index = 0;
	getchar();									//吸收空格
	//读取每位字符,挨个化整存入整形数组
	while (ch != '\n')						//把每位字符保存到数组(换行退出)
	{
		ch = getchar();						//读取一位字符
		if (ch != '\n')
			code_de[i++] = ch - 48;	//字符化整
	}
	decode_length = i;					//译码数组长度
}

//获取权重
void GetFrequency()
{
	int i = 0;
	//统计字符频率
	for (i = 0; i < strlen(str); i++)
	{
		frequency[str[i]]++;					//频率表中对应字符出现数值+1
	}
}

//创建树节点
void CreateTnode()
{
	int i = 0, count = 0;
	//添加字符和频率到树结构体数组中
	for (i = 0; i < strlen(str); i++)
	{
		if (flag_c[str[i]] == 0)				//首次出现
		{
			flag_c[str[i]] = 1;					//标志出现过
			T[count].data = str[i];
			T[count].frequency = frequency[str[i]];
			T[count].lchild = NULL;
			T[count].rchild = NULL;
			sumFrequency += T[count].frequency;		//计算叶子结点总权重
			count++;
		}
	}
	LENGTH = count;
	length = count;
}

//获取最小值						/***********注:要整体替换,否则后面会出现混乱**********/
Tnode Getmin()
{
	int i, j, max = 0;
	Tnode temp;

	//选择排序(权重从高到低)(最后的一个最小)
	for (i = 0; i < length; i++)
	{
		max = i;
		for (j = i; j < length; j++)
		{
			if (T[j].frequency > T[max].frequency)
				max = j;
		}
		if (max != i)
		{
			temp = T[i];								//整体替换
			T[i] = T[max];
			T[max] = temp;
		}
	}
	return T[--length];			//取出最后一个结点(最小权重)
}

//遍历
void Traverse(Tnode* N)
{
	int i = 0;
	if (N != NULL)
	{
		Traverse(N->lchild);
		if (N->lchild == NULL && N->rchild == NULL)					//叶子结点
		{
			printf("%c结点权值:%d\t编码:", N->data, N->frequency);
			for (i = 0; i < N->code_length; i++)
			{
				cout << N->code[i];
			}
			cout << endl;
		}

		Traverse(N->rchild);
	}
}

//初始化哈夫曼树
void Init_hfmTree()
{
	hfmTree = (Tnode*)malloc(sizeof(Tnode));			//创建哈夫曼树
	hfmTree->lchild = NULL;
	hfmTree->rchild = NULL;
	Pre = hfmTree;														//前一个结点
}

//创建哈夫曼树
Tnode Create_hfmTree()
{
	int len;
	Tnode* N = (Tnode*)malloc(sizeof(Tnode));
	while (N->frequency != sumFrequency)
	{
		N = (Tnode*)malloc(sizeof(Tnode));
		N->lchild = (Tnode*)malloc(sizeof(Tnode));			//左孩子
		N->rchild = (Tnode*)malloc(sizeof(Tnode));			//右孩子
		if (length != -1)
			*N->lchild = Getmin();
		else
			N->lchild = NULL;												//地址设为空

		if (length != -1)
			*N->rchild = Getmin();
		else
			N->rchild = NULL;												//地址设为空

		N->data = '#';															//表示空
		N->frequency = N->lchild->frequency + N->rchild->frequency;		//求权重之和
		T[length++] = *N;													//放入数组
	}
	return *N;
}


//结点编码(初始存档编码)
void NodeEncode(Tnode* N)
{
	int i = 0;
	if (N != NULL)
	{
		//向左走
		if (Pre->lchild == N)
		{
			code[step++] = 0;				//向左走
		}
		else if (Pre->rchild == N)
		{
			code[step++] = 1;				//向右走
		}
		Pre = N;									//保存上一结点

		if (N->lchild != NULL && N->rchild != NULL)		//未到达叶子结点
		{
			NodeEncode(N->lchild);
			step--;										//向回走

			Pre = N;									//保存上一结点

			NodeEncode(N->rchild);
			step--;										//向回走
		}
		else												//到达叶子结点
		{
			for (i = 0; i < step; i++)
				N->code[i] = code[i];			//记录编码
			N->code_length = step;			//记录码长
		}
	}
}


//编码
void Encode(Tnode* N)
{
	int i = 0;
	if (flag_exit)
		return;
	if (N != NULL && str[index_s] != '\0')				//到达叶子结点或者字符串到尾
	{
		if (N->lchild != NULL && N->rchild != NULL)		//未到达叶子结点
		{
			Encode(N->lchild);
			Encode(N->rchild);
		}
		else														//找到叶子结点
		{
			if (N->data == str_en[index_s])		//是要找的叶子结点
			{
				flag_exit = 1;							//退出标志
				for (i = 0; i < N->code_length; i++)
				{
					sumCode[++index_c] = N->code[i];			//编码存入
				}
			}
		}
	}
}

//译码
void Decode(Tnode* N)
{
	int i = 0;
	if (N != NULL)
	{
		if (N->lchild != NULL || N->rchild != NULL)					//没有查找到叶子结点
		{
			if (code_de[index] == 0)			//向左走
			{
				index++;
				step++;
				Decode(N->lchild);
				step--;							//弹出来
			}
			else if (code_de[index] == 1)	//向右走
			{
				index++;
				step++;
				Decode(N->rchild);
				step--;							//弹出来
			}
		}
		else					//查找到叶子结点
		{
			cout << N->data;			//输出译码结果
		}
	}
}


int main()
{
	int choice;
	int i;
	Input();						//输入字符
	GetFrequency();		//获取字符串

	//叶子结点的创建与排序
	CreateTnode();			//创建树节点

	//哈夫曼树的初始化与创建
	Init_hfmTree();
	*hfmTree = Create_hfmTree();
	//结点编码
	step = 0;
	NodeEncode(hfmTree);
	while (1)
	{
		system("cls");
		cout << "请选择需要的服务:\n";
		cout << "1、查找所有已保存的结点编码和权\n";
		cout << "2、整体编码\n";
		cout << "3、整体译码\n";
		cout << "0、退出\n";
		cout << "请选择您需要的服务:\n";
		cin >> choice;
		//选项
		switch (choice)
		{
			//0、退出
		case 0:
			exit(0);
			break;
			//1、遍历所有叶子结点
		case 1:
			Traverse(hfmTree);				//查找所有编码和权
			break;
		case 2:
			index_s = 0;
			Encode_Input();					//编码输入
			//逐字符编码
			while (str_en[index_s] != '\0')
			{
				flag_exit = 0;
				Encode(hfmTree);
				index_s++;
			}
			Encode_Output();					//编码输出
			break;
			//3、译码
		case 3:
			//输入译码数组
			Decode_Input();
			index = 0;
			while (index < decode_length)
			{
				//译码
				step = 0;
				Decode(hfmTree);			//译码并输出
			}
			cout << endl;
			break;

		default:
			cout << "无此选项!" << endl;
			break;
		}

		system("pause");
	}

	return 0;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
一、二叉树(二) 1. 写算法 (1)二叉树的直径定义为从根结点至叶子的最大路径长度。编写算法,求二叉树(二叉链表)的直径。 (2)已知二叉树(二叉链表)根结点指针bt,树中两个结点的指针p、q。编写算法求距离结点*p和*q最近的公共祖先的地址。 (3)已知二叉树(二叉链表)根结点指针bt,利用二叉树叶子结点的rchild指针域将所有叶子结点从左向右连接成一个单向链表。算法返回单向链表头结点指针(即最左边第1个叶子结点的地址)。 2. 编程题 (1) 从键盘输入一个字符串(要求字符串中无重复字符),将串中字符当做完全二叉树的顺序存储结构,建立对应的完全二叉树的二叉链表存储结构,输出先、中、后序遍历结果。 (2) 用先序遍历法建立二叉树二叉链表存储结构(结点数据域类型为char,输入字符序列用字符'#'表示NULL),实现中序线索化,并用非递归算法输出中序遍历结果的正序和逆序序列。 二、图 1. 已知某无向图如下图所示。画出该图的多重邻接表存储结构示意图。根据该存储结构,写出从顶点v0出发,深度和宽度优先遍历顶点访问次序。 2.写一个算法,判断无向图是否有环。算法提要:深度优先遍历过程中,访问某顶点后,该顶点的邻接点中有已访问的顶点且该已访问邻接点不是该顶点的上一级递归出发顶点(即存在回边),则有环。 3.编程题: 建立无向图邻接表存储结构,输出深度和宽度优先遍历顶点访问次序。 4.编程题:建立AOE网络存储结构,计算并输出ve[]和vl[]。 5.选作题*:算法设计-已知AOE网络的邻接表存储结构G,ve[]和vl[]值已全部求取,写出算法,输出所有关键路径。要求每条关键路径用源点至汇点的顶点序列(拓扑有序)表示。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_(*^▽^*)_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值