【串和序列处理 7】LIS 最长递增子序列

LIS: 给定一个字符串序列S={x0,x1,x2,...,x(n-1)},找出其中的最长子序列,而且这个序列必须递增存在。

 

下面给出解决这个问题的几种方法:

 

(1) 转化为LCS问题

 

      思想: 将原序列S递增排序成序列T,然后利用动态规划算法取得S与T的公共最长子序列。具体算法详见《LCS最长公共子序列 》。

 

      效率: 这个方法排序最好的是时间复杂度是O(n*logn),动态规划解决LCS的时间复杂度是O(n^2)。因此总体时间复杂度是O(n*logn)+O(n^2)=O(n^2) 级别。

 

(2) 分治策略

 

      思想: 假设f(i)表示S中 x0 ... xi 子串的最长递增子序列的长度。则有如下递归:找到所有在xi之前,且值小于xi 的元素xj,即j<i 且 xj<xi。如果这样的元素存在,那么所有的xj 都有一个x0  ... xj 子串的最长递增子序列,其长度为f(j)。把其中最大的f(j)选出来,则

                                        f(i)=Max(f(j))+1.  其中{j | j<i 且xj<xi}

如果这样的j不存在,则xi自身构成一个长度为1的递增子序列。

 

该算法Java源代码如下:

Java代码  收藏代码
  1. package net.hr.algorithm.string;  
  2. /** 
  3.  * 最长递增子序列 LIS 
  4.  * @author heartraid 
  5.  */  
  6. public class LIS {  
  7.   
  8.     char[] chars=null;  
  9.       
  10.     public LIS(String str){  
  11.         chars=str.toCharArray();  
  12.     }  
  13.       
  14.     public void getLIS(){  
  15.            
  16.         int[] f=new int[chars.length]; //用于存放f(i)值  
  17.         String[] sequence=new String[chars.length];  
  18.         f[0]=1//以第x1为末元素的最长递增子序列长度为1  
  19.   
  20.         for(int i=1;i<chars.length;i++)//循环n-1次  
  21.         {  
  22.             sequence[i]=""+chars[i];  
  23.             f[i]=1;//f[i]的最小值为1;  
  24.             String temp="";  
  25.             for(int j=0;j<i;j++)//循环i 次  
  26.             {  
  27.                   
  28.                 if(chars[j]<chars[i]&&f[j]>f[i]-1){  
  29.                     temp=temp+chars[j];  
  30.                     f[i]=f[j]+1;//更新f[i]的值。  
  31.                 }  
  32.                   
  33.             }  
  34.             sequence[i]=temp+sequence[i];  
  35.         }  
  36.         //打印结果  
  37.         int maxLength=0;  
  38.         int maxSize=0;  
  39.         for(int k=0;k<chars.length;k++){  
  40.             if(maxLength<f[k]){  
  41.                 maxLength=f[k];  
  42.                 maxSize=k;  
  43.             }  
  44.         }  
  45.         System.out.println("最大递增子序列为:"+sequence[maxSize]+"(length="+maxLength+")");   
  46.     }  
  47.   
  48.     public static void main(String[] args) {  
  49.         LIS lis=new LIS("ijabcsrewesdsdewg");  
  50.         lis.getLIS();  
  51.           
  52.     }  
  53.   
  54. }  

 

     效率: 算法时间复杂度为O(n^2)级别。

 

 

(3) 动态规划算法

 

      实际上这是一道很典型的动态规划问题。我们假设a[0]....a[i-1] 有一个最长递增子序列,其长度f(i-1)<=i, 且该最长递增子序列的最后一个元素为b。

      那么对于a[0].... a[i] 而言,如果b<a[i],那么f(i)=f(i-1)+1,且最长递增子序列的最后一个元素变成了a[i]。如果b>=a[i],那么f(i)=f(i-1)。

      上面的过程有一个难点:如果a[0]....a[i-1] 有多个最大长度为f(i-1)的递增子序列怎么办?需不需要所有长度等于f(i-1)的递增子序列的最后一个元素b0...bi全部存储起来,再一一和a[i]比较大小呢?如果是这样,那么整个算法与上面的分治策略将没有什么不同了?

      事实上,并不需要怎么做。我们举个例子: a[]={1、2、5、3、7}

      a[0] ... a[3] 的最大递增子序列有两个{1,2,5}和{1,2,3},当增加a[4]的时候,如果a[4]>5,则两个子序列都需要增加a[4];如果a[4]>3,则{1,2,3}+a[4]将必定成为新的最大子序列,而{1,2,5}不确定。因此我们看出,只要保存所有最大序列的最小的末尾元素即可。

 

      因此我们设计一个如下的算法:其中b[k]用来表示最大子序列长度为k时的最小末尾元素。

Java代码  收藏代码
  1. int LIS(){  
  2.    b[1]=a[0];  
  3.    for(int i=1;k=1;i<n;i++){  
  4.       if(a[i]>=b[k]) b[++k]=a[i];  
  5.       else b[binary(i,k)]=a[i];  
  6.    }  
  7.    return k;  
  8. }  
  9.   
  10. int binary(int i, int k){  
  11.    if(a[i]<b[1]) return 1;  
  12.    for(int h=1,j=k;h!=j-1;){  
  13.       if(b[k=(h+j)/2]<=a[i]) h=k;  
  14.       else j=k;    
  15.    }  
  16.    return j;  
  17. }   

   该算法的时间复杂为O(N*logN)。

posted @ 2012-07-22 21:44  springbarley  阅读(233)  评论(0)    收藏  举报