1. 程式人生 > >最大子序列、最長連續公共子串(連續)、最長公共子序列(動態規劃)

最大子序列、最長連續公共子串(連續)、最長公共子序列(動態規劃)

原文連結:http://blog.sina.com.cn/s/blog_54f82cc20100zi4b.html

最大子序列

最大子序列是要找出由陣列成的一維陣列中和最大的連續子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已經看出來了,找最大子序列的方法很簡單,只要前i項的和還沒有小於0那麼子序列就一直向後擴充套件,否則丟棄之前的子序列開始新的子序列,同時我們要記下各個子序列的和,最後找到和最大的子序列。

程式碼如下:

#include<iostream>
using  namespace  std; int  MaxSubSeq( const  int  *arr, int  len, int  *start, int  *end){     
int  max=0;                   //記錄目前找到的最大子序列的和      int  sum=0;                 
  //記錄當前子序列的和      int  begin=0,finish=0;        //記錄當前子序列的起始下標      *start=begin;*end=finish;    //記錄最長子序列的起始下標      for ( int  i=0;i<len;i++){          sum+=arr[i];          finish=i;          if (sum>max){              max=sum;              *end=finish;              *start=begin;          }          if (sum<=0){              sum=0;              begin=i+1;          }      }      return  max; } int  main(){      int  arr[6]={5,-3,-2,12,9,-1};      int  start,end;      int  max=MaxSubSeq(arr,6,&start,&end);      cout<< "The MaxSubSeq is from position " <<start<< "to position " <<end<< "." <<endl;      cout<< "Sum of MaSubSeq: " <<max<<endl;      return  0; }

最長公共子串(LCS)

找 兩個字串的最長公共子串,這個子串要求在原字串中是連續的。其實這又是一個序貫決策問題,可以用動態規劃來求解。我們採用一個二維矩陣來記錄中間的結 果。這個二維矩陣怎麼構造呢?直接舉個例子吧:"bab"和"caba"(當然我們現在一眼就可以看出來最長公共子串是"ba"或"ab")

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  1

a  0  1  0

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

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

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

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

在構造這個二維矩陣的過程中由於得出矩陣的某一行後其上一行就沒用了,所以實際上在程式中可以用一維陣列來代替這個矩陣。

程式碼如下:

#include<iostream> #include<cstring> #include<vector> using  namespace  std; //str1為橫向,str2這縱向 const  string LCS( const  string& str1, const  string& str2){      int  xlen=str1.size();        //橫向長度      vector< int > tmp(xlen);         //儲存矩陣的上一行      vector< int > arr(tmp);      //當前行      int  ylen=str2.size();        //縱向長度      int  maxele=0;                //矩陣元素中的最大值      int  pos=0;                   //矩陣元素最大值出現在第幾列      for ( int  i=0;i<ylen;i++){          string s=str2.substr(i,1);          arr.assign(xlen,0);      //陣列清0          for ( int  j=0;j<xlen;j++){              if (str1.compare(j,1,s)==0){                  if (j==0)                      arr[j]=1;                  else                      arr[j]=tmp[j-1]+1;                  if (arr[j]>maxele){                      maxele=arr[j];                      pos=j;                  }              }                } //      { //          vector<int>::iterator iter=arr.begin(); //          while(iter!=arr.end()) //              cout<<*iter++; //          cout<<endl; //      }          tmp.assign(arr.begin(),arr.end());      }      string res=str1.substr(pos-maxele+1,maxele);      return  res; } int  main(){      string str1( "21232523311324" );      string str2( "312123223445" );      string lcs=LCS(str1,str2);      cout<<lcs<<endl;      return  0; }


最長公共子序列

最長公共子序列與最長公共子串的區別在於最長公共子序列不要求在原字串中是連續的,比如ADE和ABCDE的最長公共子序列是ADE。

我們用動態規劃的方法來思考這個問題如是求解。首先要找到狀態轉移方程:

等號約定,C1是S1的最右側字元,C2是S2的最右側字元,S1‘是從S1中去除C1的部分,S2'是從S2中去除C2的部分。

LCS(S1,S2)等於下列3項的最大者:

(1)LCS(S1,S2’)

(2)LCS(S1’,S2)

(3)LCS(S1’,S2’)--如果C1不等於C2; LCS(S1',S2')+C1--如果C1等於C2;

邊界終止條件:如果S1和S2都是空串,則結果也是空串。

下面我們同樣要構建一個矩陣來儲存動態規劃過程中子問題的解。這個矩陣中的每個數字代表了該行和該列之前的LCS的長度。與上面剛剛分析出的狀態轉移議程相對應,矩陣中每個格子裡的數字應該這麼填,它等於以下3項的最大值:

(1)上面一個格子裡的數字

(2)左邊一個格子裡的數字

(3)左上角那個格子裡的數字(如果 C1不等於C2); 左上角那個格子裡的數字+1( 如果C1等於C2)

舉個例子:

       G  C  T  A

   0  0  0  0  0

G  0  1  1  1  1

B  0  1  1  1  1

T  0  1  1  2  2

A    0  1  1  2  3

填寫最後一個數字時,它應該是下面三個的最大者:

(1)上邊的數字2

(2)左邊的數字2

(3)左上角的數字2+1=3,因為此時C1==C2

所以最終結果是3。

在填寫過程中我們還是記錄下當前單元格的數字來自於哪個單元格,以方便最後我們回溯找出最長公共子串。有時候左上、左、上三者中有多個同時達到最大,那麼任取其中之一,但是在整個過程中你必須遵循固定的優先標準。在我的程式碼中優先級別是左上>左>上。

奉上程式碼:

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include<iostream> #include<cstring> #include<stack> #include<utility> #define LEFTUP 0 #define LEFT 1 #define UP 2 using  namespace  std; int  Max( int  a, int  b, int  c, int  *max){             //找最大者時a的優先級別最高,c的最低.最大值儲存在*max中      int  res=0;           //res記錄來自於哪個單元格      *max=a;      if (b>*max){          *max=b;          res=1;      }      if (c>*max){          *max=c;          res=2;      }      return  res; } //呼叫此函式時請注意把較長的字串賦給str1,這主要是為了在回溯最長子序列時節省時間。如果沒有把較長的字串賦給str1不影響程式的正確執行。 string LCS( const  string &str1, const  string &str2){      int  xlen=str1.size();                //橫向長度      int  ylen=str2.size();                //縱向長度      if (xlen==0||ylen==0)                 //str1和str2中只要有一個為空,則返回空          return  "" ;      pair< int , int > arr[ylen+1][xlen+1];     //構造pair二維陣列,first記錄資料,second記錄來源      for ( int  i=0;i<=xlen;i++)          //首行清0          arr[0][i].first=0;      for ( int  j=0;j<=ylen;j++)          //首列清0          arr[j][0].first=0;      for ( int  i=1;i<=ylen;i++){          char  s=str2.at(i-1);          for ( int  j=1;j<=xlen;j++){              int  leftup=arr[i-1][j-1].first;              int  left=arr[i][j-1].first;              int  up=arr[i-1][j].first;              if (str1.at(j-1)==s)          //C1==C2                  leftup++;              int  max;              arr[i][j].second=Max(leftup,left,up,&arr[i][j].first); //          cout<<arr[i][j].first<<arr[i][j].second<<" ";          } //      cout<<endl;      }            //回溯找出最長公共子序列      stack< int > st;      int  i=ylen,j=xlen;      while (!(i==0&&j==0)){          if (arr[i][j].second==LEFTUP){              if (arr[i][j].first==arr[i-1][j-1].first+1)                  st.push(i);              --i;              --j;          }          else  if (arr[i][j].second==LEFT){              --j;          }          else  if (arr[i][j].second==UP){              --i;          }      }      string res= "" ;      while (!st.empty()){          int  index=st.top()-1;          res.append(str2.substr(index,1));          st.pop();      }      return  res; } int  main(){      string str1= "GCCCTAGCG" ;      string str2= "GCGCAATG" ;      string lcs=LCS(str1,str2);      cout<<lcs<<endl;      return  0; }

下面給一個Java版本

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  static  <E> List<E> longestCommonSubsequence(E[] s1,E[] s2){          int [][] num= new  int [s1.length+ 1 ][s2.length+ 1 ];          for ( int  i= 1 ;i<s1.length;i++){              for ( int  j= 1 ;j<s2.length;j++){                  if (s1[i- 1 ].equals(s2[j- 1 ])){                      num[i][j]= 1 +num[i- 1 ][j- 1 ];                  }                  else {                      num[i][j]=Math.max(num[i- 1 ][j],num[i][j- 1 ]);                  }              }          }          System.out.println( "lenght of LCS= " +num[s1.length][s2.length]);          int  s1position=s1.length,s2position=s2.length;          List<E> result= new  LinkedList<E>();          while (s1position!= 0  && s2position!= 0 ){              if (s1[s1position- 1 ].equals(s2[s2position- 1 ])){                  result.add(s1[s1position- 1 ]);                  s1position--;                  s2position--;              }              else  if (num[s1position][s2position- 1 ]>=num[s1position- 1 ][s2position]){                  s2position--;              }              else {                  s1position--;              }          }          Collections.reverse(result);          return  result;      }

std::endl是一個特殊值,稱為操縱符(manipulator),將它寫入輸出流時具有輸出換行的效果,並重新整理與裝置相關聯的緩衝區(buffer)。通過重新整理緩衝區使用者可立即看到寫入到流中的輸出。

我在除錯以上程式碼時在45行(cout<<endl;)處設定斷點,結果發現“43行(cout<<arr[i][j].first<<arr[i][j].second<<"";) 沒有執行”,這就是因為43行末尾沒有加endl,所以使用者沒有立即看到輸出流中的資料。