【題目描述】 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塊的最小花費
題目分析
哈夫曼編碼通常用於資料壓縮,也可以用來解決類似的問題,尤其是涉及合併成本最小化時。對於這個問題我們可以使用哈夫曼樹的思想來解決。
演算法過程:
- 初始化:將所有的木頭放入一個陣列中
- 合併:每次從堆中取出兩個最小的木頭,建立一個新的結點,其值為兩個木頭長度之和,並將結點放回陣列中。
- 重複:重複上述步驟,直到只剩下一個結點。
- 計算:在每次合併時累加合併成本,得到總最小花費。
哈夫曼演算法中取兩個最小的數演算法思想是:從陣列的起始位置開始,首先找到兩個無父節點的結點(沒有構成樹),然後和後續無父節點的節點依次比較。有兩種情況需要考慮:
- 如果該節點比兩個節點都小,保留這個節點,刪除原來較大的節點;
- 如果該節點介於兩個節點大小之間,則可以替換掉那個最大的結點。
基於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;
}