給定兩個串序列A、B,找出兩個串的最長公共子序列LCS包含的元素和長度。html
注意:
(1) 一個序列S任意刪除若干個字符獲得新序列T,則T叫作S的子序列。
(2) 與最長公共子串(Longest Common Substring)相區別,最長公共子串要求元素相同且連續,而最長公共子序列只要求元素出現的順序一致,並不要求連續。即對於
串A:abcde
串B:bcdae
最長公共子串爲:bcd
最長公共子序列爲:bcde
該算法主要使用了動態規劃算法(DP)。java
算法中使用到的數據結構以下:node
示例:
X = < A,B,C,B,D,A,B >
Y = < B,D,C,A,B,A >
最長公共子序列爲:<B,C,B,A>c++
該過程生成的二維數組以下圖所示:
算法
int m = 7 + 1, n = 6 + 1; int c[m][n], b[m][n]; // 注意A、B字符串的第一個字符是不進行計算的,能夠用_來進行佔位 char A[] = "_ABCBDAB"; char B[] = "_BDCABA"; // 對第0行和第0列進行初始化 // c中 0爲初始值,1表明左,2表明上,3表明左上 for (int i = 0; i < m; i++) b[i][0] = c[i][0] = 0; for (int j = 0; j < n; j++) b[0][j] = c[0][j] = 0; for (int i = 1; i < m; i++){ for (int j = 1; j < n; j++){ if (A[i] == B[j]){ c[i][j] = c[i - 1][j - 1] + 1; b[i][j] = 3; } else{ if (c[i - 1][j] >= c[i][j - 1]){ c[i][j] = c[i - 1][j]; b[i][j] = 2; } else{ c[i][j] = c[i][j - 1]; b[i][j] = 1; } } } } // 輸出二維數組的值 for (int i = 0; i < m; i++){ for (int j = 0; j < n; j++){ cout << c[i][j] << "("; switch (b[i][j]){ case 1: cout << "←"; break; case 2: cout << "↑"; break; case 3: cout << "↖"; break; default: cout << " "; break; } cout << ") "; } cout << endl; }
LCS的應用:最長遞增子序列LIS(Longest Increasing Subsequence)
給定一個長度爲N的數組,找出一個最長的單調遞增子序列。
例如:給定數組{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列爲{5,6,7,8},長度爲4。數組
分析:其實此LIS問題能夠轉換成最長公子序列問題,爲何呢?數據結構
原數組爲A {5, 6, 7, 1, 2, 8}
排序後:A’ {1, 2, 5, 6, 7, 8}函數
由於,原數組A的子序列順序保持不變,並且排序後A’自己就是遞增的,這樣,就保證了兩序列的最
長公共子序列的遞增特性。如此,若想求數組A的最長遞增子序列,其實就是求數組A與它的排序數
組A’的最長公共子序列。性能
(此外,本題也能夠直接使用動態規劃/貪心法來求解)優化
求兩個序列中最長的公共子序列算法,普遍的應用在圖形類似處理、媒體流的類似比較、計算生物學
方面。生物學家經常利用該算法進行基因序列比對,由此推測序列的結構、功能和演化過程。
LCS能夠描述兩段文字之間的「類似度」,即它們的雷同程度,從而可以用來辨別抄襲。另外一方面,對
一段文字進行修改以後,計算改動先後文字的最長公共子序列,將除此子序列外的部分提取出來,這
種方法判斷修改的部分,每每十分準確。簡而言之,百度知道、百度百科都用得上。
給定一個串,找出該串的最長遞增子序列LIS包含的元素和長度。
最長遞增子序列,基本定義與最長公共子序列類似,還要求該序列是遞增序列。
注意:在計算b[i]
的時候,須要遍歷b
數組在i
以前全部位置j的值,取b[j]
爲最大值且aj<ai
,此時
b[i] = b[j] + 1
// a數組存儲序列元素 // b數組存儲LIS序列長度 // c數組存儲b[i]的計算來源位置的下標 int a[] = { 1, 4, 6, 2, 8, 9, 7 }; int n = 7; int b[n], c[n]; int i, j, k; // 初始化 for (int i = 0; i < n; i++) { b[i] = 1; c[i] = -1; } for (i = 1; i < n; i++) { k = -1; for (j = i - 1; j >= 0; j--) { if (a[j] < a[i] && b[j] > k) { b[i] = b[j] + 1; c[i] = j; k = b[j]; } } } // 自定義輸出數組函數,print(int a[], int n) cout << "array: "; print(a, n); cout << "LIS: "; print(b, n); cout << "pos: "; print(c, n); // stack用於存儲序列元素 // max爲最大的LIS序列的長度 int stack[n]; int top = -1, max = -1; k = -1; for (i = 0; i < n; i++) if (max < b[i]) { max = b[i]; k = i; } cout << "max: " << max << endl; while (c[k] != -1) { stack[++top] = a[k]; k = c[k]; } stack[++top] = a[k]; cout << "LIS序列: "; while (top != -1) { cout << stack[top--] << ", "; }
array: 1, 4, 6, 2, 8, 9, 7 LIS: 1, 2, 3, 2, 4, 5, 4 pos: -1, 0, 1, 0, 2, 4, 2 max: 5 LIS序列: 1, 4, 6, 8, 9
在計算機科學中,Knuth-Morris-Pratt字符串查找算法(簡稱爲KMP算法)可在一個主文本字符串S內查找一個詞W的出現位置。此算法經過運用對這個詞在不匹配時自己就包含足夠的信息來肯定下一個匹配將在哪裏開始的發現,從而避免從新檢查先前匹配的字符。
KMP算法解決的是字符串的查找問題,即:
給定文本串text和模式串pattern,從文本串text中找出模式串pattern第一次出現的位置。
算法步驟:
/** * 暴力法解字符串匹配問題 */ int brute_force_search(const char* s, const char* p){ // i爲當前匹配到的原始串首位 // j爲模式串的匹配位置 int i=0, j=0; int size = strlen(p); int last = strlen(s) - size; while (i<=last && j<size){ if(s[i+j] == p[j]) j++; else{ i++; j=0; } } if(j>=size) return i; return -1; }
/** * 獲得 next 數組 */ void get_next(char* p, int next[]){ int len = strlen(p); next[0] = -1; int k = next[0]; int j = 0; while (j < len - 1){ // 此時, k即next[j-1],且p[k]表示前綴,p[j]表示後綴 // 注:k==-1表示未找到k前綴與k後綴相等,首次分析可忽略 if(k == -1 || p[j] == p[k]){ ++j; ++k; next[j] = k; } else { // p[j]與p[k]失配,則繼續遞歸計算前綴p[next[k]] k = next[k]; } } } int kmp(char s[], char p[], int next[]){ int s_len = strlen(s); int p_len = strlen(p); int i = 0, j = -1; while (i<s_len && j<p_len){ if(j==-1 || s[i] == p[j]){ ++i; ++j; } else { j = next[j]; } } if (j >= p_len) return i - p_len; return -1; }
/** * 獲得優化以後的next數組,滑動匹配距離更大了,便於滑動匹配 */ void get_next_2(char* p, int next[]){ int len = strlen(p); next[0] = -1; int k = next[0]; int j = 0; while (j < len - 1){ // 此時, k即next[j-1],且p[k]表示前綴,p[j]表示後綴 // 注:k==-1表示未找到k前綴與k後綴相等,首次分析可忽略 if(k == -1 || p[j] == p[k]){ ++j; ++k; if(p[j] == p[k]) next[j] = next[k]; else next[j] = k; } else { // p[j]與p[k]失配,則繼續遞歸計算前綴p[next[k]] k = next[k]; } } } int kmp(char s[], char p[], int next[]){ int s_len = strlen(s); int p_len = strlen(p); int i = 0, j = -1; while (i<s_len && j<p_len){ if(j==-1 || s[i] == p[j]){ ++i; ++j; } else { j = next[j]; } } if (j >= p_len) return i - p_len; return -1; }
給定一個長度爲n的字符串S,若是存在一個字符串T,重複若干次T可以獲得S,那麼,S叫作週期串,T叫作S的一個週期。
如:字符串abababab是週期串,abab、ab都是它的週期,其中,ab是它的最小週期。
設計一個算法,計算S的最小週期。若是S不存在週期,返回空串。
int power_string(char* p){ int len = strlen(p); if(!len) return -1; int next[len]; int k = next[0] = -1; int j = 0; while (j < len - 1){ if(k == -1 || p[j+1] == p[k]){ ++j; ++k; next[j] = k; } else { k = next[k]; } } int last = next[len-1]; if(last == 0) return -1; if(len % (len - last) == 0) return len - last; return -1; }