動態規劃理解

馬爾科夫模型

對於Ai+1,只需要考察前一個狀態Ai即可,只要狀態Ai確定,則計算Ai+1時不需要考察前面的的狀態A1…Ai-1,在圖論中,常稱之爲馬爾科夫模型

高階馬爾科夫模型

對於Ai+1,需要考察前面i個狀態集{A1…Ai-1Ai}才能完成整個推理過程,稱之爲高階馬爾科夫模型。
在計算機算法中,高階馬爾科夫模型的推理叫做“動態規劃”,馬爾科夫模型的推理,對應“貪心法”。

動態規劃

無論是動態規劃還是貪心算法,都是根據A0…i,計算Ai+1的過程。

  • 計算Ai+1不需要Ai+2…Ai+7…
  • 一旦Ai+1計算完成之後,後面就不需要再次計算Ai+2、Ai+3…,並且Ai+1的值不會更改了
  • 就叫做無後效性
    後者這樣理解動態規劃:計算Ai+1,只需要知道A[0…i]的值,無需知道A[0…i]是通過何種途徑計算得到的——只需要知道它們當前的狀態值本身。如果A[0…i]的全體作爲一個整體,可以認爲動態規劃是馬爾科夫過程,而非高階馬爾科夫模型。

例1:最長遞增子序列LIS,給定一個長度爲N的數組,找出一個最長單調的自增子序列,不要求是連續的。例如:6 5 7 8 4 3 9 1,最長遞增子序列是{ 6, 7, 8, 9}{5, 7, 8, 9}。可以看出最長遞增子序列不唯一,但是長度一定是唯一的,不然也不能叫最長遞增子序列。
記以第i個數組元素ai結尾的最長遞增子序列的長度爲dp[i],子序列記爲Li,ai是要綴到L0L1…Li-1的後面的條件:

  • 如果aj<ai,則可以將ai綴到Lj後面,使得Lj長度增加
    故:dp[i]={max(dp[j]+1,0<=j<i並且a[i]<=a[j])}
  • 遍歷在i之前所有的位置,找出滿足條件的a[i]<=a[j]的a[j]
  • 計算得到dp[0…n-1]後,遍歷所有的dp[i],找出最大的長度
  • dp[i] 默認都爲 1,因爲以 i 結尾的 LIS 至少包含自己。
    (1) 6 5 7 8 4 3 9 1 dp[0]=1
    (2)6 5 7 8 4 3 9 1 處理第二個元素5的時候判斷是否比前面的元素 6大,沒有的話那麼以 5 爲結尾的 LIS 就是 5本身,即 LIS 長度爲 1,dp[1]=1
    (3)6 5 7 8 4 3 9 1 處理第三個元素7的時候需要跟前面的每個元素都進行比較,7大於 6,5 則 LIS 的長度可能爲 dp[2]={max(dp[0]+1,dp[1]+1)} =2,以此類推,得到最長序列爲4。
void LIS()
{
    for(int i = 0; i < n; i++)//初始化dp[i] = 1;
    {
        dp[i] = 1;
    }
    for(int i = 1; i < n; i++)//遍歷
    {
        for(int j = 0; j < i; j++)//從i前面的查找
        {
            if(a[i] > a[j] && dp[i] < dp[j] + 1)//dp[i] < dp[j] + 1的作用就是更新最大的一個遞增
                dp[i] = dp[j] + 1;//max(dp[j]+1,0<=j<i並且a[i]<=a[j])
        }
    }
}

何時考慮使用動態規劃

  • 初始規模下能夠方便得出結論:空串、長度爲0的數組、自身(LIS=1)等——初始條件
  • 能夠得到問題規模增大導致的變化:遞推式——狀態轉移方程
  • 無後效性

例2:字符串的交替連接,輸入三個字符串s1、s2和s3,判斷s3是否是前兩個字符串交錯而成,s1∪s2=s3。
狀態轉移函數:

  • dp[i,j]表示s3[1…i+j]是否由s1[1…i]和s2[1…j]的字符組成,取值爲true/false
  • 如s1[i]==s3[i+j],且dp[i-1,j]爲真,那麼dp[i,j]爲真,因爲s1和s3末尾相等,追上一個s1[i]還可以相等
  • 如s2[j]==s3[i+j],且dp[i,j-1]爲真,那麼dp[i,j]爲真,因爲s2和s3末尾相等,追上一個s2[j]還可以相等
  • 其他情況,dp[i,j]爲假
    初始條件:dp[0,0]=true,空串可以由空串組成
 */
    bool isInterleave(string s1, string s2, string s3) {
    	int n=s1.length(),m=s2.length(),s=s3.length();
    	//如果長度不一樣,則s1∪s2不可能爲s3
        if(s!=m+n)  
            return false;  
        if(n==0)  
            return s2==s3;  
        if(m==0)  
            return s1==s3;  
        vector<vector<bool> > dp(n+1,vector<bool>(m+1,false));  
        dp[0][0] = true; //空串可以由空串組成 
        for(int i=0;i<n+1;i++)  
        {  
            for(int j=0;j<m+1;j++)  
            {  
            	if(i-1>=0&&dp[i-1][j]==true&&s1.charAT(i-1)==s3.charAT(i+j-1)||j-1>=0&&dp[i][j-1]==true&&s2.charAT(j-1)==s3.charAT(i+j-1))
                	dp[i][j]=true; 
                else
                	dp[i][j]=false; 
                   
            }  
        }  
        return dp[n][m];  

例3:走棋盤/格子取數,給定m*n的矩陣,每個位置是一個非負數,從左上角開始,每次只能朝下和右邊走,走到右下角,求總和最小的路徑。
初始條件:dp[0][0]=a[0][0]
狀態轉移函數:dp[x][y]=max(dp[x-1][y]+a[x][y],dp[x][y-1]+a[x][y]),即dp[x][y]=max(dp[x-1][y],dp[x][y-1])+a[x][y]

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