最长公共子序列(LCS)

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

算法分析:

采用动态规划方法来解决问题,将最长公共子序列问题转变为较小的问题。设两个字符串分别为 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

 

 

 

 

 

 

 

 

 

 

代码实现如下:

 

	 public static int compute(char[] str1, char[] str2)
    {
        int substringLength1 = str1.length;
        int substringLength2 = str2.length;
	 
        // 构造二维数组记录子问题A[i]和B[j]的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 (str1[i] == str2[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:" + new String(str1));
        System.out.println("substring2:" + new String(str2));
        System.out.print("LCS:");
 
        int i = 0, j = 0;
        while (i < substringLength1 && j < substringLength2)
        {
            if (str1[i] == str2[j])
            {
                System.out.print(str1[i]);
                i++;
                j++;
            }
            else if (opt[i + 1][j] >= opt[i][j + 1])
                i++;
            else
                j++;
        }
        System.out.println();
        return opt[0][0];
    }
 
    public static int compute(String str1, String str2)
    {
        return compute(str1.toCharArray(), str2.toCharArray());
    }

 

 

 

将该算法进行改进,实现求两个字符串的最长公共子序列,此时两个字符串的子序列不仅仅是一个字母,而有可能是一个英文字符串。如:[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代码如下:

 

 /*
	 *求两个字符串的最长公共子序列(拼音分隔后存入list,然后求其最长公共子序列。
	 */
    public static int LCS(String str1,String str2){
    	 String [] sub1 = str1.split("[*|?|\\[|\\]]");
    	 String [] sub2 = str2.split("[*|?|\\[|\\]]");
    	 
    	 List<String> strList1 = new ArrayList<>();
    	 List<String> strList2 = new ArrayList<>();
    	 
    	 for(String s:sub1){
 	    	if(!s.equals("") && !s.equals("-")){
 	    		strList1.add(s);
 	    	}
 	    }
    	for(String s:sub2){
    		if(!s.equals("") && !s.equals("-")){
	    		strList2.add(s);
	    	}
	    }
    	int substringLength1 = strList1.size();
        int substringLength2 = strList2.size();
        
        // 构造二维数组记录子问题A[i]和B[j]的LCS的长度
        int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
 
        // 从后向前,动态规划计算所有子问题。也可从前到后。
        for (int i = strList1.size() - 1; i >= 0; i--)
        {
            for (int j = strList2.size() - 1; j >= 0; j--)
            {
                if (strList1.get(i).equals(strList2.get(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:" + new String(str1));
        System.out.println("substring2:" + new String(str2));
        System.out.print("LCS:");
        int i = 0, j = 0;
        while (i < substringLength1 && j < substringLength2)
        {
            if (strList1.get(i).equals(strList2.get(j)))
            {
                System.out.print(strList1.get(i));
                i++;
                j++;
            }
            else if (opt[i + 1][j] >= opt[i][j + 1])
                i++;
            else
                j++;
        }
        System.out.println();
        return opt[0][0];
    }
    
	public static void main(String[] args) {
		String str1 = "[ai]man*jian*shen*";
    		String str2 = "[ai]man*dian*shen*tai*yuan*jian";
    		System.out.println(LCS(str1,str2));
	}	 *求两个字符串的最长公共子序列(拼音分隔后存入list,然后求其最长公共子序列。
	 */
    public static int LCS(String str1,String str2){
    	 String [] sub1 = str1.split("[*|?|\\[|\\]]");
    	 String [] sub2 = str2.split("[*|?|\\[|\\]]");
    	 
    	 List<String> strList1 = new ArrayList<>();
    	 List<String> strList2 = new ArrayList<>();
    	 
    	 for(String s:sub1){
 	    	if(!s.equals("") && !s.equals("-")){
 	    		strList1.add(s);
 	    	}
 	    }
    	for(String s:sub2){
    		if(!s.equals("") && !s.equals("-")){
	    		strList2.add(s);
	    	}
	    }
    	int substringLength1 = strList1.size();
        int substringLength2 = strList2.size();
        
        // 构造二维数组记录子问题A[i]和B[j]的LCS的长度
        int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];
 
        // 从后向前,动态规划计算所有子问题。也可从前到后。
        for (int i = strList1.size() - 1; i >= 0; i--)
        {
            for (int j = strList2.size() - 1; j >= 0; j--)
            {
                if (strList1.get(i).equals(strList2.get(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:" + new String(str1));
        System.out.println("substring2:" + new String(str2));
        System.out.print("LCS:");
        int i = 0, j = 0;
        while (i < substringLength1 && j < substringLength2)
        {
            if (strList1.get(i).equals(strList2.get(j)))
            {
                System.out.print(strList1.get(i));
                i++;
                j++;
            }
            else if (opt[i + 1][j] >= opt[i][j + 1])
                i++;
            else
                j++;
        }
        System.out.println();
        return opt[0][0];
    }
    
	public static void main(String[] args) {
		String str1 = "[ai]man*jian*shen*";
    		String str2 = "[ai]man*dian*shen*tai*yuan*jian";
    		System.out.println(LCS(str1,str2));
	}

 

运行结果如下:

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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值