基于C语言利用哈夫曼树实现文件压缩

一、哈夫曼树

        具有n个权值的n个叶子结点,构造出一个二叉树,使得该树的带权路径长度(WPL)最小,则称此二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。

注意:哈夫曼树是带权路径长度最短的树,且权值越大的叶子结点离根结点越近。

二、哈夫曼编码

        哈夫曼编码是一种编码方式,又称“霍夫曼编码”,其是可变字长的编码(VCL)的一种,是由霍夫曼于1952年提出的一种编码方式,有时被称为最佳编码,一般称为Huffman编码

那么我们为什么要使用哈夫曼编码进行压缩?

首先原因在于,传统压缩方式中,字符的编码是以等字长的方式进行压缩,相比于哈夫曼编码的可变字长而言压缩率会降低。其次,传统方式中可能会出现某些字符的编码相同,存在二义性,相比于哈夫曼编码的唯一性。可以大大提高正确性。

2.1 如何得到哈夫曼编码

        通过已经构造的哈夫曼树,结点左分支记为二进制数“0”,结点右分支记为二进制数“1”。从根结点向下到该叶子结点路途经过的数字拼接成为的编码即为该字符的哈夫曼编码。现通过以下图例进行演示。

此时各字符对应的哈夫曼编码可表示为:

A:00        B:01        C:1

三、哈夫曼树实现文件压缩基本步骤

3.1 基本实现流程

3.2 使用文件输入流读取需压缩的文件,并判断该文件是否存在

存在后执行接下来的步骤,不存在提示用户,效果图如下

3.3 读取,统计字符

        用户输入正确文件路径且存在时使用文件输入流读取文件,将文件放于一个字符数组中(注意字符数组开辟空间应足够大)。统计出现的字符及其出现的次数。

char ch;
		while(feof(fp) == 0){
			fscanf(fp,"%c",&ch);
			str[ch]++;
		}
		printf("\n");
		for(i = 0; i < 1024; i++){
			if(str[i] != 0){
				count[n++] = i; 
			}
		}

3.4 构造哈夫曼树

注:由于后续会使用此哈夫曼编码,为了方便获取,这里将求出的哈夫曼编码放置于一个二维数组中。

        CreateHT(ht,node);
		char strarr[256][256];
		int aa,bb;
		for(aa=0; aa<256;aa++)
			for(bb=0;bb<256;bb++)
				strarr[aa][bb]=-1; 
		
		for(k = 0;k < node;k++){
			huffmanCode(ht,k,strarr[k]);
		}
void CreateHT(HTNode *ht, int n){
	int i, j, lnode, rnode n1, n2;
	for(i = n; i < 2*n-1; i++){
		lnode = 65432; rnode = lnode;
		n1 = 0; n2 = 0;
		for(j = 0; j < i; j++){
			if(ht[j].parent == -1){
				if(ht[j].weight <lnode){
					rnode = lnode;
					n2 =n1;
					lnode = ht[j].weight;
					n1 = j; 
				} else if(ht[j].weight < rnode){
					rnode = ht[j].weight;
					n2 = j; 
				}
			} 
		}
		ht[n1].parent = i;
		ht[n2].parent = i;
		
		ht[i].weight = lnode+rnode;
		ht[i].parent = -1;
		ht[i].lchild = n1;
		ht[i].rchild = n2;
	}
} 

3.5 根据构造哈夫曼树得到哈夫曼编码

        此时我们使用3.3中统计的字符作为叶子结点,其出现次数作为权值,构造哈夫曼树求出哈夫曼编码。下面以此段英文为例,得到每个字符对应哈夫曼编码。

You can take away our money,house,car,or even our clothes and we can surive.But if our health was taken away,we would surely die.That is why we always try to eat in a healthy way and excise regularly..

3.6 替换字符为其哈夫曼编码

        用户输入压缩至的文件路径,将替换好的0 1数字字符数组写入该文件。由于C语言没有基于字符串的操做,所以我们替换时只能通过对字符数组进行操作,涉及到了被替换字符串长度替换字符串长度的问题,以及字符数组的长度是否充足。由于此次替换被替换字符串只是一个字符,且字符数组开辟空间足够大。所以只考虑替换字符串长度,即字符对应哈夫曼编码的长度,在这里使用一个方法来进行替换。

