最近看了一篇关于LCS(Longest common subsequence problem,最长公共子序列)的文章,文章给出了传统的DP(dynamic programming,动态规划)算法进行求解的过程,并用c语言实现。另外参考一篇论文实现了其中的一种打印所有最大公共子序列的算法,这个算法比起传统的算法而言,时间复杂度大大降低.但是DP算法看上去比较复杂,实现起来比较困难,今天我就介绍另一种算法.

 

一:LCS解析

      看下什么是子序列?如对于字符串:“student”,那么su,sud,sudt等都是它的子序列。它可以是连续的也可以不连续出现,如果是连续的出现,比如stud,一般称为子序列串,这里我们只讨论子序列。

       公共子序列?很简单,有两个字符串,如果包含共同的子序列,那么这个子序列就被称为公共子序列了。如“student”和“shade”的公共子序列就有“s”或者“sd”或者“sde”等。而其中最长的子序列就是所谓的最长公共子序列(LCS)。当然,最长公共子序列也许不止一个,比如:“ABCBDAB”和“BDCABA”,它们的LCS为“BCBA”,“BCAB”,“BDAB”。知道了这些概念以后就是如何求LCS的问题了。

      解决一个问题要有一个思路,有思路了,实现起来就简单了.这里的难点主要是子序列可以是不连续的.那该怎么去处理呢?我今天要讲的是比较简单一个思路.

     假如有两个字符串str1和str2,对于这两个字符串的最长子序列假设是str。既然是子序列,那str字符串中从左到右每个字符在str1和str中肯定也是从左到右(什么意思呢?比如子序列str="ABC",那么在str1和str2中肯定有'A'、'B'、'C'这三个字符,这是毋庸置疑的。而且这三个字符的顺序肯定也是左到右排列的:也就是说对于str1和str2字符串肯定是这样的一种形式*A*B*C*,其中*代表0个或者多个字符)。

      好了,讲到这里大家也许有点思路了,接着讲。假设子序列str='ABC'。那么子序列的第一个字符是'A'。如果我们找到了子序列的第一个字符,继续找第二个字符,第二个字符肯定在第一个字符'A'后面。可以理解对吧?依次后面的也是。好了下面给出实现过程

二:java实现

      首先有两个char型的一维数组char1(存放str1)和char2(存放str2)(比如str1="AABCD",那么char1={'A','A','B','C','D'}),将字符串转换成字符数组并放置。一个HashMap,放置子寻列和子序列长度。

      第一:将char1的第一位字符跟char2的每一位字符进行比较,如果得到相等的,就break,跳出。并记下那个字符,以及char2中那个字符的位置(这两个标记很重要),

      第二:将char1的第二位字符依次跟char2标记位(标记在第一比较的时候做的)以后的字符比较,又得到相等的,break,(并记下那个字符,以及char2中字符的位置)。     

      第三:对char1地每一个字符作此操作。这是一轮循环,这一轮循环下来,会得到一个字符串(每个字符相加),此字符串肯定是子序列,但不一定是最长的。将此子序列和长度放到map。

 

     第二轮循环:从char1的第二个字符开始比较。得到一个子序列

     第三轮循环:从char1的第三个字符开始比较。得到一个子序列

      。。。。。

      最后:当最后一轮比较完,map里就会有char1.length个子序列,

     

      呵呵,到了这一步,我想大家在map里面找出最长的子序列不难了吧。

好了下面用java实现

 

  1. package algorithm;  
  2.  
  3. import java.util.HashMap;  
  4. import java.util.Iterator;  
  5. import java.util.Map;  
  6.  
  7. public class MaxSequence {  
  8.     private static char[] arr1;  
  9.     private static char[] arr2;  
  10.     private Map<String,Integer> map=new HashMap<String, Integer>();  
  11.     private int sum;  
  12.     public static void toArray(String str1,String str2){  
  13.         arr1=str1.toCharArray();  
  14.         arr2=str2.toCharArray();  
  15.           
  16.     }  
  17.     public Map MaxS(char[] arr1,char[] arr2){  
  18.         for(int i=0;i<=arr1.length;i++){  
  19.             int sum=0;//计长度  
  20.             String string="";//子序列  
  21.             int m=0;  
  22.             for(int j=i;j<arr1.length;j++){  
  23.                 for(int k=m;k<arr2.length;k++){  
  24.                     if(arr1[j]==arr2[k]){  
  25.                         m=k+1;//标记位置  
  26.                         sum++;//计字符串长度  
  27.                         string+=arr1[j];//每个字符想加  
  28.                         break;  
  29.                     }  
  30.                 }  
  31.             }  
  32.             map.put(string,sum);  
  33.         }  
  34.         return map;  
  35.     }  
  36.     public static void main(String[] args) {  
  37.         MaxSequence m=new MaxSequence();  
  38.         m.toArray("ABCBDABACEFGDH""BDCABABDFGH");  
  39.         Iterator it = m.MaxS(arr1, arr2).entrySet().iterator();   
  40.         while(it.hasNext()){  
  41.             Map.Entry n=(Map.Entry)it.next();   
  42.             System.out.println(n.getKey()+":"+n.getValue());  
  43.         }  
  44.  
  45.     }  
  46. }  

 

虽然MaxS套了三层循环,但是循环次数永远<=两个字符串长度的乘积