【資料結構】哈夫曼樹的建立與基礎應用

洛斯馬裡發表於2020-12-04

根據給定的n個權值生成哈夫曼二叉樹,輸出赫夫曼編碼及進行譯碼

哈夫曼樹的結點的結構體

struct huffmannode//哈夫曼樹結點的結構體 
{
	int weight;//結點權重 
	
	int parent;//父結點 
	
	int leftchild;//左子樹 
	
	int rightchild;	//右子樹 
};

建樹

void creat_tree(huffmannode tree[], int w[],int n)//建哈夫曼樹 
{
	int node1 = 0, node2 = 0;
	
	for(int i=0;i<2*n-1;++i)//初始化結點 
	{
		tree[i].parent = -1;
		
		tree[i].leftchild = -1;
		
		tree[i].rightchild = -1;
	}
	
	for(int i=0;i<n;++i)//將輸入的結點值輸入哈夫曼樹 
	{
		tree[i].weight = w[i];
	}
	
	for(int k=n;k<2*n-1;++k)
	{
		select(tree,k,node1,node2);//找到最小結點,次小結點 
		
		tree[k].weight = tree[node1].weight + tree[node2].weight;//生成新結點,權重為兩結點之和 
		
		tree[node1].parent = k;//最小結點父結點為新結點 
		
		tree[node2].parent = k;//次小結點父結點為新結點 
		
		tree[k].leftchild = node1;//新結點左子樹為最小結點 
		
		tree[k].rightchild = node2;//新結點右子樹為次小結點 
	}
}

思路:將最開始輸入的結點放入待選擇結點列表,不斷選擇權重最小與權重次小的結點,用他們生成新的結點,並使新結點成為他們的父結點,再將新結點也放入待選擇的結點列表,直至新生成了n-1個結點,此時該父結點也為根結點,最後這2*n-1個結點就成為了哈夫曼樹。

建樹時使用的查詢函式

void select(huffmannode tree[],int k,int &node1,int &node2)//在尚未使用的結點中,找到兩個權重最小的結點 
{
	node1 = -1, node2 = -1;//初始化兩個沒有父結點的最小結點的位置 
	
	int minweight = 1e8;//初始化最小權重 
	
	for(int i=0;i<k;++i)
	{
		if(tree[i].parent==-1)//沒有父結點 
		{
			if(tree[i].weight<minweight)//小於最小權重 
			{
				minweight = tree[i].weight;//更新最小權重 
				
				node1 = i;//更新最小點位置 
			}
		}
	}
	
	minweight = 1e8;//初始化權重 
	
	for(int i=0;i<k;++i)
	{
		if(tree[i].parent==-1&&i!=node1)//沒有父結點且不是最小結點 
		{
			if(tree[i].weight<minweight)//更新次小結點 
			{
				minweight = tree[i].weight;
				
				node2 = i;
			}
		}
	}
}

思路:兩遍遍歷,第一遍找到未連線父結點且權重最小的點,將其標記為node1;第二遍,找到除了node1之外,未連線父結點且權重最小的點,將其標記為node2.

生成結點對應的哈夫曼編碼

void form_code(huffmannode tree[],int n)//生成哈夫曼編碼 
{
	int cur, parent, start;
	
	string huffcode;
	
	for(int i=0;i<n;++i)//遍歷n個輸入的結點 
	{
		cur = i;
		
		parent = tree[i].parent;
		
		while(parent!=-1)//當沒有到達根結點,向上尋找父結點 
		{
			if(tree[parent].leftchild==cur)//如果當前結點是左子樹 
			{
				huffcode = '0' + huffcode;//在哈夫曼編碼前加0 
			}
			else
			{
				huffcode = '1' + huffcode;//加1 
			}
			
			cur = parent;//更新當前結點 
			
			parent = tree[parent].parent;//更新父結點 
		}
		
		code[tree[i].weight] = huffcode;//將生成的哈夫曼編碼用map儲存 
		
		translate[huffcode] = ch[i];//將哈夫曼編碼對應的值儲存 
		
		huffcode.clear();//清空哈夫曼編碼 
	}
}

思路:分別以n個輸入的結點為起點,不斷向上尋找它的父結點,直到父結點到達根結點,如果對於父結點來講,當前結點是左子結點,則在已生成的哈夫曼編碼前加上0,如果是右子結點,則加上1。在生成完整編碼後用map容器儲存編碼。

完整程式碼

#include<iostream>
#include<cstdio>
#include<string>
#include<map>

using namespace std;

const int Size = 1e4+5;

struct huffmannode//哈夫曼樹結點的結構體 
{
	int weight;//結點權重 
	
	int parent;//父結點 
	
	int leftchild;//左子樹 
	
	int rightchild;	//右子樹 
};

map<int,string> code;//儲存值對應的哈夫曼編碼 

map<string,char> translate;//儲存哈夫曼編碼對應的值 

map<char,int> alp;

char ch[105];
	
int w[105];

