std string 编码_关于哈夫曼树编码

aa153c0ce52073afd6c03c468ac8e086.png

我已经预想到可能一个月/两个月/三个月后的我回来看这份代码的时候有多想杀了现在的我了。。。


利用最优二叉树(也称哈夫曼树)可以对文本进行编码. 例如一个文本内只出现了A,B,C,D,E,F,G,H八种符号, 并且各自出现的频数如下表:

字符 频数 A 5 B 29 C 23 D 3 E 11 F 14 G 7 H 8

每个字符的ASCII码占8位, 一个字节, 容易算出该文本占用字节数: 5+29+23+3+11+14+7+8 = 100.

可以按类似以下方法构造最优二叉树, 实现这些字符的重新编码.

77adaeb4ac0a872e9c6ad4e226571740.png

由上图, 可以得到字符新的编码:

字符ABCDEFGH频数529233111478编码01101000011101011011101111

该文本占用的字节数为: (5*4+29*2+23*2+3*4+11*3+14*3+7*4+8*4)/8 = 33.875, 共34字节.

如果文本文件中的字符序列为: ABCDFF……

则该文本文件的编码文件的二进制序列为: 011010000111110110……

反过来, 由此二进制序列, 结合上面的哈夫曼树, 可以解码得到对应的字符序列

一. 问题分析

对于该问题,我们需要做的有如下x点:

1. 读入字符串,记录频数。

2. 想办法把树建立起来,建立方法是利用循环结构和众多递归函数(大多是中序排序)循环结构对后j个数频数排序,然后按最小的两个数是否已经在树里分类并向上或横向建树。

3. 遍历所有叶子,利用递归把叶子赋值编码并与字符对应

4. 然后根据最开始读入的字符串判断对应编码并输出

二. 解决方案

笔者用C语言的指针链式结构建立树然后想办法实现的代码,相对很多可能要长一些。

1. 我们用一个稍大的字符数组读入字符串,然后遍历一次,记录每个出现的字符的频数(此处简化为默认字符只有A-H来计数)

2. 开结构体数组,将每个字符的“字符”、“频数”、“是否已经被利用过在数中有连接的结点布尔变量flag”、“最后重新开一个相同大小的结构体数组用来储存对应编码的字符数组(在建立树的过程无用)”然后按频数sort排序

3. 然后进入循环体,用j=1;j<=7;j++来循环,(我们容易知道只用循环字符种类-1次,所以用for),进入循环体之前我们将后j个数按频数排序(因为按树的生成方式最小的j-1个数已经被利用过)然后分为三种情况:

l 第一种:如果两个最小的数(第j和第j+1)都没有在树里(即flag==0),那么重新在树中生成两个叶子和他们的父亲结点,不与其他已经存在的叶子连通。这个父亲的频数等于两个叶子的频数之和,并且在结构体数组中将第j+1个数的频数用j+1和j频数之和覆盖掉,因为第j小的数已经被转移到树中,此后也不会用到,所以不影响结果。

l 第二种:如果最小的两个数都在树里(即两个flag都==1)那么这两个数必然是树中的两个根结点,我们生成一个父亲结点,然后这两个根结点就是这个父亲的儿子。

l 第三种,只有一个数在树里,那么需要生成两个结点,一个是父亲,一个是现有的结点的兄弟叶子结点。

4. 树生成好后,我们对树进行遍历,遇到叶子就把叶子的频数、字符储存在一组新的结构体数组中的一个,遍历完毕后这个结构体数组outcome就是字符+对应的编码。然后根据字符ASCII码sort排序一次

5. 对照输入,for二重循环,遍历输入,寻找对应编码,打印输出。

6. 为了用指针有头有尾,我们把malloc来的空间再中序遍历树把使用的空间释放掉。至此,我的(laji)解法结束。

三.参考程序

