最长公共子序列(LCS)

分手后的思念是犯贱 2022-09-29 15:52 280阅读 0赞

这两天编程涉及到求两个字符串的最长公共子序列问题,于是便重新复习之前一直没弄懂的最长公共子序列算法,也算是弄懂了一点。

算法分析:

采用动态规划方法来解决问题,将最长公共子序列问题转变为较小的问题。设两个字符串分别为 A=”a(0),a(1),…,a(m-1)”,B=”b(0),b(1),…,b(n-1)”,设Z=”z(0),z(1),…,z(k-2)”为它们的最长公共子序列。很容易看出:

(1)如果a(m-1)=b(m-1),则z(k-1)=a(m-1)=b(m-1),且”z(0),z(1),…,z(k-2)”是”a(0),a(1),…,a(m-2)”和”b(0),b(1),…,b(n-2)”的一个最长公共子序列;

(2)如果a(m-1)!=b(m-1),则若z(k-1)!=a(m-1),蕴含”z(0),z(1),…,z(k-1)”是”a(0),a(1),…,a(m-2)”和”b(0),b(1),…,b(n-1)”的一个最长公共子序列;

(3)如果a(m-1)!=b(m-1),则若z(k-1)!=b(m-1),蕴含”z(0),z(1),…,z(k-1)”是”a(0),a(1),…,a(m-1)”和”b(0),b(1),…,b(n-2)”的一个最长公共子序列。

使用一个(m+1)*(n+1)de二维数组c,c[i][j]存储序列”a(0),a(1),…,a(m-1)”和B=”b(0),b(1),…,b(n-1)”的最长公共子序列的长度,由递推关系可得递推关系式:

(1)c[i][j]=0,若i=0或j=0;

(2)c[i][j]=c[i-1][j-1]+1,若i,j>0且a[i-1]=b[j-1];

(3)c[i][j]=max(c[i][j-1],c[i-1][j]),若i,j>0且a[i-1]!=b[j-1]。

最终c[i][j]存储最长公共子序列的长度。















































































































下标j

 

上标i

0

1

2

3

4

5

6

yj

B

D

C

A

B

A

0

xi

0

0

0

0

0

0

0

1

A

0

0

0

0

1

1

1

2

B

0

1

1

1

1

2

2

3

C

0

1

1

2

2

2

2

4

B

0

1

1

2

2

3

3

5

D

0

1

2

2

2

3

3

6

A

0

1

2

2

3

3

4

7

B

0

1

2

2

3

4

4

代码实现如下:

  1. public static int compute(char[] str1, char[] str2)
  2. {
  3. int substringLength1 = str1.length;
  4. int substringLength2 = str2.length;
  5. // 构造二维数组记录子问题A[i]和B[j]的LCS的长度
  6. int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
  7. // 从后向前,动态规划计算所有子问题。也可从前到后。
  8. for (int i = substringLength1 - 1; i >= 0; i--)
  9. {
  10. for (int j = substringLength2 - 1; j >= 0; j--)
  11. {
  12. if (str1[i] == str2[j])
  13. opt[i][j] = opt[i + 1][j + 1] + 1;// 状态转移方程
  14. else
  15. opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);// 状态转移方程
  16. }
  17. }
  18. System.out.println("substring1:" + new String(str1));
  19. System.out.println("substring2:" + new String(str2));
  20. System.out.print("LCS:");
  21. int i = 0, j = 0;
  22. while (i < substringLength1 && j < substringLength2)
  23. {
  24. if (str1[i] == str2[j])
  25. {
  26. System.out.print(str1[i]);
  27. i++;
  28. j++;
  29. }
  30. else if (opt[i + 1][j] >= opt[i][j + 1])
  31. i++;
  32. else
  33. j++;
  34. }
  35. System.out.println();
  36. return opt[0][0];
  37. }
  38. public static int compute(String str1, String str2)
  39. {
  40. return compute(str1.toCharArray(), str2.toCharArray());
  41. }

将该算法进行改进,实现求两个字符串的最长公共子序列,此时两个字符串的子序列不仅仅是一个字母,而有可能是一个英文字符串。如:[ai]man*jian*shen*和[ai]man*dian*shen*tai*yuan*jian*。首先将两个字符串分别通过正则式切割为多个字符串,并存入list集合,然后应用改进的LCS算法求这两个字符串的“最长公共子序列”。



























































































下标j

 

上标i

0

1

2

3

4

strj

ai

man

jian

shen

0

stri

0

0

0

0

0

1

ai

0

1

1

1

1

2

man

0

1

2

2

2

3

dian

0

1

2

2

2

4

shen

0

1

2

2

3

5

tai

0

1

2

2

3

6

yuan

0

1

2

2

3

7

jian

0

1

2

3

3

