轉【算法之動態規劃(三)】動態規劃算法之:最長公共子序列 & 最長公共子串(LCS)&字符串類似度算法 最長公共子序列求解:遞歸與動態規劃方法 字符串類似度算法 遞歸與動態規劃求解分析

一、先科普下最長公共子序列 & 最長公共子串的區別:html

找兩個字符串的最長公共子串,這個子串要求在原字符串中是連續的。而最長公共子序列則並不要求連續。java

二、最長公共子串算法

 

 

其實這是一個序貫決策問題,能夠用動態規劃來求解。咱們採用一個二維矩陣來記錄中間的結果。這個二維矩陣怎麼構造呢?直接舉個例子吧:"bab"和"caba"(固然咱們如今一眼就能夠看出來最長公共子串是"ba"或"ab")shell

   b  a  b編程

c  0  0  0數組

a  0  1  0數據結構

b  1  0  1app

a  0  1  0dom

咱們看矩陣的斜對角線最長的那個就能找出最長公共子串。post

不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費時的事,下面改進:當要在矩陣是填1時讓它等於其左上角元素加1。

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

這樣矩陣中的最大元素就是 最長公共子串的長度。

在構造這個二維矩陣的過程當中因爲得出矩陣的某一行後其上一行就沒用了,因此實際上在程序中能夠用一維數組來代替這個矩陣。

2.1 代碼以下:

public  class  LCString2 {
 
     public  static  void  getLCString( char [] str1,  char [] str2) {
         int  i, j;
         int  len1, len2;
         len1 = str1.length;
         len2 = str2.length;
         int  maxLen = len1 > len2 ? len1 : len2;
         int [] max =  new  int [maxLen];
         int [] maxIndex =  new  int [maxLen];
         int [] c =  new  int [maxLen];  // 記錄對角線上的相等值的個數
 
         for  (i =  0 ; i < len2; i++) {
             for  (j = len1 -  1 ; j >=  0 ; j--) {
                 if  (str2[i] == str1[j]) {
                     if  ((i ==  0 ) || (j ==  0 ))
                         c[j] =  1 ;
                     else
                         c[j] = c[j -  1 ] +  1 ;
                 else  {
                     c[j] =  0 ;
                 }
 
                 if  (c[j] > max[ 0 ]) {  // 若是是大於那暫時只有一個是最長的,並且要把後面的清0;
                     max[ 0 ] = c[j];  // 記錄對角線元素的最大值,以後在遍歷時用做提取子串的長度
                     maxIndex[ 0 ] = j;  // 記錄對角線元素最大值的位置
 
                     for  ( int  k =  1 ; k < maxLen; k++) {
                         max[k] =  0 ;
                         maxIndex[k] =  0 ;
                     }
                 else  if  (c[j] == max[ 0 ]) {  // 有多個是相同長度的子串
                     for  ( int  k =  1 ; k < maxLen; k++) {
                         if  (max[k] ==  0 ) {
                             max[k] = c[j];
                             maxIndex[k] = j;
                             break // 在後面加一個就要退出循環了
                         }
 
                     }
                 }
             }
         }
 
         for  (j =  0 ; j < maxLen; j++) {
             if  (max[j] >  0 ) {
                 System.out.println( "第"  + (j +  1 ) +  "個公共子串:" );
                 for  (i = maxIndex[j] - max[j] +  1 ; i <= maxIndex[j]; i++)
                     System.out.print(str1[i]);
                 System.out.println( " " );
             }
         }
     }
 
     public  static  void  main(String[] args) {
 
         String str1 =  new  String( "123456abcd567" );
         String str2 =  new  String( "234dddabc45678" );
         // String str1 = new String("aab12345678cde");
         // String str2 = new String("ab1234yb1234567");
         getLCString(str1.toCharArray(), str2.toCharArray());
     }
}

ref:

LCS的Java算法---考慮可能有多個相同的最長公共子串

http://blog.csdn.net/rabbitbug/article/details/1740557

 

最大子序列、最長遞增子序列、最長公共子串、最長公共子序列、字符串編輯距離

http://www.cnblogs.com/zhangchaoyang/articles/2012070.html

2.2 其實 awk 寫起來也很容易:

echo  "123456abcd567
234dddabc45678 "|awk -vFS=" " 'NR==1{str=$0}NR==2{N=NF;for(n=0;n++<N;){s=" ";for(t=n;t<=N;t++){s=s" "$t; if (index(str,s)){a[n]=t-n;b[n]=s; if (m<=a[n])m=a[n]} else {t=N}}}}END{ for (n=0;n++<N;) if (a[n]==m)print b[n]}'
 

ref:http://bbs.chinaunix.net/thread-4055834-2-1.html

2.3 perl的。。。真心沒看懂。。。

 

#!/usr/bin/perl
use  strict;
use  warnings;
 
my  $str1  = "123456abcd567" ;
my  $str2  "234dddabc45678" ;
my  $str  $str1  "\n"  $str2 ;
 
my  ( @substr , @result );
$str  =~ /(.+)(?=.*\n.*\1)( *PRUNE )(?{ push  @substr ,$1})( *F )/;
@substr  sort  length ( $b ) <=>  length ( $a ) }  @substr ;
@result  grep  length  ==  length  $substr [0] }  @substr ;
print  "@result\n" ;

