1. 程式人生 > >資料結構::如何實現哈夫曼樹

資料結構::如何實現哈夫曼樹

哈夫曼樹的定義】:

 哈夫曼樹又稱為最優二叉樹,它是加權路徑最短的二叉樹。

    不同於普通的二叉樹,它的每個節點都有相應的權值,當我們構建的時候最終離根節點遠的節點權重小,反之就比較大。


【哈夫曼樹的實現】:

1、利用的核心思想:貪心演算法

貪心演算法:是指在問題進行求解的時候,總是做出當前看起來最好的選擇。即就是說貪心演算法做出不是整體的最優解,而是某種意義上的區域性最優解。它不是對所有的問題都能得到整體最優解。

2、如何構建:

1)節點的設定:

     我們瞭解哈夫曼樹的定義後,我們知道就比普通的二叉樹多了一個權重,因此,我們可以這樣進行設計。(注意:我在這裡給出了父節點可以不給出父節點,因為此時的構建兩個孩子節點就足夠了,我這裡加上也就是為了稍稍的複習下三叉鏈)

template<class T>
struct HuffmanTreeNode
{
	T _weight;
	HuffmanTreeNode<T>* _left;
	HuffmanTreeNode<T>* _right;
	HuffmanTreeNode<T>* _parent;
	HuffmanTreeNode(const T& x)
		:_left(NULL)
		,_right(NULL)
		,_parent(NULL)
		,_weight(x)
	{}
//此處的過載是後面的構建要用到,讀者先不用看,到後面知道如何構建的時候,再回過頭來串一遍
	HuffmanTreeNode operator+(const HuffmanTreeNode& x)
	{
		return _weight+x._weight;
	}
};

     2)哈夫曼樹建構函式的實現

//建構函式
	HuffmanTree()
		:_root(NULL)
	{}
	HuffmanTree(T*a, size_t n,const T& invalue)
	{
		//因為在構建哈夫曼數的時候,比較的是兩個權重的大小
		//而權重的型別是Node*,所以要進行過載
		struct com
		{
			bool operator()(Node* l,Node* r)
			{
				return l->_weight < r->_weight;
			}
			
		};
		//利用堆來實現資料的儲存
		Heap<Node* ,com> hp;
		for(size_t i = 0; i<n; i++)
		{
			if(a[i] != invalue)
			hp.Push(new Node(a[i]));
		}
		//然後再進行構建哈夫曼樹
		//構建的最後,數組裡會只剩下一個元素
		while(hp.Size()>1)
		{
			//先取出堆頂的元素給左孩子
			Node* left = hp.Top();
			//出堆
			hp.Pop();
			//再取出堆頂的元素給右孩子
			Node* right = hp.Top();
			//出堆
			hp.Pop();
			//然後左右構建出一個父節點,即兩者的權重之和
			//(注意:此處需要進行過載+)
			Node* parent = new Node(left->_weight+right->_weight);
			//將節點要進行連線
			parent->_left = left;
			parent->_right = right;
			//下面的這兩條語句用於構建三叉鏈
			right->_parent = parent;
			left->_parent = parent;
			//要將構建完後的節點扔回來,因為它可能不是最小的
			hp.Push(parent);
		}
		//出迴圈後,陣列中的儲存就剩最後的一個節點,此節點就是根
		_root = hp.Top();
	}

      【說明】:

*這裡我寫的很詳細,我寫了兩個建構函式,一個是無參的,一個是通過陣列來進行構建。

           *我們在構建哈夫曼樹的時候,是從陣列中選取兩個權重小的數,因此我們這裡就會出現各種各樣的辦法

           方法一:首先想到的就是排序,如果我們利用排序的話,每次取出數之後,前面我說到每次取出兩個數進行處理後要將它們的父節點再扔回去,此時,當扔回去的時候,又要進行重新排序,所以利用陣列的話,代價會很大

           方法二:利用遍歷,每次遍歷找出最小的一個,重複,它的時間複雜度是O(n^2),效率比較低,所以不建議採取

           方法三:利用堆採用這種方法的時間複雜度是O(nlg(n)),效率會比較高,所以這裡我們採用這種方法

【說明】:此處我們要注意的一個點是,用堆的話,我們在堆裡要用什麼,怎麼放的問題?

                              我們如果在堆裡放節點的話就太大了(因為此處是結構體),應放節點的指標(來根據節點的權值                               選擇。):即就是這裡的Heap<Node*>hp

            *我這裡是用我自己實現的堆來進行實現的,讀者也可以用庫裡的STL 演算法中的幾個有關介面來進行實現

       3)解構函式的實現

           因為哈夫曼樹也是二叉樹,所以在析構的時候也要用到遞迴,和普通的二叉樹的析構一樣。

//解構函式
	~HuffmanTree()
	{
		if(_root)
			Destory(_root);
		_root = NULL;
	}
	void Destory(Node* root)
	{
		if(root == NULL)
			return;
		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}

【哈夫曼樹的應用】:

1、哈夫曼編碼: 

關於哈夫曼的應用,在哈夫曼樹構建好後,它的節點向左我們標記為0,向右我們標記為1,這樣每個節點到根節點都會有自己的一系列關於0,1的序列,次序列就是哈夫曼編碼。

2、經典應用:檔案壓縮

     此處我就先不展開了,具體的我在後面開一篇部落格來詳細的進行講述,如何進行檔案壓縮