void replace(char oldstr[], char key[], char rep[]){
	int oldstrLen, lengthOfKey, lengthOfRep, i, j , flag;
	char tmp[1000]; 
	oldstrLen = strlen(oldstr);
	lengthOfKey = strlen(key);
	lengthOfRep = strlen(rep);

	for( i = 0; i <= oldstrLen - lengthOfKey; i++){
		flag = 1;
		for(j = 0; j < lengthOfKey; j++){
			if(oldstr[i + j] != key[j]){
				flag = 0; 
				break;
			}
		}
		if(flag){
			strcpy(tmp, oldstr);
			strcpy(&tmp[i], rep);
			strcpy(&tmp[i + lengthOfRep], &oldstr[i + lengthOfKey]);
			strcpy(oldstr, tmp);
			i += lengthOfRep - 1;
			oldstrLen = strlen(oldstr);
		}
	}
}

替换完成后的字符数组使用输出流写入用户输入文件路径。

3.7 进制转换

        将文件中每八位二进制数转换为十进制数,再转换为其对应的ASCII码值,最后不足八位的二进制数以0补齐,实现文件压缩。

3.7.1 进制转换方法

int retTen(char str[]){
	int ten = 0;
	int t = 0;
	for(t = 0;t < strlen(str);t++){
		ten += (str[t]-'0') * pow(2,strlen(str) - t - 1); 
	} 
	return ten;
} 

3.7.2 实现效果

3.8 计算压缩率

        最后为验证是否实现压缩,可通过计算文件压缩率来验证。其中,压缩率=原文件字节大小/压缩后文件大小。

int oldFileSize=getFileSize(path);
int newFileSize=getFileSize(Gopath);
		
printf("\n");
printf("压缩成功!,文件已压缩至%s\n",Gopath); 
printf("该文件压缩率为:%.2lf\n",(double)newFileSize/(double)oldFileSize);
		

注:注意计算压缩率时的类型转换

