输出所有的最长公共子序列

 
 

请先通过维基百科或其他搜索引擎了解如下知识点,如果已经掌握可直接跳过:

【参考网址:

http://en.wikipedia.org/wiki/Standard_Template_Library

http://www.geeksforgeeks.org/dynamic-programming-set-4-longest-common-subsequence/

1. 最长公共子序列 ;

2. 最长公共子序列与最长公共子串的区别;

 Longest Common Subsequence / Longest Common Substring

3. 求解最长公共子序列的动态规划的算法;

可用动态规划算法求解的问题一般具有两个特点:

1)重叠子问题;

2)最优子结构;

4. 通常用动态规划算法先求最长公共子序列的长度,并将所有子问题的解都保存在一个二维数组中。

例如:

字符串X: abcabcaa

字符串Y: acbacba

m = |X| = 8

n = |Y| = 7

递推表达式:

保存子问题的解(Tabulation)

a c b a c b a
0 0 0 0 0 0 0 0
a 0 1 1 1 1 1 1 1
b 0 1 1 2 2 2 2 2
c 0 1 2 2 2 3 3 3
a 0 1 2 2 3 3 3 4
b 0 1 2 3 3 3 4 4
c 0 1 2 3 3 4 4 4
a 0 1 2 3 4 4 4 5
a 0 1 2 3 4 4 4 5

LCS(X,Y) = 5

输出所有的最长公共子序列:

例子:

字串 X = “abcabcaa" 

字串 Y = "acbacba",

它们的所有最长公共子序列:

ababa
abaca
abcba
acaba
acaca
acbaa
acbca

1)按照O(mn)动态规划的算法求出最长公共子序列的长度

子问题的解保存在二维数组dp里
dp[i][j] = { 字串 X 的 1~i 部分与字串 Y 的 1~j 部分的最长公共子序列的长度 }

2)构造字母表

S = { i | i in X or i in Y }

字母表S由出现在字串X和字串Y的所有字符构成的集合。

前面例子中 X 与 Y 构成的字母表S = "abc"。


3)字母表中的字符在两个字串中最后出现的下标 

概念可参考Java中String类的lastIndexOf()方法

last1[i][j] = { 到字串 X 下标 i 为止,字母表第 j 个字符在字串 X 中最后一次出现的下标 }
last2[i][j] = { 到字串 Y 下标 i 为止,字母表第 j 个字符在字串 Y 中最后一次出现的下标 }


4)枚举最长公共字串的每一个字符

从两个字串的长度 len1 和 len2 开始枚举字母表 S 中的每一个字符

例如,

 t1 = last1[len1][0] 

t2 = last2[len2][0]

表示字母表第0个字符在 X 字符串1---len1的最大下标为t1, 

在Y 字符串1--len2的最大下标为t2。

若dp[t1][t2] 的值为s1和s2的最大公共子序列长度 cnt , 则表示这个字符即字母表的第0个字符是最长公共子序列的最后一个字符,

并且继续在 t1 - 1 和 t2 - 1 的子问题中寻找符合最大公共子序列长度为为cnt - 1的字符串,依此类推,直到达最长公共子序列为0时结束。

把保存的字符串放入set集合里面,让它按字典序排序;否则继续枚举字母表中的下一个字符。


为什么从最长公共子序列的最后一个字符开始寻找呢?

例如:

”CDCD" 

"FUCKC" 

它们构成的字母表S = ”UDFCK"
现在枚举字符'C',它在字母表S中的下标 = 3

last1[0][3] = 0 //下标从0开始

last1[3[3] = 2

last2[2][3] = 2

last2[4][3] = 4

根据’C'在两个字串出现的位置,共有(0, 2)、(0, 4)、 (2, 2) 、(2, 4) 四种可能。

我们舍弃前三个,可以为后续的枚举提供更大的空间。


5)Java语言实现

import java.util.*;

/**
 * The string "abcabcaa" and "acbacba" have longest common subsequences:
 * ababa
 * abaca
 * abcba
 * acaba
 * acaca
 * acbaa
 * acbca
 * @author York
 *
 */

public class LongestCommonSubsequence {
	String source;
	String target;
	int[][] c;
	int[][] last1;
	int[][] last2;
	int lcsLength;
	char[] tmp;
	String alphabet;
	HashSet<String> set = new HashSet<String>();
	
	public LongestCommonSubsequence() {
	}
	
	public LongestCommonSubsequence(String s, String t) {
		this.source = s;
		this.target = t;
	}
	
