经典算法学习_动态规划_最长公共子序列

前几天做笔试题,遇到最长递增子序列的问题,不知怎么解,看网上有一种先将该序列排序,以转化为求新序列与原有序列的最长公共子串。然而,最长公共子序列也!不!会!
这两个算法在大学时都应该是接触过的,到此时居然已经忘的一干二净,才知道自己究竟差了多少功夫。于是先学一下最长公共子序列的解法,在这里记下来,待下次再不会做时也好有个查阅的好地方。最长递增子序列的解法改天再学。


设有字符串a,b。
记a(0,i)为a从0位到i位的子串。
使用二维数组c来记录最长公共子序列长度,c[i][j]表示a(0,i)与b(0,j)的最长公共子序列长度。设c[-1][j] = c[i][-1] = 0,不论 i,j 取何值。

动态规划的思路如下:
1)若a[i] == b[j],则c[i][j] = c[i-1][j-1] + 1
2)若a[i] != b[j],则c[i][j] = max{c[i-1][j], c[i][j-1]}

在其后构造最长公共子序列时,则逆向使用求最长公共子序列长度时的数据。首先初始化空的字符串。
从表c右下角开始循环,记c[i][j],每次在下列三个表项中查找最大值
a) c[i-1][j]
b) c[i][j-1]
c) c[i-1][j-1]
若a)最大,则令 i = i - 1
若b)最大,则令 j = j - 1
若c)最大,则在字符串前插入a[i]的字符,而后令 i = i - 1,j = j - 1
其中可能出现 a),b) 两数值相等,则说明有两种构造最长公共子序列的方式,这两种子序列可能相同,也可能不同。
为了简化这个过程,也可以构造二维数组d,来存储构造最长公共子序列时,i,j 的调整方向。
二维数组c的示意图如下,来自算法导论第三版。
二维数组c的示意图,来自算法导论第三版
写练习代码如下,未仔细推敲,大致应该或许没有错…

public class LCS 
{
    public static void main(String[] args)
    {
        String strA = "ABCBDABDEWKJFIEWJFDJKSLVJIWE";
        String strB = "BDCABAFJEWKOCDJNIFJWEKFLWE";
        LCS aLCS = new LCS();
        aLCS.calLength(strA, strB);
        System.out.println("The LCS = \""+aLCS.getLCS(strA, strB)+"\"");
    }

    public int calLength(String a, String b)
    {
        //获得初始化表的长度,将字符串的长度加1,
        //作为表的第一行和第一列全部为0的数据,
        //以统一运算,避免在循环中加入条件判断语句。
        int lenA = a.length() + 1;
        int lenB = b.length() + 1;
        //第i行第j列的数值表示a(0,i-1)和b(0,j-1)的最大匹配长度
        lengthTab = new int[lenA][lenB];
        //第i行第j列的数值表示a(0,i-1)和b(0,j-1)的最大匹配长度根据哪个单元获得,具体数值见实例域定义
        directTab = new int[lenA][lenB];

        //按行开始遍历
        for(int i=1; i<lenA; i++)
        {
            //对第i行的每一个单元
            for(int j=1; j<lenB; j++)
            {
                //若a字符串的第i-1个字符和b字符串的第j-1个字符相同
                //则说明a(0,i-1)和b(0,j-1)的最大匹配长度是a(0,i-2)和b(0,j-2)匹配长度加1
                if(a.charAt(i-1) == b.charAt(j-1))
                {
                    directTab[i][j] = this.TOP_AND_LEFT;
                    lengthTab[i][j] = lengthTab[i-1][j-1] + 1; 
                }
                else
                {
                    //若a字符串的第i-1个字符和b字符串的第j-1个字符不同
                    //则说明a(0,i-1)和b(0,j-1)的最大匹配长度是a(0,i-2)和b(0,j-2)最大匹配长度相同
                    if(lengthTab[i-1][j] > lengthTab[i][j-1])
                    {
                        directTab[i][j] = this.TOP;
                        lengthTab[i][j] = lengthTab[i-1][j]; 
                    }
                    else if(lengthTab[i-1][j] < lengthTab[i][j-1])
                    {
                        directTab[i][j] = this.LEFT;
                        lengthTab[i][j] = lengthTab[i][j-1]; 
                    }
                    else
                    {
                        directTab[i][j] = this.TOP_OR_LEFT;
                        lengthTab[i][j] = lengthTab[i-1][j]; 
                    }
                }
            }
        }
//      System.out.println("LengthTab:");
//      printTab(lengthTab);
//      System.out.println("DirectTab:");
//      printTab(directTab);
        return lengthTab[lenA-1][lenB-1];
    }

    public String getLCS(String a, String b)
    {
        StringBuilder sb = new StringBuilder();
        if(directTab==null)
        {
            return "";
        }
        //检验directTab行数和列数是否与字符串长度匹配
        if(directTab.length != a.length() + 1)
            return "";
        if(directTab.length <= 0 || directTab[0].length != b.length() + 1)
            return "";

        int lenA = directTab.length - 1;
        int lenB = directTab[0].length - 1;

        //在表中查找使匹配长度最大的路线(其中一条)
        while(lenA > 0 && lenB > 0)
        {
            //若该单元记录为TOP_AND_LEFT,说明在此最大匹配子串的构造中,包含a.charAt(lenA-1)
            if(directTab[lenA][lenB] == this.TOP_AND_LEFT)
            {
                sb.insert(0, a.charAt(lenA-1));
                lenA--;
                lenB--;
            }
            //若此最大匹配子串的构造中,不包含a.charAt(lenA-1),寻找下一个包含在子串中的字符
            else if(directTab[lenA][lenB] == this.TOP)
            {
                lenA--;
            }
            else if(directTab[lenA][lenB] == this.LEFT)
            {
                lenB--;
            }
            else
            {
                //
                //存在多个最长公共子序列,假设取左
                lenB--;
            }
        }
        return sb.toString();
    }

    public void printTab(int[][] tab)
    {
        for(int[] line : tab)
        {
            for(int element : line)
            {
                System.out.printf("%4d ", element);
            }
            System.out.println();
        }
    }

    private int[][] lengthTab = null;
    private int[][] directTab = null;

    //表示这一个单元的最大匹配长度是从上一行同一列获得
    public static final int TOP = -1;           
    //表示这一个单元的最大匹配长度是从前一列同一行获得
    public static final int LEFT = 0;           
    //表示这一个单元的最大匹配长度是左上角单元数值加1获得
    public static final int TOP_AND_LEFT = 1;   
    //表示这一个单元的最大匹配长度同其左侧及上方的单元都一样,
    //TOP_OR_LEFT原为找到所有最长公共子序列而设,在这个例子中没有实际使用,也未经测试是否有效,
    //为避免有不同路径的相同匹配子序列,可使用TreeSet等集合返回查找结果,
    //但需要注意克隆方法,以及String类的Comparable接口实现情况。
    public static final int TOP_OR_LEFT = 2;    
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值