動態規劃之棋盤路徑問題
- 2019 年 10 月 6 日
- 筆記
動態規劃之棋盤路徑問題
1.對比
DP vs 回溯 vs 貪心
- 回溯(遞歸) – 重複計算
- 貪心 – 永遠局部最優
- DP – 記錄局部最優子結構/多種記錄值
2.棋盤路徑問題
問題描述:
如下圖所示,小人從左上角移動到右下角,總共有多少種走法.
約束條件為:粉紅色為石頭,走不通,小人走路方向只有向下與向右.

該問題可以分解f(0,0)=0,f(1,0)=1,f(0,1)=1 f(m,n)=f(m-1,n)+f(m,n-1)(此時m,n不為0)。
|
0(A) |
1 |
|---|---|
|
1 |
2(B) |
如上表所示為從棋盤中取出的左上角4個格子,填充的數據中第二行第二列(index假設從1開始)為2,表示從A到B有2種路徑,依次往下走,最終得到f(m,n)=f(m-1,n)+f(m,n-1)(此時m,n不為0)。
因此該問題是遞歸問題,同時可以通過動態規劃解決。
3.判斷是石頭還是空地
上述棋盤有個很強的約束條件,就是棋盤的約束問題,粉紅色假設為石頭,沒有粉紅色為空地,那我們就是要找到空地,從空地進行行走,下面來編寫這樣的函數。
棋盤假設為grid的二維數組,共有m行,n列。生成的二維數組中1表示石頭,0表示空位。
棋盤生成代碼:
import numpy as np m,n = 8,8 grid = np.zeros((m,n),dtype=np.int) for i in range(m): for j in range(n): if (i==1 and (j==2 or j==6)) or (i==2 and j==4) or (i==3 and (j==0 or j==2 or j==5)) or (i==4 and j==2) or (i==5 and (j==3 or j==4 or j==6)) or (i==6 and (j==1 or j==5)): grid[i][j]=1 print(grid)
輸出:
[[0 0 0 0 0 0 0 0] [0 0 1 0 0 0 1 0] [0 0 0 0 1 0 0 0] [1 0 1 0 0 1 0 0] [0 0 1 0 0 0 0 0] [0 0 0 1 1 0 1 0] [0 1 0 0 0 1 0 0] [0 0 0 0 0 0 0 0]]
判斷該棋盤中某行某列是否是空地:
# 判斷是否是空地 def validSquare(grid,m,n): if grid[m][n]==1: return False return True
4.遞歸法
- 每次先判斷當前是否是空地
- 左上角處理
- 每次遞歸都可以劃分為(1,0)或(0,1)子問題
- 第一行與第一列遞歸
- 中間行列數據遞歸
def countPath(grid,m,n): # 是否為空地,grid中0為空地,1為非空地 if not validSquare(grid,m,n): return 0 # 左上角特殊處理 if (m==0 and n==0): return 0 # (0,1)與(1,0)為1 if ((m == 0 and n == 1) or (m == 1 and n == 0)): return 1 # 第一行,第一列特殊處理 elif ((m == 0) and (n>1)): return countPath(grid, m, n - 1) elif ((n == 0) and (m>1)): return countPath(grid,m-1,n) # 其他則遞歸 return countPath(grid,m, n - 1) + countPath(grid,m - 1, n) # 申請dp數組 dp = np.zeros((m,n),dtype=np.int) print(countPath(grid,m-1,n-1))
5.動態規劃
從左上角到右下角直接使用遞推式,找到動態規劃的狀態轉移方程,然後返回最後的一個數據即可。
我們知道當第一行或者第一列中有石頭的時候,隨之後面的行或者列就走不通了,那麼直接設為0即可。
所以第一步先堆特殊的第一行與第一列進行處理。
然後堆中間行與列處理。
def countPath2(grid,m,n,dp): # 處理每一行 for i in range(m): if not validSquare(grid,i,0): for j in range(i,m): dp[j][0]=0 break dp[i][0]=1 # 處理每一列 for i in range(n): if not validSquare(grid,0,i): for j in range(i,m): dp[0][j]=0 break dp[0][i]=1 # 左上角處理 dp[0][0]=0 for i in range(1,m): for j in range(1,n): if validSquare(grid,i,j): dp[i][j]=dp[i-1][j]+dp[i][j-1] else: dp[i][j]=0 return dp[m-1][n-1]
由於從左上角到右下角與從右下角到左上角路徑對稱,那麼我們可以再寫一個從右下角到左上角來驗證之前的代碼正確性,如果正常,則兩次結果是一樣的!
下面為從右下角到左上角的動態規劃代碼:
def countPath1(grid,m,n,dp): # 處理每一行 for i in range(m-1,-1,-1): if not validSquare(grid,i,n-1): for j in range(i,m): dp[j][n-1]=0 break dp[i][n-1]=1 # 處理每一列 for i in range(n-1,-1,-1): if not validSquare(grid,m-1,i): for j in range(i,m): dp[m-1][j]=0 break dp[m-1][i]=1 dp[m-1][n-1]=0 for i in range(m-2,-1,-1): for j in range(n-2,-1,-1): if validSquare(grid,i,j): dp[i][j]=dp[i+1][j]+dp[i][j+1] else: dp[i][j]=0 return dp[0][0]
