Task A2 哈夫曼樹的應用

YunC發表於2024-11-25

【題目描述】 PTA(資料結構與演算法題目集 7-29)
農夫要修理牧場的一段柵欄,他測量了柵欄,發現需要 N 塊木頭,每塊木頭長度為整數 Li個長度單位,於是他購買了一條很長的、能鋸成 N 塊的木頭,即該木頭的長度是 Li 的總和。但是農夫自己沒有鋸子,請人鋸木的酬金跟這段木頭的長度成正比。為簡單起見,不妨就設酬金等於所鋸木頭的長度。例如,要將長度為 20 的木頭鋸成長度為 8、 7 和 5 的三段,第一次鋸木頭花費20,將木頭鋸成 12 和 8;第二次鋸木頭花費 12,將長度為 12 的木頭鋸成 7 和 5,總花費為 32。如果第一次將木頭鋸成 15 和 5,則第二次鋸木頭花費 15,總花費為 35(大於 32)。請編寫程式幫助農夫計算將木頭鋸成 N 塊的最少花費。

輸入格式
第一行為正整數N≤104,表示將木頭鋸成N塊,第二行給出N個正整數,表示每段木塊的長度

輸出格式
一個正整數,即將木頭鋸成N塊的最小花費

題目分析
哈夫曼編碼通常用於資料壓縮,也可以用來解決類似的問題,尤其是涉及合併成本最小化時。對於這個問題我們可以使用哈夫曼樹的思想來解決。
演算法過程:

  1. 初始化:將所有的木頭放入一個陣列中
  2. 合併:每次從堆中取出兩個最小的木頭,建立一個新的結點,其值為兩個木頭長度之和,並將結點放回陣列中。
  3. 重複:重複上述步驟,直到只剩下一個結點。
  4. 計算:在每次合併時累加合併成本,得到總最小花費。

哈夫曼演算法中取兩個最小的數演算法思想是:從陣列的起始位置開始,首先找到兩個無父節點的結點(沒有構成樹),然後和後續無父節點的節點依次比較。有兩種情況需要考慮:

  1. 如果該節點比兩個節點都小,保留這個節點,刪除原來較大的節點;
  2. 如果該節點介於兩個節點大小之間,則可以替換掉那個最大的結點。
    基於c++的演算法如下:
void Select(int n, int &i1, int &i2)
{
    int i = 0;
    for( ; i < n; i++)
        if(huffTree[i].parent == -1) {i1 = i; break;}
    for(i = i + 1; i < n; i++)
        if(huffTree[i].parent == -1) {i2 = i; break;}
    //首先找到沒有兩個父節點的兩個節點i1 和 i2,在這裡的parent等於-1表示沒有構成樹
    
    if(huffTree[i1].weight > huffTree[i2].weight)
    {
        int temp = i1;
        i1 = i2;
        i2 = temp //交換程式碼
    }
    //在這裡規定i1和i2的大小,i1為小,i2為大

    //迴圈遍歷陣列
    for(i = i + 1; i < n; i++)
    {
        if(huffTree[i].parent == -1)  //找到後續沒有父節點的結點
        {
            if(huffTree[i].weight < huffTree[i1].weight)//如果比i1和i2都小
            {
                i2 = i1; i1 = i;  
            }
            else if(huffTree[i].weight < huffTree[i2].weight) //如果介於兩者之間
            {
                i2 = i;
            }
        } 
    }
}

這樣我們就確定了兩個權值最小的節點。

完整程式碼如下

#include<iostream>
using namespace std;
const int MAXSIZE = 104;
//定義樹的結點結構,包括節點權值,左右孩子指標,父節點指標
struct Elemtype {
	int weight;
	int parent, lchild, rchild;
}; 

//構造哈夫曼樹
class HuffmanTree {
public:
	HuffmanTree(int w[], int n); //構造哈夫曼樹,引數w為權值陣列,n為權值陣列的個數
	int GetSumWeight();//返回哈夫曼樹的權值和
private:
	Elemtype* hufftree;//儲存哈夫曼樹的結點
	int num;//節點個數
	int Sumweght = 0;
	void Select(int n, int& i1, int& i2); //選擇權值最小的兩個節點
};

HuffmanTree::HuffmanTree(int w[], int n)
{
	int i, k, i1, i2;
	hufftree = new Elemtype[2 * n + 1];//開闢儲存結點的空間,個數為2n+1
	num = n;
	for (i = 0; i < 2 * num + 1; i++)
	{
		hufftree[i].parent = -1;
		hufftree[i].lchild = -1;
        hufftree[i].rchild = -1;
	}//初始化結點,沒有孩子,沒有父節點
	for(i = 0; i < num; i++)
		hufftree[i].weight = w[i];
	//儲存葉子結點的權值
	for (k = num; k < 2 * num - 1; k++)
	{
		Select(k, i1, i2); //選擇權值最小的兩個節點
		hufftree[k].weight = hufftree[i1].weight + hufftree[i2].weight;
		Sumweght += hufftree[k].weight;
		hufftree[i1].parent = k;
		hufftree[i2].parent = k;
		hufftree[k].lchild = i1;
		hufftree[k].rchild = i2;
	}
}

void HuffmanTree::Select(int n, int& i1, int& i2)
{
	int i = 0, temp;
	for (; i < n; i++)
		if (hufftree[i].parent == -1) { i1 = i; break; }
	for (i = i + 1; i < n; i++)
		if (hufftree[i].parent == -1) { i2 = i; break; }
	if (hufftree[i1].weight > hufftree[i2].weight)
	{
        temp = i1;
        i1 = i2;
        i2 = temp;
	}
    for (i = i + 1; i < n; i++)
		if (hufftree[i].parent == -1)
		{
			if (hufftree[i].weight < hufftree[i1].weight)
			{
				i2 = i1, i1 = i;
			}
			else if (hufftree[i].weight < hufftree[i2].weight)
			{
				i2 = i;
			}
		}
}

int HuffmanTree::GetSumWeight()
{
	return Sumweght;
}

int main() {
	cout << "請輸入你要將木頭切割的塊數正整數N:" << endl;
	int N;
	cin >> N;
	cout << "請輸入每段木塊的長度:"<<endl;
    int w[MAXSIZE];
	for (int i = 0; i < N; i++)
	{
		cin >> w[i];
	}
	HuffmanTree ht(w, N);
	cout << "哈夫曼樹的權值和為:" << ht.GetSumWeight() << endl;
	return 0;
}



相關文章