void select(huffmannode tree[],int k,int &node1,int &node2)//在尚未使用的結點中,找到兩個權重最小的結點 
{
	node1 = -1, node2 = -1;//初始化兩個沒有父結點的最小結點的位置 
	
	int minweight = 1e8;//初始化最小權重 
	
	for(int i=0;i<k;++i)
	{
		if(tree[i].parent==-1)//沒有父結點 
		{
			if(tree[i].weight<minweight)//小於最小權重 
			{
				minweight = tree[i].weight;//更新最小權重 
				
				node1 = i;//更新最小點位置 
			}
		}
	}
	
	minweight = 1e8;//初始化權重 
	
	for(int i=0;i<k;++i)
	{
		if(tree[i].parent==-1&&i!=node1)//沒有父結點且不是最小結點 
		{
			if(tree[i].weight<minweight)//更新次小結點 
			{
				minweight = tree[i].weight;
				
				node2 = i;
			}
		}
	}
}

void creat_tree(huffmannode tree[], int w[],int n)//建哈夫曼樹 
{
	int node1 = 0, node2 = 0;
	
	for(int i=0;i<2*n-1;++i)//初始化結點 
	{
		tree[i].parent = -1;
		
		tree[i].leftchild = -1;
		
		tree[i].rightchild = -1;
	}
	
	for(int i=0;i<n;++i)//將輸入的結點值輸入哈夫曼樹 
	{
		tree[i].weight = w[i];
	}
	
	for(int k=n;k<2*n-1;++k)
	{
		select(tree,k,node1,node2);//找到最小結點,次小結點 
		
		tree[k].weight = tree[node1].weight + tree[node2].weight;//生成新結點,權重為兩結點之和 
		
		tree[node1].parent = k;//最小結點父結點為新結點 
		
		tree[node2].parent = k;//次小結點父結點為新結點 
		
		tree[k].leftchild = node1;//新結點左子樹為最小結點 
		
		tree[k].rightchild = node2;//新結點右子樹為次小結點 
	}
}

void form_code(huffmannode tree[],int n)//生成哈夫曼編碼 
{
	int cur, parent, start;
	
	string huffcode;
	
	for(int i=0;i<n;++i)//遍歷n個輸入的結點 
	{
		cur = i;
		
		parent = tree[i].parent;
		
		while(parent!=-1)//當沒有到達根結點,向上尋找父結點 
		{
			if(tree[parent].leftchild==cur)//如果當前結點是左子樹 
			{
				huffcode = '0' + huffcode;//在哈夫曼編碼前加0 
			}
			else
			{
				huffcode = '1' + huffcode;//加1 
			}
			
			cur = parent;//更新當前結點 
			
			parent = tree[parent].parent;//更新父結點 
		}
		
		code[tree[i].weight] = huffcode;//將生成的哈夫曼編碼用map儲存 
		
		translate[huffcode] = ch[i];//將哈夫曼編碼對應的值儲存 
		
		huffcode.clear();//清空哈夫曼編碼 
	}
}

void print_code(huffmannode tree[],int n)//輸出 
{
	cout << "得到的哈夫曼值為" << endl;
	
	for(int i=0;i<n;++i)
	{
		cout << ch[i] << ":" << tree[i].weight << "的哈夫曼值為" << code[tree[i].weight] << endl;
	}
}

void translate_code(string unknown)//翻譯編碼 
{
	string s;
	
	for(int i=0;i<unknown.size();++i)//讀入未知編碼 
	{
		s = s + unknown[i];
		
		if(translate[s]!=0)//已讀入部分成功匹配現有編碼 
		{
			cout << translate[s];//輸出對應值 
			
			s.clear();//清空已讀入部分 
		}
	}
	
	cout << endl;
}

void translate_string(string unknown)//編碼字串 
{	
	for(int i=0;i<unknown.size();++i)//讀入未知編碼 
	{
		cout << code[alp[unknown[i]]];
	}
	
	cout << endl;
}

int main()
{
	int n;
	
	cout << "請輸入結點的數量(解碼過程)" << endl;
	
	cin >> n; 
	
	cout << "請輸入結點各自的字元和值" << endl;
	
	for(int i=0;i<n;++i)
	{
		cin >> ch[i];
		
		cin >> w[i];
		
		alp[ch[i]] = w[i];
	}
	
	huffmannode *tree = new huffmannode[2*n-1];
	
	creat_tree(tree,w,n);
	
	form_code(tree,n);
	
	print_code(tree,n);
	
	string unknown;
	
	cout << "請輸入一段待翻譯的01字串(譯碼過程)" << endl; 
	
	cin >> unknown;
	
	cout << "翻譯結果為"  << endl; 
	
	translate_code(unknown);
	
	cout << "請輸入一段字串(編碼過程)" <<endl;
	
	cin >> unknown;
	
	cout << "轉碼結果為" << endl; 
	
	translate_string(unknown);
	
	return 0;	
}

相關文章