经典算法学习_动态规划_最长公共子序列
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的示意图如下,来自算法导论第三版。
![](http://img.blog.csdn.net/20160313221053147)
写练习代码如下,未仔细推敲,大致应该或许没有错…
这两个算法在大学时都应该是接触过的,到此时居然已经忘的一干二净,才知道自己究竟差了多少功夫。于是先学一下最长公共子序列的解法,在这里记下来,待下次再不会做时也好有个查阅的好地方。最长递增子序列的解法改天再学。
设有字符串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; }