内容一 最长公共子序列
给定两个序列X和Y,基于备忘录方法,编 写程序找出X和Y所有的最长公共子序列
要求:基于备忘录方法
思考:多个LCS如何寻找
分析:时间复杂度
扩展一:动态规划以及备忘录方法和递归的比较
动态规划的基本要素:
1 最优子结构性质
当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2 重叠子问题性质
动态规划算法对每个问题只解一次,将其解保存在一个表格中,当再次需要解此问题时,用常数时间查看一下结果。因此,用动态规划算法通常只需要多项式时间。
备忘录方法: •用一个表格来保存已解决的子问题的答案,用的时候查表即可。 •采用的递归方式是自顶向下。 •控制结构与直接递归相同,区别在于备忘录方式为每个解过的子问题建立备忘录。 •初始化为每个子问题的记录存入一个特殊的值,表示并未求解。在求解过程中,查看相应记录如果是特殊值,表示未求解,否则只要取出该子问题的解答即可。
备忘录方法与动态规划和递归的区别:
1、动态规划是自底向上 ,备忘录方法是自顶向下,递归是自顶向下
2、动态规划每个子问题都要解一次,但不会求解重复子问题;备忘录方法只解哪些确实需要解的子问题;递归方法每个子问题都要解一次,包括重复子问题• 。
递归解法
/**
* @author SJ
* @date 2020/10/22
*/
public class LCS {
//求两个序列的最长公共子序列
public static void main(String[] args) {
String s1="abcde";
String s2="ace";
char[] chars1 = s1.toCharArray();
char[] chars2 = s2.toCharArray();
int lcs = getLcs(chars1.length - 1, chars2.length - 1, chars1, chars2);
System.out.println(lcs);
}
//暴力递归方法
//指针i和j自顶向下对两个子序列进行扫描
public static int getLcs(int i,int j,char[] s1,char[] s2){
//找到最前面还没有找到公共元素
if (i==-1||j==-1)
return 0;
//找到一个公共元素就继续向前找
else if (s1[i]==s2[j])
return getLcs(i-1,j-1,s1,s2)+1;
else
return Math.max(getLcs(i-1,j,s1,s2),getLcs(i,j-1,s1,s2));
}
}
测试结果:
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
3
Process finished with exit code 0
备忘录解法
/**
* @author SJ
* @date 2020/10/27
*/
public class LCS2 {
public static void main(String[] args) {
String s1 = "abcde";
String s2 = "ace";
new LCS2(s1.length() + 1, s2.length() + 1);
int lcs = getLcs(s1.length(), s2.length(), s1.toCharArray(), s2.toCharArray());
System.out.println(lcs);
}
//建立备忘录
public static int[][] memo;
public LCS2(int l1, int l2) {
memo = new int[l1][l2];
initializeMemo();
}
//初始化备忘录
public static void initializeMemo() {
for (int i = 0; i < memo.length; i++) {
for (int i1 = 0; i1 < memo[i].length; i1++) {
memo[i][i1] = -1;
}
}
}
public static int getLcs(int i, int j, char[] s1, char[] s2) {
//递归求解之前先检查备忘录
//之后的过程也是更新备忘录的过程
if (memo[i][j] != -1)
return memo[i][j];
//第一行和第一列不放东西
if (i == 0 || j == 0)
memo[i][j] = 0;
else if (s1[i - 1] == s2[j - 1])
memo[i][j] = getLcs(i - 1, j - 1, s1, s2) + 1;
else
memo[i][j] = Math.max(getLcs(i - 1, j, s1, s2), getLcs(i, j - 1, s1, s2));
return memo[i][j];
}
}
测试结果:
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
3
Process finished with exit code 0
动态规划解法
import java.util.*;
/**
* @author SJ
* @date 2020/10/27
*/
public class LCS3 {
//得到最长公共子序列并输出
//这次采用自底向上的动态规划的方法保存每一个子问题的解,方便回溯
public int[][] dp;//保存每一个子问题的最长公共子序列的长度
public static Set<String> lcs=new HashSet<>();//保存具体的最长公共子序列
public LCS3(int l1, int l2) {
dp = new int[l1 + 1][l2 + 1];
//在构造函数里顺便初始化了
for (int i = 0; i < dp.length; i++) {
for (int i1 = 0; i1 < dp[i].length; i1++) {
if (i == 0 || i1 == 0)
dp[i][i1] = 0;
else
dp[i][i1] = -1;
}
}
// lcs = new HashSet<>();
}
//自底向上更新dp数组
public void getLcs(char[] s1, char[] s2) {
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[i].length; j++) {
if (s1[i - 1] == s2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
//从dp右下角开始回溯,求出最长公共子序列
//从dp右下角开始回溯,求出最长公共子序列
public void traceBack2(int i, int j, List<Character> temp, char[] s1, char[] s2) {
//终止条件
if (temp.size() == dp[s1.length][s2.length]) {
// System.out.println(temp.toString());
lcs.add(temp.toString());
return;
}
while (i > 0 && j > 0) {
if (s1[i - 1] == s2[j - 1]) {
temp.add(0, s1[i - 1]);
i--;
j--;
} else {
if (dp[i - 1][j] > dp[i][j - 1])
i--;
else if (dp[i - 1][j] < dp[i][j - 1])
j--;
else {
traceBack(i - 1, j, temp, s1, s2);
traceBack(i, j - 1, temp, s1, s2);
return;
}
}
}
}
//输出dp数组(测试用)
public void printDp() {
for (int i = 0; i < dp.length; i++) {
for (int i1 = 0; i1 < dp[i].length; i1++) {
System.out.print(dp[i][i1] + " ");
}
System.out.println();
}
}
//输出lcs列表(测试用)
public static void printLcsList() {
System.out.println("最长公共子序列有");
for (String lc : lcs) {
System.out.println(lc);
}
}
public static void main(String[] args) {
String s1 = "ABCBDAB";
String s2 = "BDCABAD";
LCS3 lcs3 = new LCS3(s1.length(), s2.length());
lcs3.getLcs(s1.toCharArray(), s2.toCharArray());
lcs3.printDp();
List<Character> temp = new ArrayList<>();
lcs3.traceBack(s1.length(), s2.length(), temp, s1.toCharArray(), s2.toCharArray());
printLcsList();
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"
0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1
0 1 1 1 1 2 2 2
0 1 1 2 2 2 2 2
0 1 1 2 2 3 3 3
0 1 2 2 2 3 3 4
0 1 2 2 3 3 4 4
0 1 2 2 3 4 4 4
最长公共子序列有
[B, C, B, D]
[B, C, B, A]
Process finished with exit code 0
//dp回溯的时候有点错误,大家自行修改一下 文档是后期上传的,我懒得回去翻正确代码了