最长公共子序列(LCS)问题

/**
 * 第一步:论证是否是动态规划问题
 * 首先要证明最长公共子序列问题是动态规划问题,即符合动态规划算法的两个特点:最优子结构和重叠子问题
 * 最优子结构:
 * 记:Xi=﹤x1...xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)        Yj=﹤y1...yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)
 * 假定Z=﹤z1...zk﹥∈LCS(X , Y)。
 * 若xm=yn(最后一个字符相同),则问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。
 * 若xm≠yn,则问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。
 * 由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。
 * 另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质。
 * 重叠子问题:
 * 在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列,
 * 而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列,因此最长公共子序列问题具有子问题重叠性质。
 *
 * 第二步:建立递归式
 * 用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。建立递归关系如下:
 *            0                 if i=0||j=0
 * c[i][j]=   c[i-1][j-1]+1     if i,j>0&&x[i]==y[j]
 *            max(c[i][j-1],c[i-1][j]) if i,j>0&&x[i]!=y[j];
 */
public class LCS {


    /**第三步:计算最优值
     * 计算最长公共子序列长度的动态规划算法LCS_LENGTH(X,Y)以序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>作为输入。
     * 输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。其中c[i,j]存储Xi与Yj的最长公共子序列的长度,b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,
     * 这在构造最长公共子序列时要用到。最后,X和Y的最长公共子序列的长度记录于c[m,n]中。
     * 在这里可以将数组b省去。事实上,数组元素c[i,j]的值仅由c[i-1,j-1],c[i-1,j]和c[i,j-1]三个值之一确定,而数组元素b[i,j]也只是用来指示c[i,j]究竟由哪个值确定。
     * 因此,在算法LCS中,我们可以不借助于数组b而借助于数组c本身临时判断c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一个数值元素所确定,代价是Ο(1)时间。
     * @param x
     * @param y
     * @return
     */
    public int[][] lcsLength(char x[],char y[]){
        int m = x.length;
        int n = y.length;
        int [][]c = new int[m+1][n+1];
        for(int i = 0;i<m+1;i++)
            c[i][0]=0;
        for(int j = 0;j<n+1;j++)
            c[0][j]=0;
        for(int i = 1;i<=m;i++){
            for(int j = 1;j<=n;j++){
                //i,j从1开始,所以下面用i-1和j-1使得可以从数组0元素开始
                if(x[i-1]==y[j-1]){
                    c[i][j] = c[i-1][j-1]+1;
                }else if(c[i-1][j]>=c[i][j-1]){
                    c[i][j]=c[i-1][j];
                }else{
                    c[i][j]=c[i][j-1];
                }
            }
        }
        return c;
    }
    /**第四步:构造最长公共子序列
     * lcs函数用来构造最长公共子序列,它使用在计算最优值中得到的c数组可以快速的
     * 构造序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列。
     * @param c
     * @param x
     * @param i
     * @param j
     */
    public void lcs(int c[][],char x[],int i,int j){
        if(i==0||j==0)return;
        if(c[i][j]==(c[i-1][j-1]+1)){
            lcs(c,x,i-1,j-1);
            //注意c的长度要比x大1
            System.out.println(x[i-1]);
        }else if(c[i][j]==c[i-1][j]){
            lcs(c,x,i-1,j);
        }else{
            lcs(c,x,i,j-1);
        }
    }
    //测试
    public static void main(String args[]){
        char x[]={'A','B','C','B','D','A','B'};
        char y[]={'B','D','C','A','B','A'};
        LCS lcs = new LCS();
        int [][]c = lcs.lcsLength(x, y);
        lcs.lcs(c, x, x.length, y.length);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值