動態規劃初步 數字三角形(算法競賽入門經典讀書筆記)

問題簡述:

如圖示數字三角形。從第一行的數開始,每次可以往左下或右下走一步,直到走到最下行,把沿途經過的數全部加起來,如何走才能使得這個和儘量大?
在這裏插入圖片描述

狀態定義:

把每個數字的 當前位置(i,j) 表示如下:
在這裏插入圖片描述
這裏定義幾個狀態:(務必理解這兩個狀態)
a[ i ][ j ]: 存儲對應位置的數值。如a[1][1]=1,a[2][1]=3。
d[ i ][ j ]: 從(i,j)出發時能得到的最大和(包括其本身的值)。如d[3][1]=8。即 d(i,j) = a(i,j) + max { d(i+1, j) , d(i+1, j+1) }

解決方法:

  1. 遞歸計算:不推薦,由於相同的子問題被重複計算了多次,故效率低下
  2. 遞推計算:推薦使用,關鍵是邊界和計算順序
  3. 記憶化搜索:推薦使用,不必確定計算順序,但要記錄每個狀態“是否已經計算過”

1.遞歸計算:

由上述得出的方程 d(i,j) = a(i,j) + max { d(i+1, j) , d(i+1, j+1) } (這個方程就是狀態轉移方程)
可知題目要求的就是d(1,1),而d(1,1)是d(2,1)和d(2,2)中的較大值加上a(1,1)。
即:
d(1,1)=a(1,1)+max{ d(2,1) , d(2,2) }
d(2,1)=a(2,1)+max{ d(3,1) , d(3,2) }
d(2,2)=a(2,2)+max{ d(3,2) , d(3,3) }
… …
顯然,這就是個遞歸,如果我們按照這個思路寫完代碼就完美的實現了方法一遞歸計算

遞歸計算代碼:

int solve(int i,int j){
	return a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
	//如果是最後一行返回0,否則按方程計算 
} 

但是,上面三個式子中加粗的部分 d(3,2) 被重複計算了兩遍,我會覺得重複一兩個數沒有太大影響。
可事實是:這樣的重複不是單個結點,而是一棵子樹

假設繼續計算剛剛的d(2,1):
d(2,1)=a(2,1)+max{ d(3,1) , d(3,2) }

d(3,1)=a(3,1)+max{ d(4,1) , d(4,2) }
d(3,2)=a(3,2)+max{ d(4,2) , d(4,3) }

這個三角形還只有四層,如果三角形是n層,一共有2n -1結點。
就一直重複計算吖重複計算重複計算~怎麼不要讓它重複計算呢?如下

2.記憶化搜索:

記憶化看着好高級哦 其實就是當計算過一次d(i,j)後,就將計算結果存儲好並記住這個值我是算過了的,即記錄每個狀態“是否已經計算過”,下次使用時判斷如果這個d(i,j)已經計算過了呢就直接調用,沒有計算過我再計算。這樣一來,就解決了重複計算的問題

具體操作如下:

  1. 用“ memset(d,-1,sizeof(d)) ”把d全部初始化爲-1
  2. 計算結果保存在d[ i ][ j ]中。即如果已經計算過某個d[ i ][ j ],它應該是非負的
  3. 通過判斷是否d[ i ][ j ]>=0得知它是否被計算過

記憶化搜索代碼:

int solve(int i,int j){
	if(d[i][j]>=0)return d[i][j];//若已經計算過直接返回值 
	return d[i][j]=a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
	//如果是最後一行返回0,否則按方程計算 
} 

3.遞推計算:

其關鍵在於逆序,從倒數第二行開始,將每個數與其左右兩個底數中的較大值相加,同理再倒數第三行第四行…最後最頂層的值就是總和最大值。

因此保證了在計算d[i][j]之前,所需的d[i+1][j+1]一定已經計算出來了,過程如下圖所示:
在這裏插入圖片描述

遞推計算代碼:

int i,j;
for(j=1;j<=n;j++)d[n][j]=a[n][j];//邊界處理
for(i=n-1;i>=1;i--)//倒數第二行開始
	for(j=1;j<=i;j++)
		d[i][j]=a[i][j]+max(d[i+1][j],d[i+1][j+1]); 

之前藍橋杯算法訓練寫過一個數字三角形答案,現在看來當時用的遞推hhh
https://blog.csdn.net/weixin_42324771/article/details/87023413

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章