動態規划入門-數字三角形(從朴素遞歸到各種優化)


數字三角形(POJ1163)

Description

7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

 

在上面的數字三角形中尋找一條從頂部到底邊的路徑,使得
路徑上所經過的數字之和最大。路徑上的每一步都只能往左下或
右下走。只需要求出這個最大和即可,不必給出具體路徑。
三角形的行數大於1小於等於100,數字為 0 - 99

輸入格式:
5 //三角形行數。下面是三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

要求輸出最大和

 

Sample Output

30

Source

 
 

解題思路:
用二維數組存放數字三角形。
D( r, j) : 第r行第 j 個數字(r,j從1開始算)
MaxSum(r, j) : 從D(r,j)到底邊的各條路徑中,
最佳路徑的數字之和。
問題:求 MaxSum(1,1)
典型的遞歸問題。
D(r, j)出發,下一步只能走D(r+1,j)或者D(r+1, j+1)。故對於N行的三角形:
if ( r == N)
MaxSum(r,j) = D(r,j)
else
MaxSum( r, j) = Max{ MaxSum(r+1,j), MaxSum(r+1,j+1) } + D(r,j)

改進

如果每算出一個MaxSum(r,j)就保存起來,下次用
到其值的時候直接取用,則可免去重復計算。

 

遞歸轉成遞推

“人人為我”遞推型動歸 Pascal代碼

 1 //By LYLtim
 2 //2010.10.18
 3 uses math;
 4 var n,i,j:byte;
 5     a:array[1..10,1..10]of word;
 6     f:array[1..10,1..10]of word;
 7 begin
 8     assign(input,'tower.in');reset(input);
 9     assign(output,'tower.out');rewrite(output);
10     readln(n);
11     for i:=1 to n do
12         begin
13             for j:=1 to i do
14                 read(a[i,j]);
15             readln;
16         end;
17     fillchar(f,sizeof(f),0);
18     for i:=1 to n do f[n,i]:=a[n,i];
19     for i:=n-1 downto 1 do
20         for j:=1 to i do
21             f[i,j]:=max(f[i+1,j],f[i+1,j+1])+a[i,j];
22     writeln('max=',f[1,1]);
23     close(input);close(output);
24 end.

空間優化

沒必要用二維maxSum數組存儲每一個MaxSum(r,j),只要從底層一行行向上
遞推,那么只要一維數組maxSum[100]即可,即只要存儲一行的MaxSum值就
可以。

進一步考慮,連maxSum數組都可以不要,直接用D的
第n行替代maxSum即可。
節省空間,時間復雜度不變

 

遞推+空間優化 C++代碼
 1 //By LYLtim
 2 //2015.2.11
 3 #include <iostream>
 4 #include <algorithm>
 5 using namespace std;
 6 int main()
 7 {
 8     int n, d[101][101];
 9     cin >> n;
10     for (int i = 1; i <= n; i++)
11         for (int j = 1; j <= i; j++)
12             cin >> d[i][j];
13     for (int i = n-1; i >= 1 ; i--)
14         for (int j = 1; j <= i; j++)
15             d[n][j] = max(d[n][j], d[n][j+1]) + d[i][j];
16     cout << d[n][1];
17 }

 

遞歸到動規的一般轉化方法

遞歸函數有n個參數,就定義一個n維的數組,數組
的下標是遞歸函數參數的取值范圍,數組元素的值
是遞歸函數的返回值,這樣就可以從邊界值開始,
逐步填充數組,相當於計算遞歸函數值的逆過程。

 

動規解題的一般思路
1. 將原問題分解為子問題
 把原問題分解為若干個子問題,子問題和原問題形式相同
或類似,只不過規模變小了。子問題都解決,原問題即解
決(數字三角形例)。
 子問題的解一旦求出就會被保存,所以每個子問題只需求
解一次。

2. 確定狀態
 在用動態規划解題時,我們往往將和子問題相
關的各個變量的一組取值,稱之為一個“狀
態”。一個“狀態”對應於一個或多個子問題,
所謂某個“狀態”下的“值”,就是這個“狀
態”所對應的子問題的解。

所有“狀態”的集合,構成問題的“狀態空間”。“狀態
空間”的大小,與用動態規划解決問題的時間復雜度直接相關。
在數字三角形的例子里,一共有N×(N+1)/2個數字,所以這個
問題的狀態空間里一共就有N×(N+1)/2個狀態。
整個問題的時間復雜度是狀態數目乘以計算每個狀態所需
時間。
在數字三角形里每個“狀態”只需要經過一次,且在每個
狀態上作計算所花的時間都是和N無關的常數。

用動態規划解題,經常碰到的情況是,K個整型變量能
構成一個狀態(如數字三角形中的行號和列號這兩個變量
構成“狀態”)。如果這K個整型變量的取值范圍分別是
N1, N2, ……Nk,那么,我們就可以用一個K維的數組
array[N1] [N2]……[Nk]來存儲各個狀態的“值”。這個
“值”未必就是一個整數或浮點數,可能是需要一個結構
才能表示的,那么array就可以是一個結構數組。一個
“狀態”下的“值”通常會是一個或多個子問題的解。

3. 確定一些初始狀態(邊界狀態)的值
以“數字三角形”為例,初始狀態就是底邊數字,值
就是底邊數字值。

4. 確定狀態轉移方程
定義出什么是“狀態”,以及在該 “狀態”下的“值”后,就要
找出不同的狀態之間如何遷移――即如何從一個或多個“值”已知的
“狀態”,求出另一個“狀態”的“值”(“人人為我”遞推型)。狀
態的遷移可以用遞推公式表示,此遞推公式也可被稱作“狀態轉移方
程”。

 

能用動規解決的問題的特點
1) 問題具有最優子結構性質。如果問題的最優解所包含的
子問題的解也是最優的,我們就稱該問題具有最優子結
構性質。
2) 無后效性。當前的若干個狀態值一旦確定,則此后過程
的演變就只和這若干個狀態的值有關,和之前是采取哪
種手段或經過哪條路徑演變到當前的這若干個狀態,沒
有關系。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM