Huffman對檔案編碼和解碼

jsjliuyun發表於2014-11-24

考核實驗中的一個,我也認為較為難的一個,其實也不是很難,只是有點複雜,只要分解成多個問題去解決就好了

比如你要知道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;
		}
	}
}


相關文章