1. Python演算法之哈夫曼編碼
問題: 哈夫曼編碼,英文名稱 Huffman Coding,有時也翻譯為霍夫曼編碼,在1952年提出的,是最好的編碼方式。哈夫曼編碼在電子通訊方面有著重要的應用,同時也廣泛應用於數據壓縮坦槐,其壓縮率通常在20% 90%之間 赫夫曼碼是可變字長編碼(VLC)的一種。哈夫曼樹是最優二叉樹, 帶權路徑長度最小的二叉樹。
原理:
假設有幾個數字40,10,20,16,14。
首先將這五個數字按照從小到大的順序排列:10, 14,16,20, 40。
構建哈夫曼樹:
1.首先選取10,14
2.重新排序:16,20,24,40
3.重新排序24,36,40,60
4.按照二叉羨信沖樹左0右1,構建哈兄殲夫曼樹
所以最終得到數字10的編碼為100,數字14的編碼為101,數字16的編碼為110,數字20的編碼為111,數字40的編碼為0。
代碼:
運行結果:
2. 哈夫曼編碼/解碼器
注釋非常詳細,希望對你有所幫助!
#ifndef Huffman_Tree_h
#define Huffman_Tree_h
#endif
#include <stdio.h>
typedef struct {
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode, * HuffmanTree; //存儲赫夫曼樹的結點類型
typedef char * * HuffmanCode; //用於存儲字元集中各個字元相應的赫夫曼編碼
void strcpy(char *S1,char *S2){ //將字元串S2復制到S1
int i = 0;
while( S2[i] != '\0' ){
S1[i] = S2[i];
i++;
}
S1[i] = '\0';
}
void Select(HuffmanTree HT,int t,int &s1,int &s2){ //在HT[1]到HT[t-1]中找出權值最小的兩個S1和S2
int i = 1;
s1 = s2 = 0;
HT[0].weight = 65535;
while( i <= t ){ //遍歷查找權值最小的結點S1
if( HT[i].parent == 0 && HT[i].weight < HT[s1].weight )
s1 = i;
i++;
}
i = 1;
while( i <= t ){ //遍歷查找除S1外權值最小的結點S2
if( i != s1 && HT[i].parent == 0 && HT[i].weight < HT[s2].weight )
s2 = i;
i++;
}
}
int HuffmanCoding( HuffmanTree &HT,HuffmanCode &HC,int *w,int n){ //根據各個字元的權值構造赫夫曼樹HT,將對應的赫夫曼編碼存儲在HC中
int s1,s2,m,i,start;
unsigned int c,f;
HTNode * p;
char *cd;
if( n <= 1 ) return 0;
m = 2 * n - 1; //赫夫曼樹的總結點樹為m
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //申請存儲赫夫曼樹的空間
for(p = HT + 1, i = 1; i <= n; ++i, ++p, ++w){ //將各個葉子結點的weight賦以相應的權值,parent,lchild,rchild均賦為0
p->weight = *(w+1);
p->parent = p->lchild = p->rchild = 0;
}
for( ; i <= m; ++i, ++p ){ //將各個非葉子結點的weight,parent,lchild,rchild均賦為0
p->weight = p->parent = p->lchild = p->rchild = 0;
}
for( i = n + 1; i <= m; ++i ){ //構造赫夫曼樹,給各個非葉子結點賦值
Select(HT, i - 1, s1, s2);
HT[s1].parent = i; HT[s2].parent = i;
HT[i].lchild = s1; HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
HC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); //申請空間,用於存儲指向存儲各個字元相應赫夫曼編碼的字元數組的指針
cd = (char *)malloc(n * sizeof(char)); //申請用於求赫夫曼編碼
cd[n - 1] = '\0'; //編碼結束符
for( i = 1; i <= n; ++i){ //逐個字元求赫夫曼編碼
start = n -1; //編碼在數組cd[]中的最前位置
for(c = i,f = HT[i].parent; f != 0; c = f, f = HT[f].parent) //從葉子到根逆向求編碼
if(HT[f].lchild == c)
cd[ --start ] = '0';
else
cd[ --start ] = '1';
HC[i] = (char *)malloc((n - start)*sizeof(char)); //為第i個字元編碼分配空間
strcpy(HC[i], &cd[start]); //將cd[]數組的start位置到n-1位置復制給HC[i]
}
free(cd); //釋放空間
return 1;
}
以上為第一部分
#include <stdio.h>
#include <stdlib.h>
#include "Huffman_Tree.h"
#define Yes 1 //當程序已經調用過初始化赫夫曼樹的InitHuff_T()函數,或已從htfTree文件讀取過,則將Init_Mode置為Yes,否則為No
#define No 0
void InitHuff_T( HuffmanTree &HT, HuffmanCode &HC, char ch[],int &n ){ //初始化赫夫曼數,要求用戶輸入字元和相應權值
int i = 1,w[100],tem,j;
char a[20];
FILE *save;
printf("請輸入編碼字元集的大小n:");
scanf("%d",&n); //獲取用戶輸入的字元集個數
while( i <= n ){ //獲取用戶輸入的字元和相應權值,分別存儲在ch[]和w[]數組中
printf("請輸入第%d個字元和該字元的權值w:",i);
fflush(stdin);
scanf("%c%d",&ch[i],&w[i]);
i++;
}
ch[i] = '\0';
HuffmanCoding(HT,HC,w,n); //根據用戶的輸入,生成赫夫曼數及各個字元相應的赫夫曼編碼,分別存在HT樹和HC中
if(( save = fopen("htfTree","w")) == NULL ){ //打開用於存儲赫夫曼樹的文件
printf("Open file fail......\n");
exit(0);
}
tem = n; //接下來的14行是將字元集大小轉換成字元形式寫入到文件中
j = 0;
while( tem != 0 ){
tem = tem / 10;
j++;
}
tem = n;
a[j] = '\0';
while( tem != 0 ){
a[j - 1] = (char)(tem % 10 + 48);
tem = tem / 10;
j--;
}
fputs(a,save);
printf("%d\n",n); //向屏幕輸出字元集大小n
fputc('\n',save);
for( i = 1; i <= n; i++ ){ //分別向文件和屏幕輸出各個字元和相應的赫夫曼編碼
fputc(ch[i],save); printf("%c\t",ch[i]);
fputc('\t',save);
fputs(HC[i],save); printf("%s\n",HC[i]);
fputc('\n',save);
}
for(i = 1; i <= 2 * n - 1; i++ ){ //將赫夫曼樹各個結點的parent,lchild,rchild分別寫入到文件中
tem = HT[i].parent; //將i結點的parent轉換成字元並寫入到文件中
if(tem == 0){
fputc(tem + 48,save);
fputc(' ',save);
}
else{
j = 0;
while( tem != 0 ){
tem = tem / 10;
j++;
}
tem = HT[i].parent;
a[j] = '\0';
while( tem != 0 ){
a[j - 1] = (char)(tem % 10 + 48);
tem = tem / 10;
j--;
}
fputs(a,save);
fputc(' ',save);
}
tem = HT[i].lchild; //將i結點的lchild轉換成字元並寫入到文件中
if(tem == 0){
fputc(tem + 48,save);
fputc(' ',save);
}
else{
j = 0;
while( tem != 0 ){
tem = tem / 10;
j++;
}
tem = HT[i].lchild;
a[j] = '\0';
while( tem != 0 ){
a[j - 1] = (char)(tem % 10 + 48);
tem = tem / 10;
j--;
}
fputs(a,save);
fputc(' ',save);
}
tem = HT[i].rchild; //將i結點的rchild轉換成字元並寫入到文件中
if(tem == 0){
fputc(tem + 48,save);
fputc('\n',save);
}
else{
j = 0;
while( tem != 0 ){
tem = tem / 10;
j++;
}
tem = HT[i].rchild;
a[j] = '\0';
while( tem != 0 ){
a[j - 1] = (char)(tem % 10 + 48);
tem = tem / 10;
j--;
}
fputs(a,save);
fputc('\n',save);
}
}
fclose(save);
}
void Encoding(HuffmanTree &HT, HuffmanCode &HC, char ch[]){ //根據赫夫曼編碼將用戶指定的文件中的字元編成相應的編碼,並將所得編碼存儲到用戶指定文件
FILE *ToBeTran,*CodeFile;
char ToBeTran_Name[100],CodeFile_Name[100]; //存儲用戶指定文件的文件名
int i;
char c;
printf("請輸入所要進行編碼的文件的文件名:");
scanf("%s",ToBeTran_Name); //獲得所要進行編碼的文件的文件名
if(( ToBeTran = fopen(ToBeTran_Name,"r")) == NULL ){ //打開文件
printf("Open file fail......\n");
exit(0);
}
printf("請輸入編碼後編碼表示的信息所存儲到的文件的文件名:");
scanf("%s",CodeFile_Name); //獲得編碼後編碼表示的信息所存儲到的文件的文件名
if(( CodeFile = fopen(CodeFile_Name,"w")) == NULL ){ //打開文件
printf("Open file fail......\n");
exit(0);
}
c = fgetc(ToBeTran); //從文件讀取一個字元
while( c != EOF ){ //對文件中的各個字元進行編碼,直至文件結尾
i = 1;
while( c != ch[i] && ch[i] != '\0' ) //在ch[]數組中查找從文件讀取的字元
i++;
if(ch[i] == '\0'){ //未找到,c不在ch[]數組中,c無法被識別,程序出錯,退出
printf("字元%c無法識別,程序將退出。\n",c);
exit(0);
}
fputs(HC[i],CodeFile); //若找到,則將c相應的赫夫曼編碼寫入到文件中
printf("%s",HC[i]); //將c相應的赫夫曼編碼輸出到屏幕
c = fgetc(ToBeTran); //讀入文件中的下一個字元
}
printf("\n");
fclose(ToBeTran);
fclose(CodeFile);
}
void Decoding(HuffmanTree HT, char ch[] , int n){ //對指定的存儲由赫夫曼編碼表示的信息的文件進行解碼,翻譯成相應的字元表示,並存儲到指定文件
int p,i = 1;
char code[1000],c;
char CodeFile_Name[100],TextFile_Name[100]; //存儲用戶指定文件的文件名
p = 2 * n - 1;
FILE *CodeFile,*TextFile;
printf("請輸入所要譯的文件名:");
scanf("%s",CodeFile_Name); //獲得所要譯的文件的文件名
if(( CodeFile = fopen("CodeFile","r")) == NULL ){ //打開文件
printf("Open file fail......\n");
exit(0);
}
printf("請輸入譯後的字元存儲到的文件的文件名:");
scanf("%s",TextFile_Name); //獲得譯後的字元存儲到的文件的文件名
if(( TextFile = fopen(TextFile_Name,"w")) == NULL ){ //打開文件
printf("Open file fail......\n");
exit(0);
}
c = fgetc(CodeFile);
while( c != EOF ){
code[i] = c;
i++;
c = fgetc(CodeFile);
}
code[i] = '\0'; //從文件讀取字元,存儲在code[]數組中
i = 1;
while ( code[i] != '\0' && p != 0 ){ //對數組code[]中的赫夫曼編碼進行解碼
if ( code[i] == '0' )
p=HT[p].lchild; //進入左分支
else
p = HT[p].rchild; //進入右分支
if (!HT[p].lchild&& !HT[p].rchild){ //進入葉子結點
fputc(ch[p], TextFile); //將相應的字元寫入到文件中
printf("%c",ch[p]); //將相應的字元輸出到屏幕
p = 2 * n - 1; //重新從樹根出發進行解碼
}
i++;
}
printf("\n");
}
void ReadHuff_T( HuffmanTree &HT, HuffmanCode &HC, char ch[], int &n){ //從文件讀取赫夫曼樹
FILE *htfTree;
char c[100],ch1;
int i,j,t;
if(( htfTree = fopen("htfTree","r")) == NULL ){ //打開存有赫夫曼樹信息的文件
printf("Open file fail......\n");
exit(0);
}
fgets(c,10,htfTree); //獲取赫夫曼樹葉子結點個數的字元串表示形式
i = 0; //以下6行將字元串形式轉換成整數形式
while( c[i] != '\n' )
i++;
n = 0;
for( j = 0; j < i; j++ )
n = 10 * n + c[j] - '0'; //求出葉子結點數n
HC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); //申請HC空間
HT = (HuffmanTree)malloc((2 * n) * sizeof(HTNode)); //申請赫夫曼樹存儲空間
i = 1;
while( i <= n ){
ch[i] = fgetc(htfTree); //讀取字元集中的一個字元
HC[i] = (char *)malloc((10)*sizeof(char)); //申請用於存儲讀取到的字元集中的字元的赫夫曼編碼的空間
fgetc(htfTree); //將『\t』輸出
ch1 = fgetc(htfTree); //讀取赫夫曼編碼,存儲在相應的HC[i][]數組里
int j = 0;
while( ch1 != '\n' ){
HC[i][j] = ch1;
j++;
ch1 = fgetc(htfTree);
}
HC[i][j] = '\0';
i++;
}
ch[i] = '\0';
i = 0;
while( i < 2 * n - 1 ){ //讀取赫夫曼樹的各個結點的parent,lchild,rchild.並賦值到赫夫曼樹HT中
ch1 = fgetc(htfTree); //讀取parent的字元串形式,存儲在c[]中,並將其轉換成整數形式,賦給HT[i].parent
j = 0;
while( ch1 != ' ' ){
c[j] = ch1;
j++;
ch1 = fgetc(htfTree);
}
HT[i+1].parent = 0;
for( t = 0; t < j; t++ )
HT[i+1].parent = 10 * HT[i+1].parent + c[t] - '0';
ch1 = fgetc(htfTree); //讀取lchild的字元串形式,並將其轉換成整數形式,賦給HT[i].lchild
j = 0;
while( ch1 != ' ' ){
c[j] = ch1;
j++;
ch1 = fgetc(htfTree);
}
HT[i+1].lchild = 0;
for( t = 0; t < j; t++ )
HT[i+1].lchild = 10 * HT[i+1].lchild + c[t] - '0';
ch1 = fgetc(htfTree); //讀取rchild的字元串形式,並將其轉換成整數形式,賦給HT[i].rchild
j = 0;
while( ch1 != '\n' ){
c[j] = ch1;
j++;
ch1 = fgetc(htfTree);
}
HT[i+1].rchild = 0;
for( t = 0; t < j; t++ )
HT[i+1].rchild = 10 * HT[i+1].rchild + c[t] - '0';
i++;
}
}
int main(){
HuffmanTree HT;
HuffmanCode HC;
char ch[100]; //用於存儲字元集
int n,Init_Mode = No; //n為字元集的大小,Init_Mode = No 表示內存中沒有赫夫曼樹的信息
char mode; //讓用戶選擇不同的操作
printf("請輸入你要選擇的功能\n");
printf("\t\tI -- 初始化\t\tE -- 編碼\n");
printf("\t\tD -- 解碼 \t\tQ -- 退出程序\n");
scanf("%c",&mode); //獲得用戶選擇的操作
while( mode != 'Q' && mode != 'q' ){ //當用戶輸入不為Q或q時,執行相應操作
switch(mode){
case 'I' :
InitHuff_T(HT,HC,ch,n);
Init_Mode = Yes;
break;
case 'i' :
InitHuff_T(HT,HC,ch,n);
Init_Mode = Yes;
break;
case 'E' :
if( No == Init_Mode )
ReadHuff_T(HT,HC,ch,n);
Encoding(HT,HC,ch);
Init_Mode = Yes;
break;
case 'e' :
if( No == Init_Mode )
ReadHuff_T(HT,HC,ch,n);
Encoding(HT,HC,ch);
Init_Mode = Yes;
break;
case 'D' :
if( No == Init_Mode )
ReadHuff_T(HT,HC,ch,n);
Decoding(HT,ch,n);
Init_Mode = Yes;
break;
case 'd' :
if( No == Init_Mode )
ReadHuff_T(HT,HC,ch,n);
Decoding(HT,ch,n);
Init_Mode = Yes;
default :
printf("您的輸入有錯,請重新選擇.\n");
}
printf("請輸入你要選擇的功能\n");
printf("\tI -- 初始化\tE -- 編碼\n");
printf("\tD -- 解碼 \tQ -- 退出程序\n");
fflush(stdin);
scanf("%c",&mode); //讓用戶繼續選擇相應的操作,直至用戶選擇退出
}
return 0;
}