PS:这篇博客的发表已经快一两年了,以我现在的知识水平来看这个程序写的太烂了,什么函数的定义写在h里面啊,h文件没有预编译宏之类的问题,关键是这个程序的效率太低了,主要原因还是编码的时候不断左移的问题,整个程序的复杂度都是O(c*m^2)了,m是一个很大很大的数,c是最大的编码长度,这个东西当是一个怀念吧。
上次我在http://www.cnblogs.com/Philip-Tell-Truth/p/4787067.html这里,稍微讲了一下一种很普通的贪婪的算法,其实这个算法一开始是用来做压缩程序的,就是最著名的Huffman压缩算法
那么这个算法是怎么样的呢,我们知道,我们的字符如果以ASCII的形式表示的话(只算英文和数字),其实就是一堆二进制代码,一个char型字符占一个字节。那么我们想到怎么压缩呢?
因为我们的文档不可能打印那一些不可能出现在屏幕的字符,那么我们完全可以把这些字符去掉,然后制一套我们自己的字符表(二进制对应),不一定是1个字节对应一个字符,我们可以6个位对应一个字符这个样子,这样会对文档有一定的压缩。
然后我们再想,在一篇文档中,每一个字符出现的概率不是相等的,有一些字母会出现很多次,比如a,b,s,这些,而有一些会出现的相对较少比如z,q这些,那么我们可不可以根据这个来压缩文件呢?答案是肯定的,既然我们已经确定了我们要根据概率来压缩文件,我们很自然地就会想到贪婪算法。
我们先来看Fence Repair这一道题,这一道题最后我们是把最小的板占据树的最低空间,同样的,对于出现概率不一的字母,我们也可以给他构建一个这样的生成树(不同深度,分叉不同,对应的二进位的位数和状态(0,1)也会不同)。
比如有一个这样的文档,里面的字符出现的概率对应如下:
a | 000 | 10 | 30 |
e | 001 | 15 | 45 |
i | 010 | 12 | 36 |
s | 011 | 3 | 9 |
t | 100 | 4 | 12 |
space | 101 | 13 | 19 |
newline | 110 | 1 | 3 |
总和 | 174 |
我们在不考虑其出现的概率进行压缩的情况下,我们重新制了一个对应表,那么最后出来的比特率就会降低很多
现在我们尝试用概率结合贪婪算法继续进行压缩,我们知道,如果我们要构建一棵生成树的话,我们可以这样办,出现概率较高的字母,我们给他二进制的位比较少,出现概率比较低的字母,我们给他二进制位比较高,然后这样定义以后我们就会有一个很直观的结论,就是因为出现概率比较高的字母占的比特率会比较低,那么我们的整体占的比特数就会相应的低。这是一种贪婪的思想。
然后我们来想,怎么构建这样的字母表呢?我们知道,由于我们最后写到文件上肯定不能直接写ASCII码,不然我们做这些就没意义了,我们最后是要写比特到上面去的,也就是一堆01010101,那么我们既然只有0,1两种状态,我们就可以想到我们可以用二叉树的形式去构建,我们把字母放在节点上,然后结合成一棵二叉树,我们可以这样规定,如果字母在节点的左边,那么这个字母的下一个二进位就是0,在右边就是1。
那么我们最后就会得到一个二叉树,由于我们是要把出现概率较低的字母放在深度比较低的地方,我们可以先把概率较低的字母先结合起来,然后结合成一个新的节点,然后和其他节点继续结合,有点像修篱笆那题。大概就像下图那样
然后我们就得到了一棵生成树,现在我们就可以给所有的字母重新编码了,那么怎么编码呢?刚才我们说了,出现在比较高的字母他的编码会比较长,在深度比较低的字母编码会比较短(节点往左走相应的二进位就代表0,往右走就代表1)我们很容易就可以得到下面的编码表
a | 001 | 10 | 30 |
e | 01 | 15 | 30 |
i | 10 | 12 | 24 |
s | 00000 | 3 | 15 |
t | 0001 | 4 | 16 |
space | 11 | 13 | 26 |
newline | 00001 | 1 | 5 |
总和 | 146 |
可以看到,最后编码占的总空间更小了,达到了我们压缩的目的。
最后的问题就很简单了,怎么实现这个生成最优编码树呢?我们维护一个堆就可以了,建立完树以后就可以根据字母在树的位置给文件写编码拉!
下面是代码,全实现,有点长
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #define MAX 255//可读字符类型最多为255(相当于散列了) 5 #define UNSIGNED_LONG_32_MAX 4294967296ULL 6 #define SUFFIX ".hfc" 7 8 typedef int Position; 9 typedef struct node 10 { 11 char symbol; 12 int sum; 13 struct node *left, *right; 14 }NODE, *Heap; 15 typedef struct node_list 16 { 17 int weight; 18 int code_gragh[MAX]; 19 }LIST; 20 typedef struct _tree 21 { 22 int sum; 23 char symbol; 24 }Hf_Store; 25 26 //局部函数定义 27 Heap *Read_TextFile(int *,FILE **,Hf_Store **,char []); 28 Heap *Read_PackagedFile(int *, FILE **, Hf_Store **,int *,int *); 29 void Inivilzie_store(int[]); 30 void BulidHeap(Heap *, int); 31 void Percolate_Up(Heap *, int, Position); 32 NODE *DeleteMin(Heap *, int *); 33 NODE *Bulid_Tree(Heap *,const int); 34 void Push_in(NODE *, Heap *, int *); 35 void Unpackged_File_to_File(NODE *, FILE *,const int,const int,char[]); 36 void Write_In_Code_List(NODE *, int *, int[],const int,LIST[]); 37 void Write_in_file(NODE *,FILE *,Hf_Store *,const int,char []); 38 void *Allocate_In_Memory(const int, int *, int *, FILE *, LIST[]); 39 void Destory_Node(NODE *); 40 void Binary_to_ANSCII(NODE *,FILE *, FILE *,const int,const int); 41 void Op_OverFlow(unsigned long[], const int); 42 void Op_LackFlow(unsigned long[], const int); 43 Heap *Put_Array_To_Heap(Hf_Store *, const int); 44 inline void Put_size_to_array(const int, const int,const int, int[]); 45 inline void Put_arrary_to_size(int[],int *,int *,int *,int *); 46 inline void Destroy_Heap_node_and_HfTree(Heap **, Hf_Store **,NODE *); 47 48 void Inivilzie_store(int store[]) 49 { 50 for (int i = 0; i < MAX; i++) store[i] = 0; 51 } 52 53 Heap *Read_TextFile(int *length, FILE **fp, Hf_Store **Hf_Tree, char file_name[261]) 54 { 55 int i, word_sum = 0, tmp, j = 1,_store[MAX]; 56 NODE *tmp_node = NULL; 57 58 Inivilzie_store(_store);//初始化读取数组 59 while (1) 60 { 61 fflush(stdin); 62 printf("请输入文件名 "); 63 scanf("%s", file_name); 64 if ((*fp = fopen(file_name, "r")) == NULL) 65 printf("a文件不存在,请重新输入 "); 66 else break; 67 } 68 for (;;) 69 { 70 tmp = fgetc(*fp); 71 if (feof(*fp)) break; 72 if (!_store[tmp]) word_sum++; 73 _store[tmp]++; 74 } 75 *length = word_sum; 76 Heap *word_arrary = (Heap *)malloc(sizeof(Heap)*(word_sum + 1));//堆从1位置开始 77 *Hf_Tree = (Hf_Store *)malloc(sizeof(Hf_Store)*word_sum); 78 for (i = 0; i < MAX; i++) 79 if (_store[i]){ 80 tmp_node = (NODE *)malloc(sizeof(NODE)); 81 tmp_node->sum = _store[i]; 82 tmp_node->right = tmp_node->left = NULL; 83 tmp_node->symbol = i; 84 word_arrary[j] = tmp_node; 85 j++; 86 } 87 BulidHeap(word_arrary, *length); 88 for (int i = 0; i < word_sum; i++) 89 { 90 (*Hf_Tree)[i].symbol = (word_arrary[i + 1])->symbol; 91 (*Hf_Tree)[i].sum = (word_arrary[i + 1])->sum; 92 } 93 return word_arrary; 94 } 95 96 Heap *Read_PackagedFile(int *length, FILE **fp, Hf_Store **Hf_Tree,int *sum_byte,int *sum_bit_left,char source_file_name[]) 97 { 98 char file_name[261], *suffix = SUFFIX; 99 int inform[3];//文件头的三个信息 100 int sum_bit, scr_size; 101 102 while (1) 103 { 104 fflush(stdin); 105 printf("请输入你要解压文件的文件名(自动添加后缀.hfc) "); 106 scanf("%s", file_name); 107 strcat(file_name, suffix); 108 if ((*fp = fopen(file_name, "rb")) == NULL)//直接用二进制模式读取 109 printf("a文件不存在,请重新输入 "); 110 else break; 111 } 112 113 fread(&scr_size, sizeof(int), 1, *fp); 114 fread(source_file_name, sizeof(char), scr_size, *fp); 115 source_file_name[scr_size] = '