最长公共子序列LCS

来源:互联网 发布:x软件下载 编辑:程序博客网 时间:2024/05/22 00:28


最长公共子序列LCS(longest common subSequence):


(一)最长公共子序列(longest common subSequence)与最长公共子串(longest common subString)的区别:
子串:是串的一个连续的部分;
子序列:不改变序列的顺序,从序列中去掉任意的元素而获得的新序列。

(二)举例:
如:串acdfg和串akdfc的最长公共子串为df,最长公共子序列为:adf

(三)方法一:动态规划法:

(1)定义:

设序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一个最长公共子序列Z=<z1, z2, …, zk>,
则:
1. 若xm=yn,则zk=xm=yn 且Zk-1 是Xm-1 和Yn-1 的最长公共子序列;
2. 若xm≠yn 且zk≠xm ,则Z 是Xm-1 和Y 的最长公共子序列;
3. 若xm≠yn 且zk≠yn ,则Z 是X 和Yn-1 的最长公共子序列。

其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>。Zk-1=<z1, z2, …, zk-1>。

(2)递归模型:

1)
用c[i,j]记录序列Xi 和Yj 的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。当
i=0 或j=0 时,空序列是Xi 和Yj 的最长公共子序列,故c[i,j]=0。

2)递归模型如下图:


(3)代码实现:

/*
函数:LCS_LENGTH求的是最大公共子序列的长度。
输入:序列X,序列Y
输出:c[i,j]存储Xi 与Yj 的最长公共子序列的长度、
b[i,j]指示c[i,j]的值是由哪一个子问题的解达到的.在求取具体的最长子序列会用到。
*/
Procedure LCS_LENGTH(X,Y);
begin
 m:=length[X];
 n:=length[Y];
for i:=1 to m do c[i,0]:=0;
for j:=1 to n do c[0,j]:=0;

for i:=1 to m do
 for j:=1 to n do
  if x[i]=y[j] then
  begin
   c[i,j]:=c[i-1,j-1]+1;
   b[i,j]:="↖";
   //为指向左上的字符,在矩阵中[i-1,j-1]就在[i,j]左上。
  end

  else if c[i-1,j]≥c[i,j-1] then
  begin
   c[i,j]:=c[i-1,j];
   b[i,j]:="↑";
  end

  else
  begin
   c[i,j]:=c[i,j-1];
   b[i,j]:="←"
  end;

return(c,b);
end;


2.求取最长公共子序列

从b[m,n]开始,沿着其中的箭头所指的方向在数组b 中搜索。

1)当b[i,j]中遇到"↖"时(意味着xi=yi 是LCS 的一个元素),表示Xi 与Yj 的最长公共子序列是由Xi-1 与Yj-1 的最长公共子序列在尾部加上xi 得到的子序列;
2) 当b[i,j]中遇到"↑"时,表示Xi 与Yj 的最长公共子序列和Xi-1 与Yj 的最长公共子序列相同;
3) 当b[i,j]中遇到"←"时,表示Xi 与Yj 的最长公共子序列和Xi 与Yj-1 的最长公共子序列相同。

/*
目的:LCS获取最长公共子序列;
输入:b,X,lenx,leny;X表示数组X,lenx,leny分别是数组的长度。
b为上面函数中获取的b数组。
输出:最长公共子序列。
*/

Procedure LCS(b,X,i,j);
begin
 if i=0 or j=0 then return;
 if b[i,j]="↖" then
 begin
  LCS(b,X,i-1,j-1);
  print(x[i]); {打印x[i]}
 end
 else if b[i,j]="↑" then LCS(b,X,i-1,j)
 else LCS(b,X,i,j-1);
end;

3.已知X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法LCS_LENGTH 和LCS 计算出的结果如下图所示。


(四)改进:
(1)思想:
在算法LCS_LENGTH 和LCS 中,可进一步将数组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]中哪一个数值元素所确定。

(2)递归模型:
设有二维数组f[i][j] 表示X 的i 位和Y 的j 位之前的最长公共子序列的长度,则有:
f[1][1] = same(1,1)
f[i][j] = max{f[i − 1][j − 1] +same(i,j), f[i − 1][j] ,f[i][j − 1]}
其中,same(a,b)当X 的第a 位与Y 的第b 位完全相同时为“1”,否则为“0”。
f[i][j]中最大的数便是X 和Y 的最长公共子序列的长度,依据该数组回溯,便可找出
最长公共子序列。

(3)代码实现:

import java.util.Random;
public class LCS{
 public static void main(String[] args){
 //设置字符串长度
 int substringLength1 = 20;
 int substringLength2 = 20; //具体大小可自行设置
 // 随机生成字符串
 String x = GetRandomStrings(substringLength1);
 String y = GetRandomStrings(substringLength2);

 Long startTime = System.nanoTime();
 // 构造二维数组记录子问题x[i]和y[i]的LCS 的长度

 int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
 // 动态规划计算所有子问题

 for (int i = substringLength1 - 1; i >= 0; i--){

  for (int j = substringLength2 - 1; j >= 0; j--){

  if (x.charAt(i) == y.charAt(j))
  opt[i][j] = opt[i + 1][j + 1] + 1;
  //参考上文我给的公式。
  else
  opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);
  }
 }

System.out.println("substring1:"+x);
System.out.println("substring2:"+y);
System.out.print("LCS:");

int i = 0, j = 0;
while (i < substringLength1 && j < substringLength2){
 if (x.charAt(i) == y.charAt(j)){
 System.out.print(x.charAt(i));
 i++;
 j++;
 }
 else if (opt[i + 1][j] >= opt[i][j + 1])
 i++;

 else
 j++;
}

Long endTime = System.nanoTime();
System.out.println(" Totle time is " + (endTime - startTime) + " ns");
}

//取得定长随机字符串
public static String GetRandomStrings(int length){
 StringBuffer buffer = new StringBuffer("abcdefghijklmnopqrstuvwxyz");
 StringBuffer sb = new StringBuffer();
 Random r = new Random();
 int range = buffer.length();

 for (int i = 0; i < length; i++){
 sb.append(buffer.charAt(r.nextInt(range)));
 }

 return sb.toString();
}
}


(五)改进:
见程序员编程艺术之求取最长公共子序列LCS问题。

0 0
原创粉丝点击