ref: http://bbs.chinaunix.net/thread-1333575-7-1.html

 

三、最長公共子序列

import  java.util.Random;
 
public  class  LCS {
 
     public  static  void  main(String[] args) {
 
         // 隨機生成字符串
         // String x = GetRandomStrings(substringLength1);
         // String y = GetRandomStrings(substringLength2);
         String x =  "a1b2c3" ;
         String y =  "1a1wbz2c123a1b2c123" ;
         // 設置字符串長度
         int  substringLength1 = x.length();
         int  substringLength2 = y.length();  // 具體大小可自行設置
 
         // 構造二維數組記錄子問題x[i]和y[i]的LCS的長度
         int [][] opt =  new  int [substringLength1 +  1 ][substringLength2 +  1 ];
 
         // 從後向前,動態規劃計算全部子問題。也可從前到後。
         for  ( int  i = substringLength1 -  1 ; i >=  0 ; i--) {
             for  ( int  j = substringLength2 -  1 ; j >=  0 ; j--) {
                 if  (x.charAt(i) == y.charAt(j))
                     opt[i][j] = opt[i +  1 ][j +  1 ] +  1 ; // 狀態轉移方程
                 else
                     opt[i][j] = Math.max(opt[i +  1 ][j], opt[i][j +  1 ]); // 狀態轉移方程
             }
         }
         System.out.println( "substring1:"  + x);
         System.out.println( "substring2:"  + y);
         System.out.print( "LCS:" );
 
         int  i =  0 , j =  0 ;
         while  (i < substringLength1 && j < substringLength2) {
             if  (x.charAt(i) == y.charAt(j)) {
                 System.out.print(x.charAt(i));
                 i++;
                 j++;
             else  if  (opt[i +  1 ][j] >= opt[i][j +  1 ])
                 i++;
             else
                 j++;
         }
     }
 
     // 取得定長隨機字符串
     public  static  String GetRandomStrings( int  length) {
         StringBuffer buffer =  new  StringBuffer( "abcdefghijklmnopqrstuvwxyz" );
         StringBuffer sb =  new  StringBuffer();
         Random r =  new  Random();
         int  range = buffer.length();
         for  ( int  i =  0 ; i < length; i++) {
             sb.append(buffer.charAt(r.nextInt(range)));
         }
         return  sb.toString();
     }
}

 REF:

字符串最大公共子序列以及最大公共子串問題

http://gongqi.iteye.com/blog/1517447

動態規劃算法解最長公共子序列LCS問題

http://blog.csdn.net/v_JULY_v/article/details/6110269

 

 

最長公共子序列求解:遞歸與動態規劃方法

