给定两个串序列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; }