Huffman對檔案編碼和解碼
考核實驗中的一個,我也認為較為難的一個,其實也不是很難,只是有點複雜,只要分解成多個問題去解決就好了
比如你要知道Huffman是怎樣對檔案進行編碼和解碼的
然後需要知道怎麼去建Huffman二叉樹,建好了Huffman樹 然後就是對其進行編碼 最後是解碼
只要把每個過程弄清楚了,用幾天時間(對本科生而言)寫出來應該還是有可能的
下面的程式碼是某位同學寫的,經過其本人允許我決定貼到我的部落格中供大家學習,但是如果按照原始碼來演示的話,你懂得……
期中要編碼的txt檔案必須放在.cpp原始檔同個資料夾下,所以大家在寫的過程中要注意。。。
當然,這個程式碼的漏洞也有很多,歡迎大家來吐槽。。。
/*
功能:Huffman對檔案進行編碼和解碼
時間:2014-11-24
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//結點結構體
typedef struct Node
{
unsigned char value; //結點值
int frequency; //結點頻數
struct Node *Lchild; //左兒子
struct Node *Rchild; //右兒子
};
//編碼字元結構體
typedef struct
{
unsigned char value; //字元值
char code[100]; //字元編碼
int frequency; // 字元頻數
int codelen; //字元編碼長度
}HuffmanCode;
struct Node *root; //哈弗曼樹根節點
//讀取檔案統計各個字母出現頻率,返回字元種數
//filename為檔名,*node為用於儲存資料的陣列
int count(char filename[],HuffmanCode *node)
{
FILE *fp=fopen(filename,"rb"); //開啟檔案
unsigned char now; //讀取的當前字元
int num = 0; //字元種數
fread(&now, sizeof(unsigned char), 1, fp); //讀取字元
while(!feof(fp)) //未讀到檔案尾
{
int i;
//重複字元
for (i = 0; i < num; i++)
{
if (now == node[i].value)
{
node[i].frequency++;
break;
}
}
//未重複字元
if (i==num)
{
node[i].value = now;
node[i].frequency=1;
num++;
}
fread(&now, sizeof(unsigned char), 1, fp); //讀取字元
}
fclose(fp);
return num;
}
//構造Huffman樹
//*node為用於儲存資料的陣列,n為字元種數
struct Node* buildHuffman(HuffmanCode *node, int n)
{
struct Node **arr,*temp; //arr為造數用陣列,temp為最終返回的樹
arr = (struct Node**)malloc(n*sizeof(struct Node));
//初始化陣列
for (int i = 0; i < n; i++)
{
arr[i] = (struct Node*)malloc(sizeof(struct Node));
arr[i]->value = node[i].value;
arr[i]->frequency = node[i].frequency;
arr[i]->Lchild = arr[i]->Rchild = NULL;
}
//建樹
for (int i = 0; i < n-1; i++)
{
int m1=-1, m2; //最小的兩個權值在陣列中下標
//將m1,m2指向頭兩棵樹
for (int j = 0; j < n; j++)
{
if (m1 == -1 && arr[j] != NULL)
{
m1 = j;
continue;
}
if (arr[j] != NULL)
{
m2 = j;
break;
}
}
//比較權值大小,找到最小的兩個
for (int j = m2; j < n; j++)
{
if (arr[j]!=NULL && arr[j]->frequency < arr[m1]->frequency)
{
m2 = m1;
m1 = j;
}
else if (arr[j] != NULL && arr[j]->frequency < arr[m2]->frequency)
m2 = j;
}
//將兩個權值最小的構成新樹
temp = (struct Node*)malloc(sizeof(struct Node));
temp->Lchild = arr[m1];
temp->Rchild = arr[m2];
temp->frequency = arr[m1]->frequency + arr[m2]->frequency;
temp->value = NULL;
//將新樹加入陣列
arr[m1] = temp;
arr[m2] = NULL;
}
free(arr);
return temp; //最終temp為樹根
}
//Huffman編碼
//*node為根節點,len為當前編碼長度,*arr為用於儲存資料的陣列,num為字元種數
void codeByHuffman(struct Node *node,int len,HuffmanCode *arr,int num)
{
static char code[100]; //編碼
//左兒子不為空,編碼為0
if (node->Lchild != NULL)
{
code[len] = '0';
code[len + 1] = '\0';
codeByHuffman(node->Lchild, len+1,arr,num); //對左兒子呼叫此函式
}
//右兒子不為空,編碼為1
if (node->Rchild != NULL)
{
code[len] = '1';
code[len + 1] = '\0';
codeByHuffman(node->Rchild, len + 1, arr, num); //對右兒子呼叫此函式
}
//葉節點
else
{
for (int i = 0; i < num; i++)
{
//將編碼複製給陣列裡的元素
if (arr[i].value == node->value)
{
strcpy(arr[i].code, code);
arr[i].codelen = 0;
for (int j = 0; arr[i].code[j] != '\0'; j++)
arr[i].codelen++;
return;
}
}
}
}
//將檔案內容轉為Huffman編碼
//sourceFile為原檔名,*arr為用於儲存資料的陣列,n為字元種數,aimFile為目標檔名
void writeCode(char sourceFile[],HuffmanCode *arr,int n,char aimFile[])
{
FILE *fSource, *fAim; //原檔案以及目標檔案
unsigned char now;//讀取的當前字元
unsigned char save = 0; //每次儲存的一個位元組長度
int len = 0; //每次儲存的一個位元組已存了多少長度
int totalLen = 0; //檔案編碼總長度
int last; //最後一次寫入時的位數
//總位元組長度
for (int i = 0; i < n; i++)
totalLen = totalLen + arr[i].codelen*arr[i].frequency;
//計算最後一次寫入多少位
last = totalLen % 8;
fSource = fopen(sourceFile, "rb");
fAim = fopen(aimFile, "wb");
//先將huffman編碼輸進檔案,以">"作為識別符號 用於譯碼
fwrite("->",1,2,fAim);
fwrite(&n, sizeof(int), 1, fAim); //寫入字元種數
fwrite(&last, sizeof(int), 1, fAim); //寫入最後一次寫入的位數
//寫入個字元值和字元頻數
for (int i = 0; i < n; i++)
{
fwrite(&arr[i].value, sizeof(unsigned char), 1, fAim);
fwrite(&arr[i].frequency, sizeof(int), 1, fAim);
}
fread(&now, sizeof(unsigned char), 1, fSource); //讀取原始檔字元
while (!feof(fSource))
{
for (int i = 0; 1; i++)
{
if (now == arr[i].value)
{
for(int j=0;j<arr[i].codelen;j++)
{
//按位操作儲存編碼
if(len!=0)
save=save<<1;
save=save|(arr[i].code[j]-'0');
len++;
//一個位元組已存滿,寫入檔案
if(len==8)
{
fwrite(&save,sizeof(unsigned char),1,fAim);
save=0;
len=0;
}
}
break;
}
}
fread(&now, sizeof(unsigned char), 1, fSource); //讀取字元
}
//最後一次可能沒寫滿一個位元組也要寫入檔案
if(len!=0)
fwrite(&save,sizeof(unsigned char),1,fAim);
fclose(fSource);
fclose(fAim);
}
//譯碼
//sourceFile為原檔名,*arr為用於儲存資料的陣列,aimFile為目標檔名
void writeDecode(char sourceFile[], HuffmanCode *arr, char aimFile[])
{
FILE *fSource, *fAim; //原檔案以及目標檔案
int n; //字元種數
int last=0; //最後一次讀取的位數
unsigned char now;//讀取的當前字元
fSource = fopen(sourceFile, "rb");
//確認是否以此程式碼編碼的Huffman
for (int i = 0; i < 2;i++)
{
fread(&now, sizeof(char), 1, fSource); //讀取字元
if (i == 0)
{
if (now == '-')
continue;
else
{
printf("原檔案huffman編碼格式不正確\n");
fclose(fSource);
return;
}
}
if (now == '>')
break;
else
{
printf("原檔案huffman編碼格式不正確\n");
fclose(fSource);
return;
}
}
//
fread(&n, sizeof(int), 1, fSource); //字元種數
fread(&last, sizeof(int), 1, fSource); //最後一次讀的位數
for (int i = 0; i < n; i++)
{
fread(&arr[i].value, sizeof(unsigned char), 1, fSource); //字元
fread(&arr[i].frequency, sizeof(int), 1, fSource);//字元頻數
}
struct Node *root = buildHuffman(arr, n); //建樹
codeByHuffman(root, 0, arr, n);
struct Node *pNow = root; //當前結點
unsigned char temp; //每次讀1個位元組
fAim = fopen(aimFile, "wb");
fread(&temp, sizeof(unsigned char), 1, fSource);
while (!feof(fSource))
{
unsigned char ifLast; //用於判斷是否讀到檔案末尾,方便讀取固定位數
fread(&ifLast, sizeof(unsigned char), 1, fSource);
int i;
if (feof(fSource))
i = last-1;
else
i = 7;
for (; i >= 0; i--)
{
if ((temp>>i & 1)==0)
pNow = pNow->Lchild;
else
pNow = pNow->Rchild;
if (pNow->Lchild == NULL && pNow->Rchild == NULL)
{
fwrite(&pNow->value, sizeof(unsigned char), 1, fAim);
pNow = root;
}
}
temp=ifLast;
}
fclose(fSource);
fclose(fAim);
}
void main()
{
while (1)
{
HuffmanCode node[2000];
int num; //字元種數
char Sfilename[100] = ""; //原檔名
char Afilename[100] = "";//輸出檔名
char choose[3] = "";
printf("請選擇:\n");
printf("\t1.編碼\n");
printf("\t2.譯碼\n");
printf("\t3.退出\n");
scanf("%s", choose);
fflush(stdin);
if (!strcmp(choose, "1"))
{
while (1)
{
printf("請輸入你想要編碼的檔名:\n");
scanf("%s", Sfilename);
fflush(stdin);
FILE *fp = fopen(Sfilename, "rb");
if (fp == NULL)
{
printf("未找到檔案.\n");
continue;
}
else
{
fclose(fp);
break;
}
}
printf("請輸入編碼後的檔名:\n");
scanf("%s", Afilename);
fflush(stdin);
num = count(Sfilename, node); //統計
root = buildHuffman(node, num); //建樹
codeByHuffman(root, 0, node, num);//各個字元的Huffman編碼
writeCode(Sfilename, node, num, Afilename); //輸出Huffman編碼檔案
printf("編碼完成!\n\n");
}
else if (!strcmp(choose, "2"))
{
while (1)
{
printf("請輸入你想要譯碼的檔名:\n");
scanf("%s", Sfilename);
fflush(stdin);
FILE *fp = fopen(Sfilename, "rb");
if (fp == NULL)
{
printf("未找到檔案.\n");
continue;
}
else
{
fclose(fp);
break;
}
}
printf("請輸入譯碼後的檔名:\n");
scanf("%s", Afilename);
fflush(stdin);
writeDecode(Sfilename, node, Afilename); //譯碼並寫檔案
printf("譯碼完成!\n\n");
}
else if (!strcmp(choose, "3"))
return;
else
{
printf("輸入有誤!\n");
continue;
}
}
}
相關文章
- Huffman編碼m檔案分析
- 【資料結構與演算法】Huffman樹&&Huffman編碼(附完整原始碼)資料結構演算法原始碼
- 檢測檔案編碼,轉換檔案編碼
- Linux下檢視檔案編碼,檔案編碼格式轉換和檔名編碼Linux
- [java IO流]之編碼和解碼Java
- Dubbo中編碼和解碼的解析
- ==和is的區別 以及編碼和解碼
- python中字串的編碼和解碼Python字串
- 前端檔案編碼方式前端
- 貪心演算法——Huffman 壓縮編碼的實現演算法
- 簡述小資料池,編碼和解碼
- 【興百放】Asp.Net 編碼和解碼ASP.NET
- java 猜測 檔案編碼Java
- 文字檔案的編碼格式
- 批量修改檔案的編碼
- iconv更改檔案編碼
- js encode64編碼和解碼程式碼例項JS
- Java 8中的Base64編碼和解碼Java
- 結合例項學習|字元編碼和解碼字元
- Windows轉到linux中,檔案亂碼,檔案編碼轉換WindowsLinux
- Linux 檔案編碼大挪移Linux
- 字元編碼與檔案處理字元
- 如何修改檔案的編碼格式
- eclipse預設檔案編碼Eclipse
- JPEG檔案編/解碼詳解 .
- PHP安全的URL字串base64編碼和解碼PHP字串
- 如何在 Linux 上用密碼加密和解密檔案Linux密碼加密解密
- js實現的字串簡單編碼和解碼程式碼例項JS字串
- android Java BASE64編碼和解碼一:基礎AndroidJava
- PDF檔案有限制密碼,該如何編輯檔案?密碼
- 轉換Linux 檔案編碼方式Linux
- iOS文字檔案的編碼檢測iOS
- Delphi編碼標準——檔案命名 (轉)
- DBeaver如何調整sql檔案編碼SQL
- GC機制+字元編碼+檔案操作GC字元
- js自定義實現的簡單編碼和解碼程式碼例項JS
- 文字編碼轉換工具iconv 附批量轉換檔案編碼命令
- java安全編碼指南之:檔案IO操作Java