#include<bits/stdc++.h>
#define N 8
using namespace std;
//使A-H频数与其一一对应
typedef struct eachchar{
	char x;
	int frequency;
	int flag;
	char code[1000];
}ec;
//对于每个结点,记录它的左儿子、右儿子、父亲地址、频数 
typedef struct Node{
	struct Node* left;
	struct Node* rightson;
	struct Node* father;
	ec knot;
	char code[1000];
}Node;
typedef struct List{
	Node* pointer;
}List;
//按频数排序 
bool cmp_fre(ec p,ec q){
	if(q.frequency!=q.frequency) return p.frequency<q.frequency;
	else return p.flag<q.flag;
}
//按ASCII码排序
bool cmp_x(ec p,ec q){
	return p.x<q.x;
}
void sum_each_character(char* p,int* q,int len)
{
	for(int i=0;i<len;i++){
		if(p[i]=='A') q[1]++;
		else if(p[i]=='B') q[2]++;
		else if(p[i]=='C') q[3]++;
		else if(p[i]=='D') q[4]++;
		else if(p[i]=='E') q[5]++;
		else if(p[i]=='F') q[6]++;
		else if(p[i]=='G') q[7]++;
		else if(p[i]=='H') q[8]++;
	}
}
//创建结点 
Node* create_nil(Node* p,int x){
	p->father=NULL;
	p->left=NULL;
	p->rightson=NULL;
	p->knot.frequency=x;
	return p;
}
int min_pointer_fre(ec* p,int j){
	int cnt=0,min=0;
	for(int i=j;i<=N;i++){
		if(p[i].flag==1){
			cnt++;
			if(cnt==1){
				min=p[i].frequency;
			}
			if(p[i].frequency<=min){
				min=p[i].frequency;
			}
		}
	}
	return min;
}
//创建第一个父亲 
void create_newknot(List* plist,int x,int y,ec* ec,int t){
	Node* p=(Node*)malloc(sizeof(Node));
	Node* q=(Node*)malloc(sizeof(Node));
	p=create_nil(p,x);
	q=create_nil(q,y);
	p->knot.x=ec[t].x;
	q->knot.x=ec[t+1].x;
	Node* r=(Node*)malloc(sizeof(Node));
	r->knot.frequency=x+y;
	r->father=NULL;
	r->left=p;
	r->rightson=q;
	q->father=r;
	p->father=r;
	plist->pointer=r;
}
//创建后面的父亲
void renew_knot(List* plist,int x){
	Node* r=(Node*)malloc(sizeof(Node));
	Node* q=(Node*)malloc(sizeof(Node));
	create_nil(q,x);//新平行节点 
	r->father=NULL;
	r->left=q;
	r->rightson=plist->pointer;
	r->knot.frequency=x+plist->pointer->knot.frequency;
	q->father=r;
	plist->pointer->father=r;
	plist->pointer=r;
}
//合并
void combine_knot(List* p,List *q){
	Node* r=(Node*)malloc(sizeof(Node));
	r->father=NULL;
	r->left=p->pointer;
	r->rightson=q->pointer;
	r->knot.frequency=q->pointer->knot.frequency+p->pointer->knot.frequency;
	p->pointer=r;
	q->pointer=r;
}
//编码
void assign_number(List* plist,char* temp,int cnt){
	if(plist->pointer->left==NULL
	&&plist->pointer->rightson==NULL){
		memcpy(plist->pointer->code,temp,cnt);
	}
	else{
		List pl,pr;
		pl.pointer=plist->pointer->left;
		pr.pointer=plist->pointer->rightson;
		temp[cnt]='0';
		assign_number(&pl,temp,cnt+1);
		temp[cnt]='1';
		assign_number(&pr,temp,cnt+1);
	}
}
//遍历
void go_through(List* plist,ec* outcome,int cnt){
	static int count=cnt;
	if(plist->pointer->left==NULL
	&&plist->pointer->rightson==NULL){
		memcpy(outcome[count].code,plist->pointer->code,strlen(plist->pointer->code));
		outcome[count].code[strlen(plist->pointer->code)]='0';
		outcome[count].x=plist->pointer->knot.x;
	}
	else{
		List pl,pr;
		pl.pointer=plist->pointer->left;
		pr.pointer=plist->pointer->rightson;
		go_through(&pl,outcome,count);
		count++;
		go_through(&pr,outcome,count);
	}
} 
//输出
void print_code(ec* outcome,char* input_string,int len){
	for(int i=0;i<len;i++){
		for(int j=0;j<N;j++){
			if(input_string[i]=='A'+j){
				printf("%s ",outcome[j+1].code);
			}
		}
	}
}
void free_malloc(List* plist){
	if(plist->pointer!=NULL){
		List pl,pr;
		pl.pointer=plist->pointer->left;
		pr.pointer=plist->pointer->rightson;
		free_malloc(&pl);
		free(plist->pointer);
		free_malloc(&pr);
	}
}
int main()
{
	int i,j,k,Count_character[N+1];//临时记录字符频数
	char input_string[1000];//输入字符串
	gets(input_string);
	int len=strlen(input_string);
		sum_each_character(input_string,Count_character,len);
	ec ec[N+1];//结构体数组,并为其赋值
	for(i=1;i<=N;i++){
		ec[i].frequency=Count_character[i];
		ec[i].x='A'+i-1;
		ec[i].flag=0;
	}
	List p[1000];
	for(k=0;k<1000;k++){
		p[k].pointer=(Node*)malloc(sizeof(Node));
	}
	int cnt_number=1;
	int cnt_pointer=1;
	//按频数排序,方便选择、检索
	for(int j=1;j<=N-1;j++){
		sort(ec+j,ec+N+1,cmp_fre);
		int t=min_pointer_fre(ec,j);//找最小的有结点的.
		//创建结点 
		if(t>=ec[j+1].frequency&&ec[j].flag==0&&ec[j+1].flag==0||t==0){
			create_newknot(&p[cnt_pointer],ec[j].frequency,ec[j+1].frequency,ec,cnt_number);
			cnt_pointer++;
			ec[j+1].frequency=ec[j+1].frequency+ec[j].frequency;
			ec[j+1].flag=1;
		}
		else{ 
			int xbs=0,xbs1=0,xbs2=0;
			for(xbs=1;xbs<=cnt_pointer;xbs++){
				if(p[xbs].pointer->knot.frequency==ec[j].frequency){
					xbs1=xbs;
				}
				if(p[xbs].pointer->knot.frequency==ec[j+1].frequency){
					xbs2=xbs;
				}
				if(xbs1&xbs2) break;
			}
			//合并结点
			if(ec[j].flag==1&&ec[j+1].flag==1){
				combine_knot(&p[xbs1],&p[xbs2]);
				ec[j+1].frequency=ec[j+1].frequency+ec[j].frequency;
			}
			else{//更新新的结点 
				if(ec[j].flag==1){
					renew_knot(&p[xbs1],ec[j+1].frequency);
					ec[j+1].frequency=ec[j+1].frequency+ec[j].frequency;
					ec[j+1].flag=1;
				}
				else if(ec[j+1].flag==1){
					renew_knot(&p[xbs2],ec[j].frequency);
					ec[j+1].frequency=ec[j+1].frequency+ec[j].frequency;
				}
			}
		}
		cnt_number++;
	}
	char temp[1000];
	memset(temp,0,1000);
	
		assign_number(&p[1],temp,0);//赋编码值 
	struct eachchar outcome[N+1];
	
		go_through(&p[1],outcome,1);//遍历获取叶子的字符+编码 
	sort(outcome+1,outcome+N+1,cmp_x); 
	
		print_code(outcome,input_string,len);//打印 
	free_malloc(&p[1]); 
	return 0;
}

四.运行结果

27f92bfb14944f60d0612a234e47fc24.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值