- 貪心算法(Greed alalgorithm) 是一種在每一步選擇中都採起在當前狀態下最好或最優(即最有利)的選擇,從而但願致使全局結果是最好或最優的算法。
- 分治算法(Divide and conquer alalgorithm) 字面上的解釋是「分而治之」,就是把一個複雜的問題分紅兩個或更多的相同或類似的子問題,直到最後子問題能夠簡單的直接求解,原問題的解即子問題的解的合併。
- 動態規劃算法(Dynamic programming,DP) 經過將原問題分解爲相對簡單的子問題的方式來求解複雜問題。一般許多子問題很是類似,爲此動態規劃法試圖僅僅解決每一個子問題一次,從而減小計算量:一旦某個給定子問題的解已經算出,則將其記憶化存儲,以便下次須要同一個子問題解之時直接查表。
貪心法在處理每一個子問題時,不能回退,而動態規劃能夠保存以前的結果,擇優選擇。下面針對Interval Scheduling 問題,分析動態規劃在實際問題中的應用。ios
以下圖所示,每一個長條方塊表明一個工做,總有若干個工做a、b... h,橫座標是時間,方塊的起點和終點分別表明這個工做的起始時間和結束時間。算法
當兩個工做的工做時間沒有交叉,即兩個方塊不重疊時,表示這兩個工做是兼容的(compatible)。編程
當給每一個工做賦權值都爲1時,則稱爲 Unweighted Interval Scheduling 問題;當給每一個工做賦不一樣的正權值時,則稱爲 Weighted Interval Scheduling 問題。ide
問題最終是要找到一個工做子集,集合內全部工做權值之和最大且集合內每一個工做都兼容。測試
對於 Unweighted Interval Scheduling 問題,使用貪心算法便可求解,具體作法是按照結束時間對全部工做進行排序,而後從結束最晚的工做開始,依次排除掉與前一個不兼容的工做,剩下的工做所組成的集合即爲所求。優化
然而,對於 Weighted Interval Scheduling 問題,貪心法找到的解可能不是最優的了。此時考慮使用動態規劃算法解決問題,兼顧權值選擇和兼容關係。ui
一、首先依然按照結束時間對全部的工做進行排序;spa
二、定義p(j)爲在工做j以前,且與j兼容的工做的最大標號,經過分析每一個工做的起始時間和結束時間,能夠很容易計算出p(j);3d
三、例以下圖所示,p(8)=5,由於工做7和6都與8不兼容,工做1到5都與8兼容,而5是其中索引最大的一個,因此p(8)=5。同理,p(7)=3,p(2)=0。code
一、定義opt(j)是j個工做中,所能選擇到的最佳方案,即opt(j)是最大的權值和;
二、對於第j個工做,有兩種狀況:
三、當j=0時,顯示結果爲0,這是邊界條件。
後一步的結果取前一步全部可能狀況的最大值,所以綜上所述,能獲得動態規劃的遞歸關係爲:
一、遞歸法
遞歸會使得空間複雜度變高,通常不建議使用。
二、自底向上法
從小到大進行計算,這樣每次均可以利用前一步計算好的值來計算後一步的值,算法時間複雜度爲O(nlogn),其中排序花費O(nlogn),後面的循環花費O(n)。
- 以下圖所示,給定一個揹包Knapsack,有若干物品Item
- 每一個item有本身的重量weight,對應一個價值value
- 揹包的總重量限定爲W
- 目標是填充揹包,在不超重的狀況下,使揹包內物品總重量最大。
對於下圖的例子,一種常見的貪心思想是:在揹包能夠裝得下的狀況下,儘量選擇價值更高的物品。那麼當揹包容量是W=11時,先選擇item5,再選擇item2,最後只能放下item1,總價值爲28+6+1=35。實際上最優解是選擇item3和item4,價值18+22=40。這說明了貪心算法對於揹包問題的求解可能不是zuiyou的。下面考慮使用動態規劃算法求解,首先要推導遞歸關係式。
相似於Weighted Interval Scheduling問題,定義opt(i, w)表示在有i個item,且揹包剩餘容量爲w時所能獲得的最大價值和。
考慮第i個item,有選和不選兩種狀況:
邊界條件: 當i=0時,顯然opt(i,w)=0。
後一步的結果取前一步全部可能狀況的最大值,所以綜上所述,能獲得動態規劃的遞歸關係爲:
算法迭代過程以下表:
值得注意的是,該算法相對於輸入尺寸來講,不是一個多項式算法,雖然O(nW)看起來很像一個多項式解,揹包問題其實是一個NP徹底問題。
爲了便於理解,能夠寫成這種形式:
W在計算機中只是一個數字,以長度logW的空間存儲,很是小。可是在實際運算中,隨着W的改變,須要計算nW次,這是很是大的(相對於logW來講)。例如,當W爲5kg的時候,以kg爲基準單位,須要計算O(5n)次,當W爲5t時,仍然以kg爲單位,須要計算O(5000n)次,而在計算機中W的變化量相對很小。
給定兩個序列x1,x2...xi和y1,y2,...,yj。要匹配這兩個序列,使類似度足夠大。首先須要定義一個表示代價的量-Edit distance,只有優化使這個量最小,就至關於最大化匹配了這兩個序列。
Edit distance的定義以下所示。
其中,匹配到空,設距離爲delta,不然字母p和q匹配的距離記爲alpha(p,q),若是p=q,則alpha=0;
那麼兩個序列匹配的總代價爲:
設opt(i,j)是序列x1,x2...xi和y1,y2,...,yj之間匹配所花費的最小代價。當i,j不全爲0時,則分別有三種狀況,分別是xi-gap,yj-gap,xi-yj,分別計算不一樣匹配狀況所花費的代價,再加上前一步的結果,就能夠創建遞推關係式,以下所示。
時間和空間複雜度皆爲O(mn)。
下面再分析一個具體的編程問題,使用動態規劃算法,可是和上面的DP又有一些區別。
有 n 個學生站成一排,每一個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎?
輸入描述
每一個輸入包含 1 個測試用例。每一個測試數據的第一行包含一個整數 n (1 <= n <= 50),表示學生的個數,接下來的一行,包含 n 個整數,按順序表示每一個學生的能力值 ai(-50 <= ai <= 50)。接下來的一行包含兩個整數,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
輸出描述
輸出一行表示最大的乘積。
則沒法實現「相鄰兩個學生的位置編號的差不超過 d」的要求。所以,須要定義一個輔助量,來包含對當前學生的定位信息。
其中,j是一個比i小的值,最大爲i-1,i、j之差不超過D,f(j,k-1)表示在前j個學生中,選擇k-1個學生,且第j個學生必選。f(i,k)選擇了第i個學生,f(j,k-1)選擇了第j個學生,i、j之差不超過D,這樣就能夠知足題目要求了。
/********************************************************************* * * Ran Chen <wychencr@163.com> * * Dynamic programming algorithm * *********************************************************************/ #include <iostream> #include <vector> #include <climits> #include <algorithm> using namespace std; int main() { int N, D, K; // 總共N個學生 vector <int> value; while (cin >> N) { for (int i = 0; i < N; ++i) { int v; cin >> v; value.push_back(v); } break; } cin >> K; // 選擇K個學生 cin >> D; // 相鄰被選擇學生的序號差值 // fmax/fmin[i, k]表示在選擇第i個數的狀況下的最大/小乘積 vector <vector <long long>> fmax(N+1, vector <long long> (K+1)); vector <vector <long long>> fmin(N+1, vector <long long> (K+1)); // 邊界條件k=1 for (int i = 1; i <= N; ++i) { fmax[i][1] = value[i - 1]; fmin[i][1] = value[i - 1]; } // 自底向上dp, k>=1 for (int k = 2; k <= K; ++k) { // i >= k for (int i = k; i <= N; ++i) { // 0 <= j <= i-1 && i - j <= D && j >= k-1 long long *max_j = new long long; *max_j = LLONG_MIN; long long *min_j = new long long; *min_j = LLONG_MAX; // f(i, k) = max_j {f(j, k-1) * value(i)} int j = max(i - D, max(k - 1, 1)); for ( ; j <= i - 1; ++j) { *max_j = max(*max_j, max(fmax[j][k - 1] * value[i - 1], fmin[j][k - 1] * value[i - 1])); *min_j = min(*min_j, min(fmax[j][k - 1] * value[i - 1], fmin[j][k - 1] * value[i - 1])); } fmax[i][k] = *max_j; fmin[i][k] = *min_j; delete max_j; delete min_j; } } // opt(N, K) = max_i {f(i, K)}, K <= i <= N long long *temp = new long long; *temp = fmax[K][K]; for (int i = K+1; i <= N; ++i) { *temp = max(*temp, fmax[i][K]); } cout << *temp; delete temp; system("pause"); return 0; }