算导实验五 最长公共子序列

实验要求

        给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列,返回 0 。

        一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

        例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
        两个字符串的公共子序列是这两个字符串所共同拥有的子序列。

        输入:text1 = "abcde", text2 = "ace" 
        输出:3  
        解释:最长公共子序列是 "ace" ,它的长度为 3 。

 

目录

1.实验内容

2.实验目的

3.源码 + 注释

4.算法测试结果

5.实验过程中遇到的困难及收获

6.实验源代码


1.实验内容

        给定两个字符串 text1  和 text2 ,返回这两个字符串的最长公共子序列。如果不存在公共子序列,返回null 。一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

        例如,"ace""abcde" 的子序列,但"aec" 不是"abcde" 的子序列。

        两个字符串的公共子序列是这两个字符串所共同拥有的子序列。

2.实验目的

        用动态规划的方法求解最长公共子序列问题。

3.源码 + 注释

        假设字符串text1text2 的长度分别为mn ,创建m+1n+1 列的二维数组dp ,其中dp[i][j] 表示text1[0:i]text2[0:j] 的最长公共子序列的长度。创建一个mn 列的二维数组path ,其中path[i][j] 表示计算dp[i][j] 时所选择的子问题的最优解。

int[][] path = new int[str1.length()][str2.length()];   //记录路径
int[][] dp = new int[str1.length() + 1][str2.length() + 1];

        考虑动态规划的边界情况,当i=0 时,text1[0:i] 为空,空字符串和任何字符串的最长公共子序列的长度都是0,因此对任意0j≤n ,有dp[0][j]=0 ;当j=0 时,text2[0:j] 为空,同理可得对任意0i≤m ,有dp[i][0]=0 。因此动态规划的边界条件是:当i=0j=0 时,dp[i][j]=0

//初始化:第0行和第0列都是0
for (int i = 0; i < dp.length; i++)
{
    dp[i][0] = 0;
}
for (int i = 0; i < dp[0].length; i++)
{
    dp[0][i] = 0;
}

        考虑dp[i][j] 的计算:当text1[i-1]=text2[j-1] 时,将这两个相同的字符称为公共字符,考虑text1[0:i-1]text2[0:j-1] 的最长公共子序列,再增加一个字符即可得到text1[0:i]text2[0:j] 的最长公共子序列,因此dp[i][j]= dp[i-1][j-1]+1 。当text1[i-1]≠text2[j-1] 时,考虑以下两项text1[0:i]text2[0:j-1] 的最长公共子序列和text1[0:i-1]text2[0:j] 的最长公共子序列,若要得到text1[0:i]text2[0:j] 的最长公共子序列,应取两项中长度较大的一项,因此dp[i][j]=max(dp[i][j-1],dp[i-1][j]) 。最终计算得到的dp[m][n] 即为text1text2 的最长公共子序列的长度。

        在计算dp[i][j] 的过程中,若dp[i][j]= dp[i-1][j-1]+1 ,则path[i-1][j-1]=0 ;若dp[i][j]= dp[i][j-1] ,则path[i-1][j-1]=-1 ;若dp[i][j]= dp[i-1][j] ,则path[i-1][j-1]=1

for (int i = 1; i < dp.length; i++)
{
    for (int j = 1; j < dp[0].length; j++)
    {
        //若对应位置字符串相等
        if (str1.charAt(i - 1) == str2.charAt(j - 1))
        {
            dp[i][j] = dp[i - 1][j - 1] + 1;
        }
        //若对应位置字符串不相等
        else if (dp[i - 1][j] >= dp[i][j - 1])  //上面大 path = 1
        {
            dp[i][j] = dp[i - 1][j];
            path[i - 1][j - 1] = 1;
        }
        else    //左面大 path = -1
        {
            dp[i][j] = dp[i][j - 1];
            path[i - 1][j - 1] = -1;
        }
    }
}

        循环结束之后,dp[m][n] 保存了两个字符串的最长公共子序列的长度,path 中保存了最长公共子序列的路径信息。调用printLCS 方法打印最长公共子序列。在该方法中,如果path[i][j]=0 ,则向左上角走;若path[i][j]=1 ,则向上走;若path[i][j]=-1 ,则向左走。在递归返回的时候打印字符,即可得到正序的字符串输出。

