Как найти самую длинную общую подстроку с помощью c++
Я искал в интернете самую длинную общую реализацию подстроки C++, но не смог найти достойную. Мне нужен алгоритм LCS, который возвращает саму подстроку, поэтому это не просто LCS.
Мне было интересно, как я могу сделать это между несколькими строками.
моя идея состояла в том, чтобы проверить самый длинный между 2 строками, а затем проверить все остальные, но это очень медленный процесс, который требует управления многими длинными строками в памяти, что делает мою программу довольно медленный.
любая идея о том, как это можно ускорить для нескольких строк? Спасибо.
Важно Изменить Одна из переменных, которые мне даны, определяет количество строк, в которых должна быть самая длинная общая подстрока, поэтому мне можно дать 10 строк и найти LCS из них всех (K=10) или LCS из 4 из них, но мне не сказали, какие 4, я должен найти лучшие 4.
7 ответов
вот отличная статья о поиск всех общих подстрок эффективно, с примерами В C. Это может быть излишним, если вам нужно только самое длинное, но это может быть проще понять, чем общие статьи о деревьях суффиксов.
ответ-обобщенное дерево суффиксов. http://en.wikipedia.org/wiki/Generalised_suffix_tree
вы можете построить обобщенное суффиксное дерево с несколькими строками.
посмотрите на это http://en.wikipedia.org/wiki/Longest_common_substring_problem
дерево суффиксов может быть построено в O(n) времени для каждой строки, K*O (n) в общей сложности. K-общее количество строк.
Так это очень быстро решить эту проблему.
это проблема динамического программирования и может быть решена за время O (mn), где m-длина одной строки, а n-другой.
Как и любая другая проблема решена с помощью динамического программирования, мы разобьем задачу на подзадачи. Скажем, если две строки x1x2x3....xm и y1y2y3...yn
S (i,j) - самая длинная общая строка для x1x2x3...xi и y1y2y3....yj, тогда
S (i,j) = max { длина самой длинной общей подстроки заканчивая xi/ yj, если (x[i] == y[j] ), S (i-1, j-1)), S (i, j-1), S (i-1, j) }
вот рабочая программа на Java. Я уверен, что вы можете преобразовать его в C++.:
public class LongestCommonSubstring {
public static void main(String[] args) {
String str1 = "abcdefgijkl";
String str2 = "mnopabgijkw";
System.out.println(getLongestCommonSubstring(str1,str2));
}
public static String getLongestCommonSubstring(String str1, String str2) {
//Note this longest[][] is a standard auxialry memory space used in Dynamic
//programming approach to save results of subproblems.
//These results are then used to calculate the results for bigger problems
int[][] longest = new int[str2.length() + 1][str1.length() + 1];
int min_index = 0, max_index = 0;
//When one string is of zero length, then longest common substring length is 0
for(int idx = 0; idx < str1.length() + 1; idx++) {
longest[0][idx] = 0;
}
for(int idx = 0; idx < str2.length() + 1; idx++) {
longest[idx][0] = 0;
}
for(int i = 0; i < str2.length(); i++) {
for(int j = 0; j < str1.length(); j++) {
int tmp_min = j, tmp_max = j, tmp_offset = 0;
if(str2.charAt(i) == str1.charAt(j)) {
//Find length of longest common substring ending at i/j
while(tmp_offset <= i && tmp_offset <= j &&
str2.charAt(i - tmp_offset) == str1.charAt(j - tmp_offset)) {
tmp_min--;
tmp_offset++;
}
}
//tmp_min will at this moment contain either < i,j value or the index that does not match
//So increment it to the index that matches.
tmp_min++;
//Length of longest common substring ending at i/j
int length = tmp_max - tmp_min + 1;
//Find the longest between S(i-1,j), S(i-1,j-1), S(i, j-1)
int tmp_max_length = Math.max(longest[i][j], Math.max(longest[i+1][j], longest[i][j+1]));
if(length > tmp_max_length) {
min_index = tmp_min;
max_index = tmp_max;
longest[i+1][j+1] = length;
} else {
longest[i+1][j+1] = tmp_max_length;
}
}
}
return str1.substring(min_index, max_index >= str1.length() - 1 ? str1.length() - 1 : max_index + 1);
}
}
существует очень элегантное решение для динамического программирования.
пусть LCSuff[i][j]
быть самым длинным общим суффиксом между X[1..m]
и Y[1..n]
. У нас здесь два случая:
X[i] == Y[j]
, это означает, что мы можем расширить самый длинный общий суффикс междуX[i-1]
иY[j-1]
. Таким образомLCSuff[i][j] = LCSuff[i-1][j-1] + 1
в этом случае.X[i] != Y[j]
, так как последние символы сами по себе разные,X[1..i]
иY[1..j]
не может иметь общего суффикса. Следовательно,LCSuff[i][j] = 0
в этом случае.
теперь нам нужно проверить максимум этих самых длинных общих суффиксов.
и LCSubstr(X,Y) = max(LCSuff(i,j))
, где 1<=i<=m
и 1<=j<=n
алгоритм в значительной степени пишет себя сейчас.
string LCSubstr(string x, string y){
int m = x.length(), n=y.length();
int LCSuff[m][n];
for(int j=0; j<=n; j++)
LCSuff[0][j] = 0;
for(int i=0; i<=m; i++)
LCSuff[i][0] = 0;
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
if(x[i-1] == y[j-1])
LCSuff[i][j] = LCSuff[i-1][j-1] + 1;
else
LCSuff[i][j] = 0;
}
}
string longest = "";
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
if(LCSuff[i][j] > longest.length())
longest = x.substr((i-LCSuff[i][j]+1) -1, LCSuff[i][j]);
}
}
return longest;
}
вот версия C#, чтобы найти самую длинную общую подстроку, используя динамическое программирование двух массивов (вы можете обратиться к:http://codingworkout.blogspot.com/2014/07/longest-common-substring.html Для больше деталей)
class LCSubstring
{
public int Length = 0;
public List<Tuple<int, int>> indices = new List<Tuple<int, int>>();
}
public string[] LongestCommonSubStrings(string A, string B)
{
int[][] DP_LCSuffix_Cache = new int[A.Length+1][];
for (int i = 0; i <= A.Length; i++)
{
DP_LCSuffix_Cache[i] = new int[B.Length + 1];
}
LCSubstring lcsSubstring = new LCSubstring();
for (int i = 1; i <= A.Length; i++)
{
for (int j = 1; j <= B.Length; j++)
{
//LCSuffix(Xi, Yj) = 0 if X[i] != X[j]
// = LCSuffix(Xi-1, Yj-1) + 1 if Xi = Yj
if (A[i - 1] == B[j - 1])
{
int lcSuffix = 1 + DP_LCSuffix_Cache[i - 1][j - 1];
DP_LCSuffix_Cache[i][j] = lcSuffix;
if (lcSuffix > lcsSubstring.Length)
{
lcsSubstring.Length = lcSuffix;
lcsSubstring.indices.Clear();
var t = new Tuple<int, int>(i, j);
lcsSubstring.indices.Add(t);
}
else if(lcSuffix == lcsSubstring.Length)
{
//may be more than one longest common substring
lcsSubstring.indices.Add(new Tuple<int, int>(i, j));
}
}
else
{
DP_LCSuffix_Cache[i][j] = 0;
}
}
}
if(lcsSubstring.Length > 0)
{
List<string> substrings = new List<string>();
foreach(Tuple<int, int> indices in lcsSubstring.indices)
{
string s = string.Empty;
int i = indices.Item1 - lcsSubstring.Length;
int j = indices.Item2 - lcsSubstring.Length;
Assert.IsTrue(DP_LCSuffix_Cache[i][j] == 0);
for(int l =0; l<lcsSubstring.Length;l++)
{
s += A[i];
Assert.IsTrue(A[i] == B[j]);
i++;
j++;
}
Assert.IsTrue(i == indices.Item1);
Assert.IsTrue(j == indices.Item2);
Assert.IsTrue(DP_LCSuffix_Cache[i][j] == lcsSubstring.Length);
substrings.Add(s);
}
return substrings.ToArray();
}
return new string[0];
}
где юнит-тесты:
[TestMethod]
public void LCSubstringTests()
{
string A = "ABABC", B = "BABCA";
string[] substrings = this.LongestCommonSubStrings(A, B);
Assert.IsTrue(substrings.Length == 1);
Assert.IsTrue(substrings[0] == "BABC");
A = "ABCXYZ"; B = "XYZABC";
substrings = this.LongestCommonSubStrings(A, B);
Assert.IsTrue(substrings.Length == 2);
Assert.IsTrue(substrings.Any(s => s == "ABC"));
Assert.IsTrue(substrings.Any(s => s == "XYZ"));
A = "ABC"; B = "UVWXYZ";
string substring = "";
for(int i =1;i<=10;i++)
{
A += i;
B += i;
substring += i;
substrings = this.LongestCommonSubStrings(A, B);
Assert.IsTrue(substrings.Length == 1);
Assert.IsTrue(substrings[0] == substring);
}
}
я попробовал несколько разных решений для этого, но все они казались очень медленными, поэтому я придумал ниже, не очень много тестировал, но, похоже, работает немного быстрее для меня.
#include <iostream>
std::string lcs( std::string a, std::string b )
{
if( a.empty() || b.empty() ) return {} ;
std::string current_lcs = "";
for(int i=0; i< a.length(); i++) {
size_t fpos = b.find(a[i], 0);
while(fpos != std::string::npos) {
std::string tmp_lcs = "";
tmp_lcs += a[i];
for (int x = fpos+1; x < b.length(); x++) {
tmp_lcs+=b[x];
size_t spos = a.find(tmp_lcs, 0);
if (spos == std::string::npos) {
break;
} else {
if (tmp_lcs.length() > current_lcs.length()) {
current_lcs = tmp_lcs;
}
}
}
fpos = b.find(a[i], fpos+1);
}
}
return current_lcs;
}
int main(int argc, char** argv)
{
std::cout << lcs(std::string(argv[1]), std::string(argv[2])) << std::endl;
}
найдите самую большую подстроку из всех рассматриваемых строк. Из N строк у вас будет N подстрок. Выберите самый большой из этих N.