	public int LCSLength() {
		int m = source.length();
		int n = target.length();
		c = new int[m+1][n+1];
	
		c[0][0] = 0;
		for(int i = 1; i <= m; ++i) {
			c[i][0] = 0;
		}
		for(int j = 1; j <= n; ++j) {
			c[0][j] = 0;
		}
		
		for(int i = 1; i <= m; i++) {
			for(int j = 1; j <= n; j++) {
				if(source.charAt(i-1) == target.charAt(j-1)) {
					c[i][j] = c[i-1][j-1] + 1;
				} else if(c[i-1][j] >= c[i][j-1]){
					c[i][j] = c[i-1][j];
				} else {
					c[i][j] = c[i][j-1];
				}
				
			}
		}
		return lcsLength = c[m][n];
	}
	
	private void lastIndexOf(String s, int[][] last) {
		for (int j = 0; j < alphabet.length(); ++j) {
			char c = alphabet.charAt(j);
			for (int i = 1; i <= s.length(); ++i) {
				int lastIndex;
				for (lastIndex = i; lastIndex >= 1; --lastIndex) {
					if(c == s.charAt(lastIndex-1)) {
						last[i][j] = lastIndex;
						break;
					}
				}
				if(lastIndex <= 0) {
					last[i][j] = 0;
				}
			}
		}
	}
	
	private void backTraceAll(int index1, int index2, int len) {
		if(len <= 0) {
			String str = new String(tmp);
			System.out.println("lcs: " + str);
			set.add(str);
			return;
		}
		if(index1 > 0 && index2 > 0) {
			for(int j = 0; j < alphabet.length(); ++j) {
				//字母表中第j个字符最后一次出现在子串source(0, index1)的下标
				int t1 = last1[index1][j];
				int t2 = last2[index2][j];
				//数学证明是?或者给出递归表达式吧
				if(c[t1][t2] == len) {
					tmp[len-1] = alphabet.charAt(j);
					backTraceAll(t1-1, t2-1, len-1);
				}
			}
		}
	}
	
	public void printAll() {
		int m = source.length();
		int n = target.length();
		
		//由两个字符串构成的字母表,包含它们中出现过的所有字符的集合
		//alphabet = {i| i in source or target};
		HashSet<Character> alpha = new HashSet<Character>();
		for(int i = 0; i < m; ++i) {
			alpha.add(source.charAt(i));
		}
		for(int j = 0; j < n; ++j) {
			alpha.add(target.charAt(j));
		}
		int length = alpha.size();
		Character[] characters = new Character[length];
		alpha.toArray(characters);
		char[] chars = new char[length];
		for(int i = 0; i < length; ++i) {
			chars[i] = characters[i];
		}
		alphabet = new String(chars);

		System.out.println("alphabet = " + alphabet);
		
		last1 = new int[m+1][alphabet.length()];
		last2 = new int[n+1][alphabet.length()];
		
		System.out.println("c[][]: ");
		for(int i = 0; i <= m; ++i) {
			for(int j = 0; j <= n; ++j) {
				System.out.print(c[i][j] + "\t");
			}
			System.out.println();
		}
		
		
		lastIndexOf(source, last1);
		lastIndexOf(target, last2);
		
		System.out.println("last1[][]: ");
		for(int i = 0; i <= m; ++i) {
			for(int j = 0; j < alphabet.length(); ++j) {
				System.out.print(last1[i][j] + " ");
			}
			System.out.println();
		}
		
		System.out.println("last2[][]: ");
		for(int i = 0; i <= n; ++i) {
			for(int j = 0; j < alphabet.length(); ++j) {
				System.out.print(last2[i][j] + " ");
			}
			System.out.println();
		}

		
		System.out.println("lcs length = " + lcsLength);
		
		tmp = new char[lcsLength];
		backTraceAll(m, n, lcsLength);
		System.out.println("All longest common sequences: ");
		for(String s: set) {
			System.out.println(s);
		}
	}
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("Enter two strings:");
		String str1 = sc.nextLine();
		String str2 = sc.nextLine();
		LongestCommonSubsequence lcs = new LongestCommonSubsequence(str1, str2);
		int max = lcs.LCSLength();
		System.out.println("longest length = " + max);
		lcs.printAll();
	}
}


参考网址:

http://blog.csdn.net/tsaid/article/details/6726698

http://blog.csdn.net/xiaoxiaoluo/article/details/8169533

http://www.cppblog.com/varg-vikernes/archive/2010/09/27/127866.html

你可能感兴趣的:(输出所有的最长公共子序列)