  在作OJ題目的時候,常常會用到字符串的處理。例如,比較二個字符串類似度。這篇文章介紹一下求兩個字符串的最長公共子序列。

  一個字符串的子序列,是指從該字符串中去掉任意多個字符後剩下的字符在不改變順序的狀況下組成的新字符串。

  最長公共子序列,是指多個字符串可具備的長度最大的公共的子序列。

  (1)遞歸方法求最長公共子序列的長度

    1)設有字符串a[0...n],b[0...m],下面就是遞推公式。

             當數組a和b對應位置字符相同時,則直接求解下一個位置;當不一樣時取兩種狀況中的較大數值。

    

    2)代碼以下:

 
#include<stdio.h>
#include<string.h>
char a[30],b[30];
int lena,lenb;
int LCS(int,int);  ///兩個參數分別表示數組a的下標和數組b的下標

int main()
{
    strcpy(a,"ABCBDAB");
    strcpy(b,"BDCABA");
    lena=strlen(a);
    lenb=strlen(b);
    printf("%d\n",LCS(0,0));
    return 0;
}

int LCS(int i,int j)
{
    if(i>=lena || j>=lenb)
        return 0;
    if(a[i]==b[j])
        return 1+LCS(i+1,j+1);
    else
        return LCS(i+1,j)>LCS(i,j+1)? LCS(i+1,j):LCS(i,j+1);
}
 

     用遞歸的方法優勢是編程簡單,容易理解。缺點是效率不高,有大量的重複執行遞歸調用,並且只能求出最大公共子序列的長度,求不出具體的最大公共子序列。

  (2)動態規劃求最長公共子序列的長度

    動態規劃採用二維數組來標識中間計算結果,避免重複的計算來提升效率。

    1)最長公共子序列的長度的動態規劃方程

    設有字符串a[0...n],b[0...m],下面就是遞推公式。字符串a對應的是二維數組num的行,字符串b對應的是二維數組num的列。

    

    另外,採用二維數組flag來記錄下標ij的走向。數字"1"表示,斜向下;數字"2"表示,水平向右;數字"3"表示,豎直向下。這樣便於之後的求解最長公共子序列。

    (2)求解公共子序列代碼

#include<stdio.h>
#include<string.h>

char a[500],b[500];
char num[501][501]; ///記錄中間結果的數組
char flag[501][501];    ///標記數組,用於標識下標的走向,構造出公共子序列
void LCS(); ///動態規劃求解
void getLCS();    ///採用倒推方式求最長公共子序列

int main()
{
    int i;
    strcpy(a,"ABCBDAB");
    strcpy(b,"BDCABA");
    memset(num,0,sizeof(num));
    memset(flag,0,sizeof(flag));
    LCS();
    printf("%d\n",num[strlen(a)][strlen(b)]);
    getLCS();
    return 0;
}

void LCS()
{
    int i,j;
    for(i=1;i<=strlen(a);i++)
    {
        for(j=1;j<=strlen(b);j++)
        {
            if(a[i-1]==b[j-1])   ///注意這裏的下標是i-1與j-1
            {
                num[i][j]=num[i-1][j-1]+1;
                flag[i][j]=1;  ///斜向下標記
            }
            else if(num[i][j-1]>num[i-1][j])
            {
                num[i][j]=num[i][j-1];
                flag[i][j]=2;  ///向右標記
            }
            else
            {
                num[i][j]=num[i-1][j];
                flag[i][j]=3;  ///向下標記
            }
        }
    }
}

void getLCS()
{

    char res[500];
    int i=strlen(a);
    int j=strlen(b);
    int k=0;    ///用於保存結果的數組標誌位
    while(i>0 && j>0)
    {
        if(flag[i][j]==1)   ///若是是斜向下標記
        {
            res[k]=a[i-1];
            k++;
            i--;
            j--;
        }
        else if(flag[i][j]==2)  ///若是是斜向右標記
            j--;
        else if(flag[i][j]==3)  ///若是是斜向下標記
            i--;
    }

    for(i=k-1;i>=0;i--)
        printf("%c",res[i]);
}

    (3)圖示

 