java代码如下:

  1. /*
  2. *求两个字符串的最长公共子序列(拼音分隔后存入list,然后求其最长公共子序列。
  3. */
  4. public static int LCS(String str1,String str2){
  5. String [] sub1 = str1.split("[*|?|\\[|\\]]");
  6. String [] sub2 = str2.split("[*|?|\\[|\\]]");
  7. List<String> strList1 = new ArrayList<>();
  8. List<String> strList2 = new ArrayList<>();
  9. for(String s:sub1){
  10. if(!s.equals("") && !s.equals("-")){
  11. strList1.add(s);
  12. }
  13. }
  14. for(String s:sub2){
  15. if(!s.equals("") && !s.equals("-")){
  16. strList2.add(s);
  17. }
  18. }
  19. int substringLength1 = strList1.size();
  20. int substringLength2 = strList2.size();
  21. // 构造二维数组记录子问题A[i]和B[j]的LCS的长度
  22. int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
  23. // 从后向前,动态规划计算所有子问题。也可从前到后。
  24. for (int i = strList1.size() - 1; i >= 0; i--)
  25. {
  26. for (int j = strList2.size() - 1; j >= 0; j--)
  27. {
  28. if (strList1.get(i).equals(strList2.get(j)))
  29. opt[i][j] = opt[i + 1][j + 1] + 1;// 状态转移方程
  30. else
  31. opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);// 状态转移方程
  32. }
  33. }
  34. System.out.println("substring1:" + new String(str1));
  35. System.out.println("substring2:" + new String(str2));
  36. System.out.print("LCS:");
  37. int i = 0, j = 0;
  38. while (i < substringLength1 && j < substringLength2)
  39. {
  40. if (strList1.get(i).equals(strList2.get(j)))
  41. {
  42. System.out.print(strList1.get(i));
  43. i++;
  44. j++;
  45. }
  46. else if (opt[i + 1][j] >= opt[i][j + 1])
  47. i++;
  48. else
  49. j++;
  50. }
  51. System.out.println();
  52. return opt[0][0];
  53. }
  54. public static void main(String[] args) {
  55. String str1 = "[ai]man*jian*shen*";
  56. String str2 = "[ai]man*dian*shen*tai*yuan*jian";
  57. System.out.println(LCS(str1,str2));
  58. } *求两个字符串的最长公共子序列(拼音分隔后存入list,然后求其最长公共子序列。
  59. */
  60. public static int LCS(String str1,String str2){
  61. String [] sub1 = str1.split("[*|?|\\[|\\]]");
  62. String [] sub2 = str2.split("[*|?|\\[|\\]]");
  63. List<String> strList1 = new ArrayList<>();
  64. List<String> strList2 = new ArrayList<>();
  65. for(String s:sub1){
  66. if(!s.equals("") && !s.equals("-")){
  67. strList1.add(s);
  68. }
  69. }
  70. for(String s:sub2){
  71. if(!s.equals("") && !s.equals("-")){
  72. strList2.add(s);
  73. }
  74. }
  75. int substringLength1 = strList1.size();
  76. int substringLength2 = strList2.size();
  77. // 构造二维数组记录子问题A[i]和B[j]的LCS的长度
  78. int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
  79. // 从后向前,动态规划计算所有子问题。也可从前到后。
  80. for (int i = strList1.size() - 1; i >= 0; i--)
  81. {
  82. for (int j = strList2.size() - 1; j >= 0; j--)
  83. {
  84. if (strList1.get(i).equals(strList2.get(j)))
  85. opt[i][j] = opt[i + 1][j + 1] + 1;// 状态转移方程
  86. else
  87. opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);// 状态转移方程
  88. }
  89. }
  90. System.out.println("substring1:" + new String(str1));
  91. System.out.println("substring2:" + new String(str2));
  92. System.out.print("LCS:");
  93. int i = 0, j = 0;
  94. while (i < substringLength1 && j < substringLength2)
  95. {
  96. if (strList1.get(i).equals(strList2.get(j)))
  97. {
  98. System.out.print(strList1.get(i));
  99. i++;
  100. j++;
  101. }
  102. else if (opt[i + 1][j] >= opt[i][j + 1])
  103. i++;
  104. else
  105. j++;
  106. }
  107. System.out.println();
  108. return opt[0][0];
  109. }
  110. public static void main(String[] args) {
  111. String str1 = "[ai]man*jian*shen*";
  112. String str2 = "[ai]man*dian*shen*tai*yuan*jian";
  113. System.out.println(LCS(str1,str2));
  114. }

运行结果如下:

Center

今天特意记录下来,以便以后复习。如有错误之处,欢迎指正。

发表评论

表情:
评论列表 (有 0 条评论,280人围观)

还没有评论,来说两句吧...

相关阅读

    相关 公共序列LCS问题

    好久没有写博客了,刚才在网上看了清华大学的数据结构公开课,链接:https://www.xuetangx.com 可以注册个账号去听数据结构课程,老师讲的特好。 我的代码是按

    相关 LCS 公共序列

    首先要明白什么是子序列,什么是子串; 设:主串长度为n; 子序列:从主串中抽出少于n的元素组成的序列(这些抽出的元素比一定是连续的他们的相对位置不变);