350 likes | 608 Views
動態規劃. Dynamic Programming. 精神. 把大問題分解成性質相似的小問題 (Divide and Conquer) 把重複出現的小問題的答案 記錄下來. 費氏數列. int fib( int n) { if(n==0) return 0; if(n==1) return 1; else return fib(n-1) + fib(n-2); }. fib(5). fib(3). fib(4). fib(1). fib(2). fib(2). fib(3). fib(1). fib(0). fib(1). fib(0).
E N D
動態規劃 Dynamic Programming
精神 • 把大問題分解成性質相似的小問題 (Divide and Conquer) • 把重複出現的小問題的答案記錄下來
int fib(int n) { if(n==0) return 0; if(n==1) return 1; else return fib(n-1) + fib(n-2); }
fib(5) fib(3) fib(4) fib(1) fib(2) fib(2) fib(3) fib(1) fib(0) fib(1) fib(0) fib(1) fib(2) fib(0) fib(1)
intdp[MAX];// initialize with 0 int fib(int n) { if(dp[n]) return dp[n]; if(n==0) return dp[n] = 0; if(n==1) return dp[n] = 1; else return dp[n] = fib(n-1) + fib(n-2); }
fib(5) fib(3) fib(4) fib(1) fib(2) fib(2) fib(3) fib(0) fib(1) 1 1 2 3 5
Top-down • Easy and Clear • Slow fib(5) fib(3) fib(4) fib(1) fib(2) fib(2) fib(3) fib(0) fib(1)
Bottom-up int fib[MAX]; fib[0] = 0; fib[1] = 1; for(inti=2; i<=n; i++) fib[i] = fib[i-1] + fib[i-2];
演算法設計 • 找出初始值、問題範圍 • 寫出把大問題分解成小問題的遞迴式 • 用陣列把算過的問題答案記錄下來 • 加速:安排計算順序 (小問題→大問題),用迴圈實作
走樓梯 • 走樓梯,一步可以走一階或兩階 • 樓梯共n階,有幾種不同走法?
Solution • 1階的樓梯有1種走法 • 2階的樓梯有2種走法 (2or 1+1) • 3階的樓梯有3種走法 (1+1+1, 1+2, 2+1) • …
Solution • n階的樓梯: • 第一步走一階→剩下n-1階 • 第一步走兩階→剩下n-2階 • 走n階樓梯的方法數 = 第一步走一階的方法數 + 第一步走兩階的方法數 = 走n-1階的方法數 + 走n-2階的方法數
Solution • 令f(n)為走n階樓梯的方法數 • f(1) = 1 • f(2) = 2 • f(n) = f(n-1) + f(n-2)
Staircase Walk • 一個n*m的棋盤,從左上角走到右下角,一步只能往右走一格或往下走一格,有幾種走法? n = 11, m = 9 (1,1) (n,m)
Solution • 令s(n,m)為從起點(1,1)走到(n,m)的方法數 • n=1 or m=1時只有1種走法(一直往下走 or 一直往右走) → s(1,k)=1, s(k,1)=1 • 走到(n,m)的方法數 = 從上面走來的方法數 + 從左邊走來的方法數 =s(n, m - 1) + s(n - 1, m)
Solution intdp[MAXN][MAXM];// 初始化為0 int s(int n, int m) { if(dp[n][m]) return dp[n][m]; if(n==1 || m==1) return dp[n][m] = 1; return dp[n][m] = s(n, m-1) + s(n-1, m); }
Solution intdp[MAXN][MAXM]; for(inti=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(i==1 || j==1) dp[i][j] = 1; else dp[i][j] = dp[i][j-1] + dp[i-1][j]; } }
空間優化 intdp[MAXM]; for(inti=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(i==1 || j==1) dp[j] = 1; else dp[j]= dp[j-1] + dp[j]; } }
切棒子 • 不同長度的鐵棒有不同的價格 • 我有一根鐵棒要賣,可以直接賣掉,也可以切成小段分開出售 • 要怎麼切才有最大獲利?
Solution • 設鐵棒長度n • 先切一段長k下來,賣出價格p[k] • 剩下長度n-k → 求長度n-k的鐵棒可以賣得的最大獲利 • 對每一個k都試試看,取最大值
Solution • 令c(n)為長度n的鐵棒可以賣出的最高價 • c(0) = 0 • c(n) = max(p[k]+c(n-k)) ∀ 1 ≤ k ≤ n
// top-down intdp[MAX]; // init with 0 int c(int n) { if(dp[n]) return dp[n]; if(n==0) return 0; int q = -1; for(int k=1; k<=n; k++) q = max(q, p[k]+c(n-k)); return dp[n] = q; }
// bottom-up intdp[MAX]; dp[0] = 0; for(inti=1; i<=n; i++) { int q = -1; for(int k=1; k<=i; k++) q = max(q, p[k]+dp[i-k]); dp[i] = q; }
Longest Common Subsequence • 子序列 (Subsequence):從一個序列中任選幾個數字所組成 • 15324 → 2、1 3、5 3 4、1 5 3 2 4、Ø • 輸入兩個序列,找出最長的共同子序列長度 s1:15324 1524 s2: 2145024
Solution • 設LCS(a, b)為”序列s1取到第a項,序列s2取到第b項”時,LCS的長度 • 情況1:s1[a] ∈ LCS,s2[b] ∉LCS → LCS=s1[1~a]和s2[1~b-1]的LCS → LCS(a, b) = LCS(a, b-1) • 情況2:s1[a] ∉ LCS,s2[b] ∈ LCS → LCS = s1[1~a-1]和s2[1~b]的LCS → LCS(a, b) = LCS(a-1, b)
Solution • 情況3:s1[a] ∉LCS,s2[b] ∉ LCS → LCS(a, b) = LCS(a-1, b-1) • 情況4:s1[a] ∈ LCS,s2[b] ∈ LCS → s1[a] == s2[b] → LCS(a, b) = LCS(a-1, b-1) + 1
0/1背包問題 • 一背包,容量V • k種物品,體積c[1] ~ c[k],價值w[1] ~ w[k] • 每種物品只有一個,可選擇拿或不拿 • 背包最多可以裝多少價值的物品?
Solution • 設kp(k, V)為容量V的背包”取到第k件物品”時,可以達到的最大總價值 • 情況一:取第k件物品 (c[k] ≤ V) → kp(k, V) = kp(k-1, V-c[k]) + w[k] • 情況二:不取第k件物品 → kp(k, V) = kp(k-1, V)
// bottom-up intkp[MAXK][MAXV]; for(inti=0; i<=k; i++) { for(int v=0; v<=V; v++) { if(i==0){ kp[i][v] = 0; continue; } if(v<c[i]) kp[i][v] = kp[i-1][v]; else kp[i][v] = max(kp[i-1][v], kp[i-1][v-c[i]]+w[i]); } }
空間優化 intkp[MAXV];//init with 0 for(inti=1; i<=k; i++) for(int v=V; v>=c[i]; v--) kp[v] = max(kp[v], kp[v-c[i]]+w[i]);
演算法筆記 (http://www.csie.ntnu.edu.tw/~u91029/) • 背包問題九講 (http://crc.hs.ntnu.edu.tw/pack/)