1. 程式人生 > >哈夫曼樹編碼-解碼(c++)

哈夫曼樹編碼-解碼(c++)

hello everybody!你們機智大氣的阿俊又回來了,最近事比較多,閒話少說,直接切入正題,聊聊如何給一篇全為英文字元的文章利用哈夫曼編碼得到每個字元的最優編碼,並完成解碼功能,注意,這次也是用檔案操作喲,今天可被二進位制檔案折磨慘了,不過搞懂後真好用,嗚嗚嗚,我該不會是個受虐狂叭。。。


需求分析

在這裡插入圖片描述
嘿嘿嘿,不瞞你說,這是我課設的題目

  • 注意一下,測試資料一定是所有的字元一定是要英文輸入法能打出來的,看看差別,中文問號【?】,英文問號【?】,一不留神就會搞混,唉~,我是不會告訴你我卡在這一個小時的。。。不然一直報錯我可不負責哦
  • 採用檔案輸入,儲存一開始也是件麻煩事,許多細節忽略了

模組分解

  • 1,建立字頻表(統計文字中每個字元出現的次數)
  • 2,建立哈夫曼樹,並求每個字元對應的編碼
  • 3,給文字編碼,以二進位制保存於檔案code.dat
  • 4,根據哈夫曼樹,將code.dat解碼,結果保存於recode.txt

1 數數字符出現個數唄

從前的我是讀入一個字元,查詢一下數組裡有沒有,沒有的話填入,次數加1,有的話直接次數加1,由於每次都得從頭找到尾,效率不高;現在的我,擁有武林祕籍–資料結構,自然採取更簡便的方法,想知道嗎?那請向下看

由於字元個數有限,所有英文字元不超過255,所以將字元的ASCII碼作為下標陣列值作為次數

,每讀一個值只要將其轉化為ASCII碼即可(類似希爾對映)

所以關鍵在於如何將字元轉化為ASCII碼,很簡單,只要將字元賦給一個整型變數,編譯器自動幫你轉化為ASCII碼。愛思考的小夥伴就會問“那ASCII碼轉化為字元咋辦?”,嘿嘿嘿,和之前步驟相似,將整型賦給一個字元變數,轉化完成,耶~~

瞧瞧可愛的程式碼