源码q:  one   three  five  one  zero  nine  seven  four  three  one

  • 33
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
哈夫曼编码是一种数据压缩算法,可以将数据压缩成更小的数据量,以节省存储空间和传输带宽。下面是基于哈夫曼树的数据压缩算法的C语言实现: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_TREE_HT 100 #define MAX_CHAR 256 //哈夫曼树的结构体 struct MinHeapNode { char data; int freq; struct MinHeapNode *left, *right; }; struct MinHeap { int size; int capacity; struct MinHeapNode **array; }; //创建一个新的哈夫曼树节点 struct MinHeapNode* newNode(char data, int freq) { struct MinHeapNode* node = (struct MinHeapNode*) malloc(sizeof(struct MinHeapNode)); node->left = node->right = NULL; node->data = data; node->freq = freq; return node; } //创建一个新的最小堆 struct MinHeap* createMinHeap(int capacity) { struct MinHeap* minHeap = (struct MinHeap*) malloc(sizeof(struct MinHeap)); minHeap->size = 0; minHeap->capacity = capacity; minHeap->array = (struct MinHeapNode**) malloc(minHeap->capacity * sizeof(struct MinHeapNode*)); return minHeap; } //交换两个哈夫曼树节点 void swapMinHeapNode(struct MinHeapNode** a, struct MinHeapNode** b) { struct MinHeapNode* t = *a; *a = *b; *b = t; } //维护最小堆的性质 void minHeapify(struct MinHeap* minHeap, int idx) { int smallest = idx; int left = 2 * idx + 1; int right = 2 * idx + 2; if (left < minHeap->size && minHeap->array[left]->freq < minHeap->array[smallest]->freq) smallest = left; if (right < minHeap->size && minHeap->array[right]->freq < minHeap->array[smallest]->freq) smallest = right; if (smallest != idx) { swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]); minHeapify(minHeap, smallest); } } //判断最小堆是否只有一个节点 int isSizeOne(struct MinHeap* minHeap) { return (minHeap->size == 1); } //获取最小堆的最小节点 struct MinHeapNode* extractMin(struct MinHeap* minHeap) { struct MinHeapNode* temp = minHeap->array[0]; minHeap->array[0] = minHeap->array[minHeap->size - 1]; --minHeap->size; minHeapify(minHeap, 0); return temp; } //插入一个节点到最小堆 void insertMinHeap(struct MinHeap* minHeap, struct MinHeapNode* minHeapNode) { ++minHeap->size; int i = minHeap->size - 1; while (i && minHeapNode->freq < minHeap->array[(i - 1) / 2]->freq) { minHeap->array[i] = minHeap->array[(i - 1) / 2]; i = (i - 1) / 2; } minHeap->array[i] = minHeapNode; } //判断一个节点是否是叶子节点 int isLeaf(struct MinHeapNode* root) { return !(root->left) && !(root->right); } //创建一个最小堆,并将所有字符的频率作为节点的值 struct MinHeap* createAndBuildMinHeap(char data[], int freq[], int size) { struct MinHeap* minHeap = createMinHeap(size); for (int i = 0; i < size; ++i) minHeap->array[i] = newNode(data[i], freq[i]); minHeap->size = size; for (int i = (minHeap->size - 1) / 2; i >= 0; --i) minHeapify(minHeap, i); return minHeap; } //构建哈夫曼树 struct MinHeapNode* buildHuffmanTree(char data[], int freq[], int size) { struct MinHeapNode *left, *right, *top; struct MinHeap* minHeap = createAndBuildMinHeap(data, freq, size); while (!isSizeOne(minHeap)) { left = extractMin(minHeap); right = extractMin(minHeap); top = newNode('$', left->freq + right->freq); top->left = left; top->right = right; insertMinHeap(minHeap, top); } return extractMin(minHeap); } //打印哈夫曼编码表 void printCodes(struct MinHeapNode* root, int arr[], int top) { if (root->left) { arr[top] = 0; printCodes(root->left, arr, top + 1); } if (root->right) { arr[top] = 1; printCodes(root->right, arr, top + 1); } if (isLeaf(root)) { printf("%c: ", root->data); for (int i = 0; i < top; ++i) printf("%d", arr[i]); printf("\n"); } } //压缩数据 void compress(char* input_string, char* output_string) { int freq[MAX_CHAR] = {0}; int n = strlen(input_string); for (int i = 0; i < n; ++i) ++freq[input_string[i]]; struct MinHeapNode* root = buildHuffmanTree(input_string, freq, MAX_CHAR); int arr[MAX_TREE_HT], top = 0; printCodes(root, arr, top); for (int i = 0; i < n; ++i) { int j; for (j = 0; j < MAX_CHAR; ++j) if (input_string[i] == root->data) break; int k = 0; while (k < top) { output_string[k++] = arr[j++]; } } } int main() { char input_string[] = "Hello World"; char output_string[MAX_TREE_HT]; compress(input_string, output_string); printf("Compressed string: %s\n", output_string); return 0; } ``` 该算法主要包括以下步骤: 1. 创建一个哈夫曼树节点结构体,包括字符数据、字符出现频率、左子节点和右子节点。 2. 创建一个最小堆结构体,包括当前堆的大小、最大容量和哈夫曼树节点数组。 3. 实现最小堆的常用操作,包括维护最小堆性质、插入节点、获取最小节点等。 4. 构建哈夫曼树,将所有字符的频率作为节点的值,以最小堆的形式存储,并通过不断提取最小值构建哈夫曼树。 5. 打印哈夫曼编码表,遍历哈夫曼树,当遇到叶子节点时,输出该字符的编码。 6. 压缩数据,遍历输入字符串,找到对应字符的编码,并将编码存储到输出字符串中。 以上是哈夫曼编码的基本实现,实际应用中还需要考虑压缩文件头、解压缩等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值