private void printLCS(int[][] path, String str, int i, int j)
{
    if (i == -1 || j == -1)
    {
        return;
    }

    if (path[i][j] == 0)    //左上角
    {
        printLCS(path, str, i - 1, j - 1);
        System.out.printf("%c", str.charAt(i));
    }
    else if (path[i][j] == 1)   //向上走
    {
        printLCS(path, str, i - 1, j);
    }
    else    //向左走
    {
        printLCS(path, str, i, j - 1);
    }
}

4.算法测试结果

text1=”abcde”,text2=”ace” ,算法的输出为

 

5.实验过程中遇到的困难及收获

        通过本实验加深了对动态规划算法的理解,总结了动态规划算法的一般步骤

        1.刻画一个最优解的结构特征

        2.递归的定义最优解的值

        3.计算最优解的值,通常采用自底向上的方法

        4.利用计算出的信息构造一个最优解

        对于LCS问题还有优化的空间:由于每个dp[i][j] 项只依赖于dpi-1jdpij-1dp[i-1][j-1] 三项,给定一个dp[i][j] ,可以在常数时间内判断出在计算dp[i][j] 时使用了三项中的哪一项,因此可以去掉path 数组,而不会影响重构最优解的工作。

6.实验源代码

.LCS.java

/*
 * @Title:
 * @Package
 * @Description:最长公共子序列
 * @author yangf257
 * @date 2021/11/16  19:06
 */

public class LCS
{
    public void longestCommonSequence(String str1, String str2)
    {
        int[][] path = new int[str1.length()][str2.length()];   //记录路径
        int[][] dp = new int[str1.length() + 1][str2.length() + 1];

        //初始化:第0行和第0列都是0
        for (int i = 0; i < dp.length; i++)
        {
            dp[i][0] = 0;
        }
        for (int i = 0; i < dp[0].length; i++)
        {
            dp[0][i] = 0;
        }

        for (int i = 1; i < dp.length; i++)
        {
            for (int j = 1; j < dp[0].length; j++)
            {
                //若对应位置字符串相等
                if (str1.charAt(i - 1) == str2.charAt(j - 1))
                {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                //若对应位置字符串不相等
                else if (dp[i - 1][j] >= dp[i][j - 1])  //上面大 path = 1
                {
                    dp[i][j] = dp[i - 1][j];
                    path[i - 1][j - 1] = 1;
                }
                else    //左面大 path = -1
                {
                    dp[i][j] = dp[i][j - 1];
                    path[i - 1][j - 1] = -1;
                }
            }
        }
        System.out.println("最长长度为" + dp[str1.length()][str2.length()]);
        System.out.print("最长公共子序列为");
        printLCS(path, str1, str1.length() - 1, str2.length() - 1);
    }

    private void printLCS(int[][] path, String str, int i, int j)
    {
        if (i == -1 || j == -1)
        {
            return;
        }

        if (path[i][j] == 0)    //左上角
        {
            printLCS(path, str, i - 1, j - 1);
            System.out.printf("%c", str.charAt(i));
        }
        else if (path[i][j] == 1)   //向上走
        {
            printLCS(path, str, i - 1, j);
        }
        else    //向左走
        {
            printLCS(path, str, i, j - 1);
        }
    }
}

Test.java

/*
 * @Title:
 * @Package
 * @Description:测试类
 * @author yangf257
 * @date 2021/11/16  19:35
 */

public class Test
{
    public static void main(String[] args)
    {
        String str1 = "abcde";
        String str2 = "ace";

        new LCS().longestCommonSequence(str1, str2);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值