动态规划入门之最长公共子序列(LCS)
2014-05-12 20:31
369 查看
LCS是动态规划在字符串问题中应用的典型。问题描述:给定2个序列,求这两个序列的最长公共子序列,不要求子序列连续。例如{2,4,3,1,2,1}和{1,2,3,2,4,1,2}的结果是{2,3,2,1}或者{2,4,1,2}。
思路:如果不用动态规划去做,而用暴力法,则必须找出其中一个序列的所有子序列(LIS如果用暴力法思路也是如此),然后判断这个子序列是不是另外一个序列的子序列。判断一个序列是不是另一个序列的子序列可以在O(n)内解决(只需要两个指针,然后依次比较并移动),但是怎么去找一个序列的所有子序列呢,就是就这个集合的所有子集,这是指数级别的复杂度。如果用动态规划去做呢?我们可以用d[i][j]表示序列a[0~i]和序列b[0~j]的最长公共子序列的长度,这是状态;那么状态转移方程:
d[i][j]=d[i-1][j-1]+1 if a[i]=b[j]; and d[i][j]=max{d[i][j-1],d[i-1][j]} if a[i] != b[j]。也就是说,如果i和j上的元素相等,那么d[i-1][j-1]+1就是d[i][j],否则,就要看d[i-1][j]和d[i][j-1]谁大了。在这里面,状态用一个二维数组表示,也就是一个矩阵。我这里引用别人的一张图帮助理解:
上图中序列分别为{B,D,C,A,B,A}和{A,B,C,B,D,A,B}。里面包含了所有的状态。从上图可以知道,每个状态都可以从它的左上、左、或者上方走过来。具体的条件就是上面说的a[i]是否和b[j]相等。我们如果要求出最终的最长公共子序列,就需要得到上面的状态图,然后从图的右下角,根据路径回溯到左上角就行了。
接下来给出JAVA实现的LCS代码:
为了能打印出LCS,需要继续每次状态转移时候选择的路径,代码里用path这个二维数组记录,最后根据这个记录打印结果,这里打印的结果是反的,要想正向打印可以参照代码中说的方法。另外:左和上这两个方向存在优先级关系,优先级不同,打印出来的结果可能不同,但都是问题的解(解不唯一)。
假设序列长度分别为m和n,那么计算矩阵的复杂度为O(m*n),打印的复杂度为 O(m+n)。
如有错误,请多多指出~
思路:如果不用动态规划去做,而用暴力法,则必须找出其中一个序列的所有子序列(LIS如果用暴力法思路也是如此),然后判断这个子序列是不是另外一个序列的子序列。判断一个序列是不是另一个序列的子序列可以在O(n)内解决(只需要两个指针,然后依次比较并移动),但是怎么去找一个序列的所有子序列呢,就是就这个集合的所有子集,这是指数级别的复杂度。如果用动态规划去做呢?我们可以用d[i][j]表示序列a[0~i]和序列b[0~j]的最长公共子序列的长度,这是状态;那么状态转移方程:
d[i][j]=d[i-1][j-1]+1 if a[i]=b[j]; and d[i][j]=max{d[i][j-1],d[i-1][j]} if a[i] != b[j]。也就是说,如果i和j上的元素相等,那么d[i-1][j-1]+1就是d[i][j],否则,就要看d[i-1][j]和d[i][j-1]谁大了。在这里面,状态用一个二维数组表示,也就是一个矩阵。我这里引用别人的一张图帮助理解:
上图中序列分别为{B,D,C,A,B,A}和{A,B,C,B,D,A,B}。里面包含了所有的状态。从上图可以知道,每个状态都可以从它的左上、左、或者上方走过来。具体的条件就是上面说的a[i]是否和b[j]相等。我们如果要求出最终的最长公共子序列,就需要得到上面的状态图,然后从图的右下角,根据路径回溯到左上角就行了。
接下来给出JAVA实现的LCS代码:
/** * * @author kerry * 求两个序列的最长公共子序列 */ public class LCS { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int[] a={2,4,3,1,2,1}; int[] b={1,2,3,2,4,1,2}; int out=lcsSolution(a,b); System.out.println(out); } /*计算状态矩阵:复杂度为O(n)*/ public static int lcsSolution(int[] a,int[] b){ int len1=a.length; int len2=b.length; if(len1==0||len2==0)return 0; int[][] status=new int[len1+1][len2+1]; for(int i=0;i<=len1;i++){ for(int j=0;j<=len2;j++){ status[i][j]=0; } } int[][] path=new int[len1][len2]; //status[i][j]表示0~i和0~j这两个序列的最长公共子序列的长度,也就是状态 for(int i=0;i<=len1;i++){ for(int j=0;j<=len2;j++){ if(i==0||j==0)status[i][j]=0; //下面是状态转移 else if(a[i-1]==b[j-1]){ status[i][j]=status[i-1][j-1]+1;//斜方向 path[i-1][j-1]=1; } else { if(status[i][j-1]>status[i-1][j]){ status[i][j]=status[i][j-1];//左方向 path[i-1][j-1]=2; } else{ status[i][j]=status[i-1][j];//上方向 path[i-1][j-1]=3; } } } } printAns(path,a); return status[len1][len2]; } //打印结果,这里打印的结果是反的,如果要想正向打印,可以利用递归的方法,类似于从尾到头打印单链表的做法,也可以用栈 public static void printAns(int[][] path,int[] a){ int len1=path.length; int len2=path[0].length; int i=len1-1; int j=len2-1; while(j>=0&&i>=0){ if(path[i][j]==1){ System.out.print(a[i]+"->"); i--;j--; } else if(path[i][j]==2){ j--; } else i--; } System.out.println(); } }
为了能打印出LCS,需要继续每次状态转移时候选择的路径,代码里用path这个二维数组记录,最后根据这个记录打印结果,这里打印的结果是反的,要想正向打印可以参照代码中说的方法。另外:左和上这两个方向存在优先级关系,优先级不同,打印出来的结果可能不同,但都是问题的解(解不唯一)。
假设序列长度分别为m和n,那么计算矩阵的复杂度为O(m*n),打印的复杂度为 O(m+n)。
如有错误,请多多指出~
相关文章推荐
- 动态规划入门之LCS(2)
- 【经典问题】二维动态规划问题:求最长公共子序列LCS
- 动态规划 : LCS(最长公共子序列)
- 动态规划 最长公共子序列LCS、最长公共连续子串、最长重复子串
- 动态规划入门策略—“最长公共子序列”
- 动态规划——最长公共子序列(LCS)
- 转【算法之动态规划(三)】动态规划算法之:最长公共子序列 & 最长公共子串(LCS)&字符串相似度算法
- 动态规划之最长公共子序列 (LCS )
- 动态规划之最长公共子序列(LCS)问题
- 基于动态规划的最长公共子序列实现(LCS)
- 动态规划求最长公共子序列(Longest Common Subsequence, LCS)
- 动态规划:最长公共子序列(LCS)
- 动态规划_最长公共子序列(LCS)
- 动态规划之最长公共子序列(LCS)
- 动态规划解最长公共子序列问题(LCS)C语言加注释
- 动态规划实现最长公共子序列(LCS)算法
- 动态规划5:LCS最长公共子序列问题
- 算法:动态规划——最长公共子序列(LCS)
- 动态规划 LCS 求两个序列A,B中全部的最长公共子序列
- 【算法之动态规划(三)】动态规划算法之:最长公共子序列 & 最长公共子串(LCS),字符串相似度算法