马尔科夫模型
对于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]