字符串類似度算法 遞歸與動態規劃求解分析

1.概念

  編輯距離,指的是兩個字符串之間,由一個轉換成另外一個所需的最少編輯操做次數。許可的編輯操做包括:(1)將一個字符替換成另外一個字符,(2)插入一個字符,(3)刪除一個字符。

  類似度,等於「編輯距離+1」的倒數。

2.分析

  設有字符串a[0...n],b[0...m]。

  (1)當a[i]=b[j]時,說明這時候不須要編輯操做。編輯距離保持,即f(i,j)=f(i-1,j-1)

  (2)當a[i]!=b[j]時,能夠有三種編輯操做。

  其中刪除和插入操做,只對一個下標i或者j產生影響。如在下圖中,當前匹配到(t1,t2)處,若是採用刪除'g',只改變t1的下標。

  

   其中替換操做,會對2個下標都產生影響。如在下圖中,當前匹配到(t1,t2)處,若是將'g'替換成'm',則下次就須要執行(t1+1,t2+1)處。

      

   因此能夠推導出下面就是遞推公式。

  

3.用遞歸求解代碼

#include<stdio.h>
#include<string.h>
char *a="abcgh";
char *b="aecdgh";
int min(int t1,int t2,int t3)   ///求三個數的最小值
{
    int min;
    min=t1<t2?t1:t2;
    min=min<t3?min:t3;
    return min;
}
int calculate(int i,int enda,int j,int endb)
{
    int t1,t2,t3;
    if(i>enda)  ///i指示超過a[]的範圍時
    {
        if(j>endb)
            return 0;
        else
            return endb-j+1;
    }
    if(j>endb)  ///j指示超過b[]的範圍時
    {
        if(i>enda)
            return 0;
        else
            return enda-i+1;
    }
    if(*(a+i) == *(b+j))    ///若是兩個相等,則直接求下一個位置
        return calculate(i+1,enda,j+1,endb);
    else
    {
        t1=calculate(i+1,enda,j,endb);  ///刪除a[i]或在b中插入a[i]
        t2=calculate(i,enda,j+1,endb);  ///刪除b[j]或在a中插入b[j]
        t3=calculate(i+1,enda,j+1,endb);    ///替換
        return 1+min(t1,t2,t3);
    }
}
int main()
{
    int dis=calculate(0,strlen(a)-1,0,strlen(b)-1);
    printf("dis=%d",dis);
    return 1;
}

 4.用動態規劃求解代碼

 

#include<stdio.h>
#include<string.h>
#define MAX 1000
int dp[MAX][MAX];   ///dp[i][j]表示當前a[0..i-1]與b[0..j-1]的編輯距離
char *a="agbgd";
char *b="ggd";

int min(int t1,int t2,int t3)   ///求三個數的最小值
{
    int min;
    min=t1<t2?t1:t2;
    min=min<t3?min:t3;
    return min;
}

int main()
{
    int i,j;
    int lena=strlen(a),lenb=strlen(b);
    memset(dp,0,sizeof(dp));
    for(i=0;i<=lena;i++)   ///a做爲行,當b爲空串時
        dp[0][i]=i;
    for(i=0;i<=lenb;i++)   ///b做爲列,當a爲空串時
        dp[i][0]=i;

    for(i=1;i<=lena;i++)
    {
        for(j=1;j<=lenb;j++)
        {
            if(*(a+i)==*(b+j))  ///相等時
                dp[i][j]=dp[i-1][j-1];
            else
                dp[i][j]=1+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]); ///不相等時,取三種可能操做的最小數值+1
        }
    }
    printf("編輯距離爲:dis=%d\n",dp[lena][lenb]);
    return ;
}

 

來源:http://blog.csdn.net/cangchen/article/details/45045541

相關文章
相關標籤/搜索