一家公司購買長鋼條,將其切割成短鋼條**,切割本身沒有成本,長度為i的短鋼條的**為pi。那給定一段長度為n的鋼條和乙個**表pi,求鋼條的切割方案使得收益rn最大。如乙個pi如下:
長度i123
4567
8910**pi15
891017
1720
2430
在距離鋼條左端i長度處,我們總是可以選擇切割或者不切割,所以長度為n的鋼條共有2的n-1次方中不同的切割方案.
假設我們把鋼條分割成k個部分,每個部分的長度分別為
這自然就想到了運籌學中的動態規劃。說到動態規劃,最經典的莫過於最優路徑問題。最短路徑問題中有乙個典型特徵就是從最短路徑中任找一點到終點的路徑也是最短的,這很容易明白,如果還有更短的,那它就不是最短的了。這裡一定是路徑中任何一點到終於,而不是任意兩點。具體描述如下:
假如最短路徑是a-b-c-d-e,那麼d-e,c-d-e,b-c-d-e也是最優的。
這樣,這個特徵就給我們乙個思路,就是如果倒著尋找最優路徑,每一步都是最優的,那麼最後一定也是最優的。即先找d-e,接著找c-d-e,b-c-d-e,這樣就找到我們想要的最優解了。這裡不要誤會是先d-e,然後找c-d,...,這樣就錯了,這就不符合上面的意思了。或許你還沒有理解是什麼意思,我再舉個例子。
上圖中,要找到從1到10的最短距離,我們倒著找。
第一層:8-10的最短距離是8,9-10的最距離是4
第二層:5到10的最短距離,比較「5-8的最短距離+8-10的最短距離」,和「5-9的最短距離+9-10的最短距離」。由於8-10,9-10的最短距離已經在第一層中求出,所以不用再求了,這樣很快就求出5-10的距離;同樣6-10,7-10的最短距離也就求出來了。
第三層:2-10的最短距離,比較「2-5的最短距離+5-10的最短距離,和"2-6的最短距離+6-10的最短距離"。由於5-10,6-10的最短距離已經在第二層求出,所以也不用再求了。這樣很快求出,2,3,4-10的最短距離。
第四層和上面一樣。
到這裡,大家應該明白我的意思了。同時從計算角度來說,減小了很多計算量,因為沒有重複計算。上面就是動態規劃的核心思想,大的方面來講就是分治思想,大事化小,小事化了的意思;小的方面來講,就是倒推理,像破案一樣,倒著推理,乙個細節也不放過。
如果你還是不明白,覺得應該是每步都是最優的,就應該每一步都是最優的。這麼想也並非不是乙個備用方案,貪心法就是這個原理。如果要從數學中找相應的例子,就是最速下降法的每步都是按下降最快的逆梯度方向下降,但整體來看,並非是下降最快的方向。
明白了上面的道理,我們回頭來分析我們的鋼條切割問題。我們也可以大事化小,可以像最短路徑一樣分層分析,如果分為一段,二段,三段和四段,就達到分層效果了。但我們如果去利用分層去解決我們的問題呢?我們一層一層地慢慢道來。
如果分為一層,很明顯就是9元,如果分為二層,則有4種分法(考慮方向),即1-3,2-2,3-1,當兩部分都達到最優時,兩者合起來才是最優的,這樣我們就把問題轉到每一部分的最優上了,分析方式和上面一樣。如果用公式表示就是:
相信大家,立刻就知道怎麼做的了吧,從程式設計角度來講,太符合遞迴的原理了,這樣我們就很容易寫程式了。但你真正開始寫程式的時候,發現沒這麼簡單,如果總這麼遞迴下去,能夠用的數就是a1了,其它的都沒用,很顯然這是不對的。解決問題,得要先知道問題出在哪,很顯然問題出在遞迴的終止條件上了,不能總返回a1,應該能夠返回所有的a值。但這樣就麻煩了,只有當分成0-n這個部分是遞迴有返回值,其它的情況就要繼續遞迴,所以這樣就得到其程式**了,這裡用c語言描述:
[cpp]view plain
copy
static
intk=0;
intsteer(
intp,
intr,
intn)
//p為每個長度的**,n為長度
//r為輸出,儲存每個長度的最優**
當n=4時
當n=5時
從上面的結構可以看出,很多長度重複了很多次,浪費了時間
那就繼續改進唄。我們再來分析,我們分析發現,1-3和3-1是一樣的,也就是說對稱的。也就是說a(i)就沒必要了,直接用p[i]代替即可,所以就有如下程式:
[cpp]view plain
copy
static
intk=0;
intsteer(
intp,
intr,
intn)
當n=4時
當n=5時
//兩者的比較如下表:12
3456
78第一種113
1345
81413
865第二種12
4102442
161298
//記錄迭代次數
很顯示第二種對第一種有了比較大的改進,但仔細看,它們的迭代次數並非演算法導論裡寫的
現在繼續改進演算法。從迭代結果來看,很多時候,我們重複計算了很多次,這就浪費了時間。如果要是不重複計算呢,把每次的結果都儲存下來,這樣時間複雜度要少很多。怎麼樣做呢?就像最短路徑分析的那樣,每層計算的時候,利用上層已經計算的最優值,這樣就可以了。所以計算每層時,要儲存其值,然後在迭代的時候,判斷其是否已經存在,程式如下:
[cpp]view plain
copy
static
intk=0;
intsteer(
intp,
intr,
intn)
當n=4時
當n=5時
很明顯,時間複雜度就就變成n了,效率大大提高了。如果你學習過運籌學裡的動態規劃,你就知道動態規劃演算法裡有兩種,一種是逆推解法,另一種是順推解法。上面的最短路徑使用的是逆推解法,而下面的程式採用的是順推解法。仔細思考的話,逆推解法是不需要遞迴的,只需要倒著一步步往上推就可以了。逆推解法的程式如下:
[cpp]view plain
copy
static
intk=0;
void
steer(
intp,
intr,
intn)
} 當n=5時
以上皆用到
#define max(x,y) ((x)>(y))?(x):(y)
如果要記錄如何切割的方案,那只需要在max時記住最大值處的索引即可。相信在這裡,讀者有了很清楚的了解了,這裡是從開始到最後的所有思路,告訴你我是怎麼分析的,而不是只有結論。
動態規劃 鋼條切割問題
已知鋼條切割的不同長度對應的不同 如下所示 長度i 1 23 45 67 89 10 pi 1589101717202430 求輸入長度,輸出最佳的收益。詳細理論知識見 演算法導論第十五章 p359 書中給出三個演算法 一 自頂向下遞迴實現 缺點 當n足夠大時,時間會 性地增長。偽 cut rod ...
動態規劃 鋼條切割問題
動態規劃與分治法相似,都是通過組合子問題的解來求解原問題。回顧下分治法的原理 它將問題劃分為互不相交的子問題 注意 互不相交 遞迴求解子問題,再將它們的解組合起來,即為原問題的解。但是,動態規劃與分治法不同,有以下幾點 1 對於子問題重疊的情況,分治法則重複求解,不高效。而動態規劃對每個子問題只求解...
動態規劃 鋼條切割問題
動態規劃主要用於求解最優化問題,方法與分治法類似,也是將原問題分解成多個子問題,通過遞迴的方法求解子問題。不同之處就是動態規劃會通過增加程式空間複雜度的方式來將時間複雜度為指數級降低為多項式,通俗的講就是動態規劃會利用陣列記錄下子問題的結果,當再需要計算該子問題時直接呼叫該結果即可,就不用再去計算,...