您的位置:首页 > 其它

经典算法学习_动态规划_最长公共子序列

2016-03-13 22:06 393 查看
前几天做笔试题,遇到最长递增子序列的问题,不知怎么解,看网上有一种先将该序列排序,以转化为求新序列与原有序列的最长公共子串。然而,最长公共子序列也!不!会!

这两个算法在大学时都应该是接触过的,到此时居然已经忘的一干二净,才知道自己究竟差了多少功夫。于是先学一下最长公共子序列的解法,在这里记下来,待下次再不会做时也好有个查阅的好地方。最长递增子序列的解法改天再学。

设有字符串a,b。

记a(0,i)为a从0位到i位的子串。

使用二维数组c来记录最长公共子序列长度,c[i][j]表示a(0,i)与b(0,j)的最长公共子序列长度。设c[-1][j] = c[i][-1] = 0,不论 i,j 取何值。

动态规划的思路如下:

1)若a[i] == b[j],则c[i][j] = c[i-1][j-1] + 1

2)若a[i] != b[j],则c[i][j] = max{c[i-1][j], c[i][j-1]}

在其后构造最长公共子序列时,则逆向使用求最长公共子序列长度时的数据。首先初始化空的字符串。

从表c右下角开始循环,记c[i][j],每次在下列三个表项中查找最大值

a) c[i-1][j]

b) c[i][j-1]

c) c[i-1][j-1]

若a)最大,则令 i = i - 1

若b)最大,则令 j = j - 1

若c)最大,则在字符串前插入a[i]的字符,而后令 i = i - 1,j = j - 1

其中可能出现 a),b) 两数值相等,则说明有两种构造最长公共子序列的方式,这两种子序列可能相同,也可能不同。

为了简化这个过程,也可以构造二维数组d,来存储构造最长公共子序列时,i,j 的调整方向。

二维数组c的示意图如下,来自算法导论第三版。



写练习代码如下,未仔细推敲,大致应该或许没有错…

public class LCS
{
public static void main(String[] args)
{
String strA = "ABCBDABDEWKJFIEWJFDJKSLVJIWE";
String strB = "BDCABAFJEWKOCDJNIFJWEKFLWE";
LCS aLCS = new LCS();
aLCS.calLength(strA, strB);
System.out.println("The LCS = \""+aLCS.getLCS(strA, strB)+"\"");
}

public int calLength(String a, String b)
{
//获得初始化表的长度,将字符串的长度加1,
//作为表的第一行和第一列全部为0的数据,
//以统一运算,避免在循环中加入条件判断语句。
int lenA = a.length() + 1;
int lenB = b.length() + 1;
//第i行第j列的数值表示a(0,i-1)和b(0,j-1)的最大匹配长度
lengthTab = new int[lenA][lenB];
//第i行第j列的数值表示a(0,i-1)和b(0,j-1)的最大匹配长度根据哪个单元获得,具体数值见实例域定义
directTab = new int[lenA][lenB];

//按行开始遍历
for(int i=1; i<lenA; i++)
{
//对第i行的每一个单元
for(int j=1; j<lenB; j++)
{
//若a字符串的第i-1个字符和b字符串的第j-1个字符相同
//则说明a(0,i-1)和b(0,j-1)的最大匹配长度是a(0,i-2)和b(0,j-2)匹配长度加1
if(a.charAt(i-1) == b.charAt(j-1))
{
directTab[i][j] = this.TOP_AND_LEFT;
lengthTab[i][j] = lengthTab[i-1][j-1] + 1;
}
else
{
//若a字符串的第i-1个字符和b字符串的第j-1个字符不同
//则说明a(0,i-1)和b(0,j-1)的最大匹配长度是a(0,i-2)和b(0,j-2)最大匹配长度相同
if(lengthTab[i-1][j] > lengthTab[i][j-1])
{
directTab[i][j] = this.TOP;
lengthTab[i][j] = lengthTab[i-1][j];
}
else if(lengthTab[i-1][j] < lengthTab[i][j-1])
{
directTab[i][j] = this.LEFT;
lengthTab[i][j] = lengthTab[i][j-1];
}
else
{
directTab[i][j] = this.TOP_OR_LEFT;
lengthTab[i][j] = lengthTab[i-1][j];
}
}
}
}
//      System.out.println("LengthTab:");
//      printTab(lengthTab);
//      System.out.println("DirectTab:");
//      printTab(directTab);
return lengthTab[lenA-1][lenB-1];
}

public String getLCS(String a, String b)
{
StringBuilder sb = new StringBuilder();
if(directTab==null)
{
return "";
}
//检验directTab行数和列数是否与字符串长度匹配
if(directTab.length != a.length() + 1)
return "";
if(directTab.length <= 0 || directTab[0].length != b.length() + 1)
return "";

int lenA = directTab.length - 1;
int lenB = directTab[0].length - 1;

//在表中查找使匹配长度最大的路线(其中一条)
while(lenA > 0 && lenB > 0)
{
//若该单元记录为TOP_AND_LEFT,说明在此最大匹配子串的构造中,包含a.charAt(lenA-1)
if(directTab[lenA][lenB] == this.TOP_AND_LEFT)
{
sb.insert(0, a.charAt(lenA-1));
lenA--;
lenB--;
}
//若此最大匹配子串的构造中,不包含a.charAt(lenA-1),寻找下一个包含在子串中的字符
else if(directTab[lenA][lenB] == this.TOP)
{
lenA--;
}
else if(directTab[lenA][lenB] == this.LEFT)
{
lenB--;
}
else
{
//
//存在多个最长公共子序列,假设取左
lenB--;
}
}
return sb.toString();
}

public void printTab(int[][] tab)
{
for(int[] line : tab)
{
for(int element : line)
{
System.out.printf("%4d ", element);
}
System.out.println();
}
}

private int[][] lengthTab = null;
private int[][] directTab = null;

//表示这一个单元的最大匹配长度是从上一行同一列获得
public static final int TOP = -1;
//表示这一个单元的最大匹配长度是从前一列同一行获得
public static final int LEFT = 0;
//表示这一个单元的最大匹配长度是左上角单元数值加1获得
public static final int TOP_AND_LEFT = 1;
//表示这一个单元的最大匹配长度同其左侧及上方的单元都一样,
//TOP_OR_LEFT原为找到所有最长公共子序列而设,在这个例子中没有实际使用,也未经测试是否有效,
//为避免有不同路径的相同匹配子序列,可使用TreeSet等集合返回查找结果,
//但需要注意克隆方法,以及String类的Comparable接口实现情况。
public static final int TOP_OR_LEFT = 2;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: