最長公用子串--longest common Subsequence,即兩個字串中連續字串的的公用子串。
描述:
給定兩個字串X=<x1, x2, … , xm>, Y=<y1, y2, ... , yn>, 假設公用子串Z=<z1, z2, ... zk> 滿足:
- 如果xm = yn,那麼zk = xm = yn而且Zk-1是Xm-1和Yn-1的一個LCS
- 如果xm ≠ yn,那麼zk ≠ xm → Z是Xm-1和Y的一個LCS
- 如果xm ≠ yn,那麼zk ≠ yn → Z是X和Yn-1的一個LCS
上面說明X,Y的LCS子串具有最優子結構。遞推公式如下:設c[i,j]為最長子串長度
過程:
1、把兩個字串一個橫排放置,一個縱排放置。如字串longstr=“acebb”, Y=“acttacebd”,此時形成了一個矩陣arr【】【】。
2、當longStr【i】== shortStr【j】,時然後我們就可以在橫、豎的矩陣中相應位置arr【i】【j】位置置1,
矩陣中對應位置置1
看這,我們想要求最長子串,我們要做的:
第一步:遍曆長、短字串,然後在矩陣數組中相同位置置1
第二步:然後數斜線長度,最長即為結果。
代碼如下:
#include <string>#include <assert.h>using namespace std;char* LCS(const char* shortStr, const char* longStr);int _tmain(int argc, _TCHAR* argv[]){const char* shortStr = "acettab";const char* longStr = "acettabfaceabcccctaf";char* result = LCS(shortStr, longStr);printf("longest common substring is: %s", result);getchar(); //在使用vs2008 vs2010時,使程式暫停下來return 0;}char* LCS(const char* shortStr, const char* longStr){assert(shortStr && longStr);int maxlen = 0;int row = 0; //計算兩字串長度unsigned slen = strlen( shortStr );unsigned llen = strlen( longStr ); //定義二維數組,即矩陣unsigned **arr = NULL; //初始化二維矩陣arr = (unsigned ** ) malloc ( llen * sizeof(unsigned*));memset(arr, 0, llen * sizeof(unsigned*));for(unsigned i = 0; i < llen; ++i){arr[i] = (unsigned *) malloc(slen * sizeof(unsigned));memset(arr[i], 0, slen * sizeof(unsigned));} //如,比較橫、豎串,當字元相等時,矩陣對應位置置1,此時記錄最長子串長度,賦值給maxlen,並記錄最長子串結束的結束位置row //row值方便計運算元串 for(unsigned i = 0; i < llen ; ++i){for (unsigned j = 0; j < slen; ++j){if(shortStr[j] == longStr[i]){if (i == 0 || j == 0){arr[i][j] = 1;}elsearr[i][j] = arr[i-1][j-1] + 1;}if (arr[i][j] > maxlen){maxlen = arr[i][j];row = i + 1;}}} //釋放資源for(int i = 0; i < slen; ++i)free(arr[i]);free(arr);char *lcsResult = NULL;lcsResult = (char*) malloc (maxlen * sizeof(char) + 1);memset(lcsResult, 0, maxlen + 1);int t;int tmp = maxlen;for (t = 0; t < maxlen; ++t){*(lcsResult + t) = *(longStr + row - tmp);//計算最長子串tmp -= 1;}lcsResult[t] = '\0';return lcsResult;}
時間複雜度:O(m*n), 空間複雜度O(m*n)
圖2:計數遞增的置數
改進:
正如上面代碼所示,使用了二維矩陣記錄每個相等的位置和子串的長度,空間複雜度為o(m*n)。從遞推公式得知中我們計算最長子串的下一個連續位置時,即C【i,j】時使用的僅僅和C【i-1,j】和C【i, j-1】,或者C【i-1, j-1】有關。那我們可以可以僅僅使用C【i-1,j】,C【i,j】兩行即可。
代碼如下:
#include <string>#include <assert.h>using namespace std;char* LCS(const char* shortStr, const char* longStr);int _tmain(int argc, _TCHAR* argv[]){const char* shortStr = "a2cett4ab";const char* longStr = "aceabfacett3abcccctaf";char* result = LCS(shortStr, longStr);printf("longest common substring is: %s", result);getchar();return 0;}char* LCS(const char* shortStr, const char* longStr){assert(shortStr && longStr);int maxlen = 0;int row = 0;unsigned slen = strlen( shortStr );unsigned llen = strlen( longStr );unsigned **arr = NULL;arr = (unsigned ** ) malloc ( 2 * sizeof(unsigned*));memset(arr, 0, 2 * sizeof(unsigned*));for(unsigned i = 0; i < 2; ++i) //僅僅申請了兩行資料{arr[i] = (unsigned *) malloc(slen * sizeof(unsigned));memset(arr[i], 0, slen * sizeof(unsigned));}for (unsigned j = 0; j < slen; ++j){if (shortStr[j] == longStr[0]){arr[0][j] = 1;}}for(int p = 0; p < slen ; ++p)printf(" %d", arr[0][p]);printf("\n");for(unsigned i = 1; i < llen ; ++i){for (unsigned j = 0; j < slen; ++j){if(shortStr[j] == longStr[i]){if (j == 0){arr[1][j] = 1;}elsearr[1][j] = arr[0][j-1] + 1;}if (arr[1][j] > maxlen){maxlen = arr[1][j];row = i + 1;}}memcpy(arr[0], arr[1], slen * sizeof(unsigned));memset(arr[1], 0, slen * sizeof(unsigned));for(int p = 0; p < slen ; ++p) //輸出printf(" %d", arr[0][p]);printf("\n");}for(int i = 0; i < 2; ++i)free(arr[i]);free(arr);char *lcsResult = NULL;lcsResult = (char*) malloc (maxlen * sizeof(char) + 1);memset(lcsResult, 0, maxlen + 1);int t;int tmp = maxlen;for (t = 0; t < maxlen; ++t){*(lcsResult + t) = *(longStr + row - tmp);tmp -= 1;}lcsResult[t] = '\0';return lcsResult;}
時間複雜度: O(m*n),空間複雜度: O(2*n)
學習思考:在使用動態規劃求最大公用子串LCS時,需要較大的記憶體開銷,即O(m*n),時間複雜度也是O(m*n),從遞推公式中我們發現可以把空間複雜度降低到O(2*n)。是一個很大的改進。那時間複雜度有沒有更好的最佳化呢?尾碼樹提供了一種更優的時間複雜度O(m)。以此為契機,學習尾碼樹。