一:問題描述
【問題描述】
利用哈夫曼編碼進行通信可以大大提高信道利用率,縮短信息傳輸時間,降低傳輸成本。但是,這要求在發送端通過一個編碼系統對待傳數據預先編碼,在接收端將傳來的數據進行譯碼(復原)。對於雙工信道(即可以雙向傳輸信息的信道),每端都需要一個完整的編/譯碼系統。試為這樣的信息收發站寫一個哈夫曼碼的編/譯碼系統。
【任務要求】
一個完整的系統應具有以下功能:
1) I:初始化(Initialization)。從終端讀入字符集大小n,以及n個字符和n個權值,建立哈夫曼樹,並將它存於文件hfmTree中。
2) E:編碼(Encoding)。利用已建好的哈夫曼樹(如不在內存,則從文件hfmTree中讀入),對文件ToBeTran中的正文進行編碼,然后將結果存入文件CodeFile中。
3) D:譯碼(Decoding)。利用已建好的哈夫曼樹將文件CodeFile中的代碼進行譯碼,結果存入文件TextFile中。
【測試數據】
用下表給出的字符集和頻度的實際統計數據建立哈夫曼樹,並實現以下報文的編碼和譯碼:“THIS PROGRAM IS MY FAVORITE”。
字符 | A | B | C | D | E | F | G | H | I | J | K | L | M | |
頻度 | 186 | 64 | 13 | 22 | 32 | 103 | 21 | 15 | 47 | 57 | 1 | 5 | 32 | 20 |
字符 | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | |
頻度 | 57 | 63 | 15 | 1 | 48 | 51 | 80 | 23 | 8 | 18 | 1 | 16 | 1 |
二:大致思路
1.構建哈夫曼樹
有n個字母待建樹,申請p[2n-1]個結構體數組空間,將各個字母及其權值存入結構體數組,將數組按照權值進行排序,每次選取最小的兩個構建哈夫曼樹,每次從上次選擇的下一個開始選擇,即第一次p[0],p[1]則第二次p[2],p[3],以此類推,每次生成的從p[n+1]開始存入,直至數組就剩下一個元素,即為根節點。
2.編碼
從葉子結點(帶權字母即葉子結點,從1步驟的結構體數組那里入手,需要判斷一下是否為葉子結點)開始向上回溯即尋找父節點,若為父節點左子樹編碼為‘0’字符,反之為‘1’字符,建個數組從最后一位往前存入直至父節點為空即根節點,直至結構體數組最后一個元素。然后將存儲的字母的編碼和待編碼的字符串進行比對,將字符串譯碼為01結構,編碼完成。
3.譯碼
將01結構的編碼從根節點開始譯碼,0往左子樹遍歷1往右子樹,直至葉節點,譯碼出一個字母,然后再從根節點開始,循環操作直至所有都譯碼完成。
三:具體實現
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; typedef struct node { char ch[2]; int weight; char ldata,rdata; node *lchild,*rchild,*parent; }*huffman; typedef struct encoding { char ch[2]; char *data; }*encode1; void paixu(huffman *p,int m) { huffman temp; for(int i=0;i<m-1;i++) { for(int j=i+1;j<m;j++) { if(p[i]->weight>p[j]->weight) { temp=p[i]; p[i]=p[j]; p[j]=temp; } } } } huffman* Read(int n) //讀取英文字母 { huffman *huff; FILE *fp; huff=new huffman[2*n-1]; for(int i=0;i<2*n-1;i++) { huff[i]=new node; huff[i]->lchild=huff[i]->rchild=NULL; } if((fp=fopen("hfmTree.txt","w"))==NULL) {cout<<"文件打開失敗!";exit(0);} cout<<"please input the letter:"<<endl; for(int i=0;i<n;i++) { huff[i]->ch[0]=cin.get(); if(huff[i]->ch[0]=='\n') huff[i]->ch[0]=cin.get(); cin>>huff[i]->weight; cin.get(); huff[i]->ch[1]='\0'; fprintf(fp,"%s%d ",huff[i]->ch,huff[i]->weight); } cout<<"hfmTree.txt文件寫入完成!"<<endl; fclose(fp); return huff; } node* Crhuffman(huffman* p,int n,int *n1) //創建哈夫曼樹 { int m=n; for(int i=0;i<2*n-1;i+=2) { if((m-1)!=i) { paixu(p,m); p[m]->weight=p[i]->weight+p[i+1]->weight; p[m]->lchild=p[i]; p[m]->rchild=p[i+1]; p[i]->parent=p[m]; p[i+1]->parent=p[m]; m+=1; } else {p[m-1]->parent=NULL;break;} } *n1=m; return p[m-1]; } int Code(huffman *p,int n,int t,char *c,int c1,char *c2) //哈弗曼編碼 { huffman q; FILE *fp; q=new node; int k=0,k1=t-1,kk=0; encode1 *encode; encode=new encode1[t]; for(int i=0;i<t;i++) { encode[i]=new encoding; encode[i]->data=new char[t]; } for(int i=0;i<t;i++) { for(int j=0;j<t;j++) { encode[i]->data[j]='a'; } } for(int i=0;i<n;i++) { if(p[i]->lchild==NULL&&p[i]->rchild==NULL) { q=p[i]; encode[k]->ch[0]=p[i]->ch[0]; while(q->parent!=NULL) { if(q->parent->lchild==q) encode[k]->data[k1]='0'; else encode[k]->data[k1]='1'; q=q->parent; k1=k1-1; } k+=1; k1=t-1; } } if((fp=fopen("CodeFile.txt","w"))==NULL) {cout<<"文件打開失敗!"<<endl;exit(0);} for(int i=0;i<t;i++) { encode[i]->ch[1]='\0'; fprintf(fp,"%s:",encode[i]->ch); for(int j=0;j<t;j++) { if(encode[i]->data[j]!='a') { fprintf(fp,"%c",encode[i]->data[j]); if(j==t-1) fprintf(fp,"\n"); } } } for(int i=0;i<c1;i++)//編碼 { for(int j=0;j<t;j++) { if(c[i]==encode[j]->ch[0]) for(int k2=0;k2<t;k2++) { if(encode[j]->data[k2]!='a') { c2[kk]=encode[j]->data[k2]; kk+=1; } } } } c2[kk]='\0'; fprintf(fp,"#題目所給英文編碼: %s",c2); cout<<"CodeFile文件寫入完成!"<<endl; fclose(fp); return kk; } int Readtxt(char *c) //讀取英文字符串 { int m=0; FILE *fp; cout<<"請輸入需要編碼的大寫英文:"<<endl; if((fp=fopen("ToBeTran.txt","w"))==NULL) {cout<<"文件打開失敗!";exit(0);} for(int i=0;;i++) { c[i]=cin.get(); if(c[i]=='\n') {c[i]='\0';break;} m+=1; } fprintf(fp,"%s",c); fclose(fp); cout<<"ToBeTran.txt文件寫入完成!"<<endl; return m; } void Initialization(huffman **p,node **p1,int &n,int &n1) //初始化 { cout<<"請輸入待插入英文字母個數:"<<endl; cin>>n; *p=Read(n); *p1=Crhuffman(*p,n,&n1); } void Encoding(int &c1,char **c,char **c2,int n,int n1,huffman *p) //編碼 { c1=Readtxt(*c); Code(p,n1,n,*c,c1,*c2); } void Decoding(node *p1,char *c) //譯碼 { int k=0,i=0; FILE *fp; node *p=p1; if((fp=fopen("TextFile.txt","w"))==NULL) {cout<<"文件打開失敗!";exit(0);} while(c[i]!='\0') { while(c[i]!='\0') { if(c[i]=='0') { p=p->lchild; if(p->lchild==NULL&&p->rchild==NULL) {i++;break;} } else { p=p->rchild; if(p->lchild==NULL&&p->rchild==NULL) {i++;break;} } i++; } fprintf(fp,"%s",p->ch);p=p1; } fclose(fp);cout<<"TextFile.txt文件寫入完成!"<<endl; } void Print() //打印 { FILE *fp,*fp1; char ch[1000]; if((fp=fopen("CodeFile.txt","r"))==NULL) {cout<<"文件打開失敗!"<<endl;exit(0);} if((fp1=fopen("CodePrint.txt","w"))==NULL) {cout<<"文件打開失敗!"<<endl;exit(0);} while(!feof(fp)) { fscanf(fp,"%s",ch); if(ch[0]=='#') { cout<<ch<<endl; fscanf(fp,"%s",ch); fprintf(fp1,"%s",ch); } cout<<ch<<endl; } fclose(fp); fclose(fp1); } void Tree_printing(node *p1) { FILE *fp; if((fp=fopen("TreePrint.txt","a"))==NULL) {cout<<"文件打開失敗!"<<endl;exit(0);} if(p1) { if(p1->ch[0]==NULL) { cout<<"空"<<p1->weight<<" "; fprintf(fp,"空%d\n",p1->weight); } else { cout<<p1->ch[0]<<p1->weight<<" "; fprintf(fp,"%c%d\n",p1->ch[0],p1->weight); } Tree_printing(p1->lchild); Tree_printing(p1->rchild); } } void homepage() { cout<<"****************************************************************************"<<endl; cout<<" 1.Initialization()&&Encoding() //初始化並編碼 "<<endl; cout<<" 2.Decoding() //譯碼 "<<endl; cout<<" 3.Print() //打印代碼文件 "<<endl; cout<<" 4.Tree_printing() //打印哈夫曼樹 "<<endl; cout<<" 5.Confirm exit //確認退出 "<<endl; cout<<"****************************************************************************"<<endl; cout<<"請選擇需要的操作:"<<endl; } void display() { int N;huffman *p,i=0; node *p1; encode1 *q; char *c,*c2; c=new char[100]; c2=new char[1000]; int n,n1,c1,n2; homepage(); cin>>N; while(1) { // system("cls"); if(N==1) { Initialization(&p,&p1,n,n1); Encoding(c1,&c,&c2,n,n1,p); i+=1; } else if(N==2) { if(i==0) {cout<<"操作不合法,請重新輸入!"<<endl;} else {Decoding(p1,c2);i+=1;} } else if(N==3) { if(i==0) {cout<<"操作不合法,請重新輸入!"<<endl;} else {cout<<" ";Print();i+=1;} } else if(N==4) { if(i==0) {cout<<"操作不合法,請重新輸入!"<<endl;} else {Tree_printing(p1);i+=1;cout<<endl;} } else if(N==5) exit(0); else printf("輸入不合法,請重新輸入!\n"); homepage(); cin>>N; } } int main() { display(); }
四:運行結果
具體編譯碼結果可以查看生成的編碼文件以及譯碼文件
五:總結
1.字母以字符形式寫入文件時會亂碼,不能用%c,可以改為%s整體寫入,注意字符數組末尾要加‘\0’轉為字符串。
2.將指針傳入函數,想改變指針的值即傳入的指針再調用完后改變,即如test(p)(main 函數里的),void test(int **p)(main函數外的),需要設二級指針,即指向指針的指針,指針也是變量,和普通變量一樣,只不過是地址類型,而且可以訪問指針所存地址里的東西。
3.結構體數組即每個元素都是結構體,指向結構體的二級指針即二級指針元素是一級指針,一級指針元素是結構體類型數據。