//統計每個字元出現次數:只有英文符號,否則報錯 
void Count_Character_Occur_Frequency()
{
	int cof[256];//儲存相應字元出現的次數,字元ASCII為下標。charater_occur_frequency 
	for(int i =
0; i < 256; i++)//初始化字元出現次數統計表 { cof[i] = 0; } //從原始檔按行讀取,並統計字元個數,由於字元個數有限,所以用字元的ASCII碼作為陣列下標,陣列值作為次數,類似雜湊對映 fstream inFile("source.txt",ios::in); if(!inFile)cout<<"source.txt 開啟失敗!"<<endl; int sum = 0;//總行數,記錄換行個數 string s;//存放一行 while(true) { getline(inFile,s); if(!inFile)break;//避免重複讀取最後一個字元 sum++; for(int i = 0; i < s.size(); i++) { int a = s[i];//cout<<"a:"<<a<<endl;中文會溢位 cof[a]++; //計數 } } inFile.close();//好習慣 int a = '\n';//換行符 cof[a] = sum; //換行符個數 //=======將所有出現的字元及其次數寫入檔案(類似全域性陣列)========= int n = 0;//計算出現字元總個數 for(int i = 0; i < 256; i++) { if(cof[i] != 0)n++; } fstream outFile("字頻表.txt",ios::out);//寫入若是無此檔案,系統自動建立 if(!outFile)cout<<"字頻表.txt 開啟失敗!"<<endl; outFile<<n<<endl;//寫入字元總個數 //列印除錯&&寫入檔案 for(int i = 0; i < 256; i++) { if(cof[i] != 0) { char ch = i - '\0'; // cout<<"i: "<<i<<" 字元:"<<ch<<" cof[i]: "<<cof[i]<<endl; outFile<<i<<" "<<cof[i]<<endl;//寫入檔案 } } outFile.close(); }

2 建棵哈夫曼樹玩玩

演算法思想很簡單,把n個帶權節點看成n棵樹,每次選取根節點權值最小的兩棵樹構建出一顆新二叉樹,加入樹的集合中,新二叉樹的權值為左右子樹根節點權值之和,同時刪除被選中的兩棵二叉樹,此時變成n-1棵樹。重複以上過程知道僅有一顆樹存在,那棵樹就是哈夫曼樹

具體演算法步驟我就不寫了,最好是能跟著演算法走一遍,體會一下

  • 應我們老師要求:兩個節點權值不同,左小右大 ;相同,下標小者在左 (大家就隨意叭,不一定要這樣)
  • 同時尋找最小值,次小值需留個心眼
  • 利用棧從葉子出發讀出每個字元的編碼

這步依賴於字頻表的建立

//建立哈夫曼樹 
void CreateHT()
{
	HuffmanTree HTree;
	fstream inFile("字頻表.txt",ios::in);
	if(!inFile)cout<<"字頻表.txt 開啟失敗!"<<endl;
	
	int n;//節點個數
	inFile>>n;
	
	HTree = (HTNode*)malloc(2*n*sizeof(HTNode));//哈夫曼構造,共需2n-1個,0號單元不用
	for(int i = 1; i < 2*n; i++)//初始化 1
	{
		HTree[i].ascii = HTree[i].lchild = HTree[i].parent = HTree[i].rchild = HTree[i].weight = 0;//0號單元無用 
	} 
	for(int i = 1; i <= n; i++)//初始化 2,從檔案讀取ASCII碼及相應權值 
	{
		inFile>>HTree[i].ascii>>HTree[i].weight;
	} 
	inFile.close();

	for(int i = n+1; i < 2*n; i++)//從n+1開始,進行n-1次計算 
	{
		//==============尋找最小,次小值,記錄其下標 =========
		int min1 = MIN1,min2 = MIN2;
		int index1 = 0,index2 = 0;
		
		for(int j = 1; j < i; j++)//i是即將要被填入的根節點 
		{
			if(HTree[j].parent == 0)//雙親為0表示尚待操作 
			{
				if(min1 > HTree[j].weight)
				{
					min2 = min1;//先賦給次小值 
					index2 = index1;
					min1 = HTree[j].weight;
					index1 = j;
				}
				else if(min2 > HTree[j].weight)
				{
					min2 = HTree[j].weight;
					index2 = j;
				}
			}
		}
		//==============五處狀態更新==================================
		HTree[i].weight = HTree[index1].weight + HTree[index2].weight;//雙親權值更新 
		HTree[index1].parent = HTree[index2].parent = i;//孩子的雙親節點更新
		if(HTree[index1].weight < HTree[index2].weight)//1,兩個節點權值不同,左小右大 ;相同,下標小者在左 
		{
			HTree[i].lchild = index1;//下標賦值 
			HTree[i].rchild = index2;
		}
		else if(HTree[index1].weight > HTree[index2].weight)
		{
			HTree[i].lchild = index2;
			HTree[i].rchild = index1;
		}
		else 
		{
			if(index1 < index2)
			{
				HTree[i].lchild = index1;
				HTree[i].rchild = index2;
			}
			else
			{
				HTree[i].lchild = index2;
				HTree[i].rchild = index1;
			}
		}
	} 
	//====================寫入檔案=====================
	fstream outFile("哈夫曼樹.txt",ios::out);
	if(!outFile)cout<<"哈夫曼樹.txt 無法開啟!"<<endl;
	outFile<<n<<endl;//節點個數 
	for(int i = 1; i < 2*n; i++)//列印輸出除錯 
	{
			outFile<<" "<<HTree[i].ascii <<" "<<HTree[i].weight<<" "<<HTree[i].parent<<" "<<HTree[i].lchild<<" "<<HTree[i].rchild<<endl;	
	} 
	outFile.close();
	
	//==========建立編碼表,寫入字元,權值,編碼==================
	outFile.open("哈夫曼編碼表.txt",ios::out);
	if(!outFile)cout<<"哈夫曼編碼表.txt 開啟失敗!"<<endl;
	//利用棧從葉子出發讀取每個字元的編碼,在寫入檔案 
	stack<char> code;//儲存編碼 
	for(int i = 1; i <= n; i++)//對n個字元分別求編碼 
	{
		int j = i;
		do{
			int p = HTree[j].parent;
			if(p != 0)
			{
				int l,r;
				l = HTree[p].lchild;
				r = HTree[p].rchild;
				if(j == l)code.push('0');
				if(j == r)code.push('1');
				j = p;
			}
		}while(HTree[j].parent != 0);
		
		outFile<<HTree[i].ascii<<" "<<HTree[i].weight<<" ";//寫入字元,權值 
		while(!code.empty())
		{
			outFile<<code.top();//寫入編碼 
			code.pop();
		}outFile<<endl;
	} 
	outFile.close();
} 

給文字編編碼

萬事具備,只欠東風咯,前期工具已就位,現在拿來實戰,看看好不好用。

根據咱們針對原文字source.txt建立的字頻表,在此基礎上建立哈夫曼樹並求出每個字元的編碼,現在求出整個文字的編碼

其實思想和之前的提到的雜湊對映一致,字元數有限,所以把每個字元的ASCII碼當字串陣列的下標,字串存相應編碼,對文字編碼時,每讀入一個字元,對映取值就OK啦

話不多說,直接上菜,注意二進位制檔案寫入

void Encode()
{
	fstream inFile("哈夫曼編碼表.txt",ios::in);
	if(!inFile)cout<< "哈夫曼編碼表.txt"<<endl;
	string s,codeList[256];//將編碼表從檔案讀入該陣列中,ASCII碼為下標,類似雜湊對映 
	int ch,w;
	while(true)
	{
		inFile>>ch>>w>>s;
		if(!inFile)break;
//		cout<<" ch:"<<ch<<" w:"<<w<<" s:"<<s<<endl;
		codeList[ch] = s; 
	}
	inFile.close();
	
	inFile.open("source.txt",ios::in);
	if(!inFile)cout<<"source.txt 開啟失敗!"<<endl;
	
	fstream outFile("code.dat",ios::out|ios::binary);
	if(!outFile)cout<<"code.dat開啟失敗!"<<endl;
	string s2;
	while(true)
	{
		getline(inFile,s);
		if(!inFile)break;
		int a;
		for(int i = 0; i < s.size(); i++)
		{
			a = s[i];//轉化為ASCII碼 
			int j; 
			for(j = 0; j < codeList[a].size();j++)
			{	
				s2 = codeList[a];
				code[j] = s2[j];
			}code[j]='\0';//!!!關鍵的一句 
			outFile.write((char*)code,20*sizeof(char));
		}
		a = '\n';//換行符手動加入,因為getline讀不出來
		for(int j = 0; j < codeList[a].size();j++)
		{
			code[j]  = (codeList[a])[j];
		}
		outFile.write((char*)code,20*sizeof(char));
	}
	inFile.close();
	outFile.close(); 
}

有趣的解碼

至此咱們已經可以對一篇文章編碼啦,編碼也是一種加密方式哈,沒人能看得懂,嘿嘿嘿,偷偷傳遞一些訊息就不會被識破啦(不過僅限不太重要的事,重要的得要高階加密演算法啦)。有加密,就會有人解密,這是最吸引人的地方,看著一堆雜亂無章的二進位制串在你的神奇程式下還原出一片文辭優美的文章,想想都令人興奮呢,那還等什麼,快來叭~

解碼原理相對容易,碰到0向左走,遇到1向右走,如果該點是葉子就找到了字元,繼續從頭開始處理0,1串

粗暴一點,沒法做個安靜的美男子咯

//解碼 
void Decode()
{
//==============讀入哈夫曼樹====================
	fstream inFile("哈夫曼樹.txt",ios::in);
	if(!inFile)cout<<"哈夫曼樹.txt 開啟失敗!"<<endl; 
	int n;
	inFile>>n;//n個節點
	HuffmanTree HTree;
	HTree = (HTNode*)malloc(2*n*sizeof(HTNode));
	for(int i = 1; i < 2*n; i++)
	{
		inFile>>HTree[i].ascii >>HTree[i].weight>>HTree[i].parent>>HTree[i].lchild>>HTree[i].rchild;	
	}
	inFile.close();
	//========處理編碼資訊=====================
	inFile.open("code.dat",ios::in|ios::binary);
	if(!inFile)cout<<"code.dat 開啟失敗!"<<endl;
	
	fstream outFile("recode.txt",ios::out);//儲存解碼結果
	if(!outFile)cout<<"recode.txt 開啟失敗!"<<endl;
	//=================解碼開始===============================
	char ch;
	int root = 2*n - 1;
	while(true)
	{
		inFile.read((char*)code,20*sizeof(char));
		if(!inFile)break;
	//	cout<<"ch: "<<ch<<" root: "<<root<<endl;	
		for(int i = 0; code[i] != '\0'; i++)
		{//cout<<ch;
			ch = code[i];
			if(ch == '0')root = HTree[root].lchild;
			else if(ch == '1')root = HTree[root].rchild;
		
			if(HTree[root].lchild == 0)
			{//cout<<endl;
				char cht = HTree[root].ascii;
				outFile<<cht;
				root = 2*n - 1;
			} 
		}
	}
	outFile.close();
}

小收穫

tips:

  • 1,二進位制檔案讀寫(無法使用string,只可用字元陣列)
  • 2,ASCII碼和字元轉換(自動轉換,想變成什麼型別就把值賦給什麼型別)
  • 3,哈夫曼演算法(有限情況下用陣列實現,而不是連結串列)
  • 4,雜湊思想的妙用(計算字頻;編碼使用)

元旦長假第一天,祝大家玩得開心,耍的愉快,我呢,繼續滾去學習吧,再見各位親~

哦哦哦,差點完了,原始碼耶

完整code

為保持原汁原味及錯誤示範,我不刪除多餘程式碼,各位看前文足夠啦

/*時間:2018.12.30
*作者:吳揚俊
*內容:給出任意一篇全英文字元的文字,求出其最優編碼,並能解碼
思路:
1,建立字頻表(統計文字中每個字元出現的次數)
2,建立哈夫曼樹,並求每個字元對應的編碼
3,給文字編碼,以二進位制保存於檔案code.dat
4,根據哈夫曼樹,將code.dat解碼,結果保存於recode.txt 
tips:
1,二進位制檔案讀寫(無法使用string)
2,ASCII碼和字元轉換
3,哈夫曼演算法
4,雜湊思想的妙用(計算字頻;編碼使用) 

資料結構
typedef struct
{
	int ascii,weight,parent,lchild,rchild;//哈夫曼樹,它們依次表示:字元的ASCII碼,雙親,左孩子,右孩子 
}HTNode,*HuffmanTree;

演算法
假設有n個字元,申請2n個空間,0號不用 ,HTree陣列首地址 
1,初始化1,所有成員賦0;初始化2,讀入字元及相應的權值
2,令下個根節點j = n+1,在parent=0的點中挑選出最小值,次小值分別記錄其下標index1,index2;
3,最小值和次小值的parent=j;  
*/
#include<iostream>
using namespace std;
#include<stdlib.h> 
#include<fstream>
#include<string>
#include<stack>

#define MIN1 0x1fffffff
#define MIN2 0x2fffffff


//Attention:只可以識別英文輸入法下的所有字元,中文打出來的‘?’都不行 
char code[20];//二進位制讀寫準備 

typedef struct
{
	int ascii,weight,parent,lchild,rchild;//哈夫曼樹,它們一次表示:字元的ASCII碼,雙親,左孩子,右孩子 
}HTNode,*HuffmanTree;

//統計每個字元出現次數:只有英文符號,否則報錯 
void Count_Character_Occur_Frequency()
{
	int cof[256];//儲存相應字元出現的次數,字元ASCII為下標。charater_occur_frequency 
	for(int i = 0; i < 256; i++)//初始化字元出現次數統計表 
	{
		cof[i] = 0;
	}
	//從原始檔按行讀取,並統計字元個數,由於字元個數有限,所以用字元的ASCII碼作為陣列下標,陣列值作為次數,類似雜湊對映 
	fstream inFile("source.txt",ios::in); 
	if(!inFile)cout<<"source.txt 開啟失敗!"<<endl;
	
	int sum = 0;//總行數,記錄換行個數 
	string s;//存放一行 
	while(true)
	{
		getline(inFile,s);
		if(!inFile)break;//避免重複讀取最後一個字元 
		sum++;
		for(int i = 
            
           

相關推薦

編碼-解碼c++

hello everybody!你們機智大氣的阿俊又回來了,最近事比較多,閒話少說,直接切入正題,聊聊如何給一篇全為英文字元的文章利用哈夫曼編碼得到每個字元的最優編碼,並完成解碼功能,注意,這次也是用檔案操作喲,今天可被二進位制檔案折磨慘了,不過搞懂後真好用,嗚嗚嗚,我該不會是個受虐狂叭

利用編碼解碼

哈夫曼(Haffman)樹(最優樹) 定義: 給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。 構造過程: 以 1,7,3,4,9,8為例: 第

編碼解碼實例(C)

sta nod 輸入 sign 赫夫曼 spa 字符數組 ++ es2017 //HuffmanTree.h #include <stdlib.h> #include <stdio.h> #include <string.h> #def

文件壓縮——編碼

結構體 splay 空間 構建 葉子 ESS rate char 底層 何謂哈夫曼樹?——   百度百科:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短

詳細講解帶例題和C語言程式碼實現——全註釋

** 哈夫曼樹詳細講解(帶例題和C語言程式碼實現——全註釋) ** 定義 哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的 路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點

20172303 2018-2019-1《程式設計與資料結構》編碼解碼

20172303 2018-2019-1《程式設計與資料結構》哈夫曼樹編碼與解碼 哈夫曼樹簡介 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

20172303 2018-2019-1《程序設計與數據結構》編碼解碼

exce eat temp 基礎 第一個 最小 charat 轉換 except 20172303 2018-2019-1《程序設計與數據結構》哈夫曼樹編碼與解碼 哈夫曼樹簡介 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最

解碼

問題 B: DS_6.14 給定報文,哈弗曼編碼、譯碼(by Yan) 時間限制: 20 Sec 記憶體限制: 256 MB 提交: 303 解決: 218 [提交][狀態][討論版] 題目描述 已知某段通訊報文內容,對該報文進行哈弗曼編碼,並計算平均碼長。

轉載:的構造和編碼C++代碼實現

作者 pos blank 字符 element start man null == 作者:qiqifanqi 原文:http://blog.csdn.net/qiqifanqi/article/details/6038822 #include<stdio.h>

資料結構——的實現以及編碼C語言實現

1、問題描述       利用哈夫曼編碼進行通訊可以大大提高通道利用率,縮簡訊息傳輸時間,降低傳輸成本。構造哈夫曼樹時,首先將由n個字 符形成的n個葉子結點存放到陣列HuffNode的前n個分量中,然後根據哈夫曼方法的基本思想,不斷將兩個較小的子樹合併為一個

資料結構之二叉應用編碼實現C++

一、哈夫曼樹1.書上用的是靜態連結串列實現,本文中的哈夫曼樹用 排序連結串列 實現;2.實現了從 字元頻率統計、構建權值集合、建立哈夫曼樹、生成哈夫曼編碼,最後對 給定字串的編碼、解碼功能。3.使用到的 “SortedList.h”標頭檔案,在上篇博文:資料結構之排序單鏈表。

編碼C語言程式碼實現

在一般的資料結構的書中,樹的那章後面,著者一般都會介紹一下哈夫曼(HUFFMAN)樹和哈夫曼編碼。哈夫曼編碼是哈夫曼樹的一個應用。哈夫曼編碼應用廣泛,如 JPEG中就應用了哈夫曼編碼。 首先介紹什麼是哈夫曼樹。哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂

#資料結構與演算法學習筆記#PTA17:編碼 Huffman Tree & Huffman CodeC/C++

2018.5.16 最近一段時間忙於實驗室各種專案和輔導員的各種雜活,間隔了半周沒有耐下心學習。導師最近接了一個要PK京東方的專案讓我來做總負責,確實是很驚喜了。責任心告訴我不能把工作做水了,但是還是嘗試把實權移交給師兄們比較好。 這道題可以說是樹這塊的壓軸題了,無論是程

C++優先隊列的使用

name sub pan main 道理 輸出 tor 數據 排序。 給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。 構造 假設有n個權

編碼解碼

哈夫曼樹,又稱最優樹,是帶權路徑最小的樹。 基本概念: 節點間的路徑長度:兩個節點間所包含的邊的數目。 樹的路徑長度:從根到樹中任意節點的路徑長度之和。 權:將節點賦予一定的量值,該量值成為權。 樹的帶權路徑長度:樹中所有葉子結點的帶權路徑長度。 哈夫曼演算法:給定一個儲存權值的陣列,求

的建立與編碼解碼

哈夫曼樹 哈夫曼樹即最優二叉樹,演算法如下: (1)在當前未使用結點中找出兩個權重最小的作為左右孩子,計算出新的根結點 (2)新的根結點繼續參與過程(1),直至所有結點連線為一棵樹 如下圖,symbol為具體字元,Frequency為出現頻率(權重) 特點:只有度數為

最小堆實現的構造及編碼解碼

      以下程式的演算法思想主要來自於浙江大學陳越老師主編的資料結構一書。最大堆(最小堆思想差不多)(之後會寫一篇部落格介紹),這裡主要講講哈夫曼樹的定義及實現。 Huffman Tree 相關概念: 結點的路徑長度:從根結點到該結點的路徑上分支的數

Java理解實現以其編碼解碼

哈夫曼樹以其編碼解碼 要求: 1.從終端讀入字符集大小為n(即字元的個數),逐一輸入n個字元和相應的n個權值(即字元出現的頻度),建立哈夫曼樹,進行編碼並且輸出。 將它存於檔案hfmtree中(選做)。 2.利用已建好的哈夫曼編碼檔案hfmtree,對鍵盤輸入的正文進行譯碼。輸出字元正文

編碼解碼

#include<stdio.h> #include<stdlib.h> #include<iostream> #include<string> using namespace std; #define MAXSIZE 30

C++構建,並輸出編碼

Huffman tree //輸出Huffman編碼 本程式實現瞭如何將一串字串輸出為Huffman編碼 VER || 1.0 DATE || 15/11/2017 AUTHER || WUD