C, n = 3 через ~ 7 секунд
Алгоритм
Алгоритм является прямым обобщением стандартного решения динамического программирования для n
последовательностей. Для 2 строк A
и B
стандартное повторение выглядит так:
L(p, q) = 1 + L(p - 1, q - 1) if A[p] == B[q]
= max(L(p - 1, q), L(p, q - 1)) otherwise
Для 3 -х строк A
, B
, C
я использую:
L(p, q, r) = 1 + L(p - 1, q - 1, r - 1) if A[p] == B[q] == C[r]
= max(L(p - 1, q, r), L(p, q - 1, r), L(p, q, r - 1)) otherwise
Код реализует эту логику для произвольных значений n
.
КПД
Сложность моего кода O (s ^ n), с s
длиной строк. Исходя из того, что я обнаружил, похоже, проблема в NP-полноте. Таким образом, хотя опубликованный алгоритм очень неэффективен для больших значений n
, на самом деле может быть невозможно добиться больших успехов. Единственное, что я увидел, - это некоторые подходы, которые повышают эффективность для маленьких алфавитов. Поскольку здесь алфавит умеренно мал (16), это может привести к улучшению. Я все еще предсказываю, что никто не найдет законное решение, которое идет выше, чем n = 4
через 2 минуты, иn = 4
выглядит уже амбициозным.
Я уменьшил использование памяти в первоначальной реализации, чтобы она могла обрабатывать n = 4
достаточно времени. Но это только произвело длину последовательности, а не саму последовательность. Проверьте историю изменений этого поста, чтобы увидеть этот код.
Код
Поскольку для циклов над n-мерными матрицами требуется больше логики, чем для фиксированных циклов, я использую фиксированный цикл для наименьшего измерения и использую только универсальную логику циклов для остальных измерений.
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define N_MAX 4
int main(int argc, char* argv[]) {
int nSeq = argc - 1;
if (nSeq > N_MAX) {
nSeq = N_MAX;
}
char** seqA = argv + 1;
uint64_t totLen = 1;
uint64_t lenA[N_MAX] = {0};
uint64_t offsA[N_MAX] = {1};
uint64_t offsSum = 0;
uint64_t posA[N_MAX] = {0};
for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
lenA[iSeq] = strlen(seqA[iSeq]);
totLen *= lenA[iSeq] + 1;
if (iSeq + 1 < nSeq) {
offsA[iSeq + 1] = totLen;
}
offsSum += offsA[iSeq];
}
uint16_t* mat = calloc(totLen, 2);
uint64_t idx = offsSum;
for (;;) {
for (uint64_t pos0 = 0; pos0 < lenA[0]; ++pos0) {
char firstCh = seqA[0][pos0];
int isSame = 1;
uint16_t maxVal = mat[idx - 1];
for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
char ch = seqA[iSeq][posA[iSeq]];
isSame &= (ch == firstCh);
uint16_t val = mat[idx - offsA[iSeq]];
if (val > maxVal) {
maxVal = val;
}
}
if (isSame) {
mat[idx] = mat[idx - offsSum] + 1;
} else {
mat[idx] = maxVal;
}
++idx;
}
idx -= lenA[0];
int iSeq = 1;
while (iSeq < nSeq && posA[iSeq] == lenA[iSeq] - 1) {
posA[iSeq] = 0;
idx -= (lenA[iSeq] - 1) * offsA[iSeq];
++iSeq;
}
if (iSeq == nSeq) {
break;
}
++posA[iSeq];
idx += offsA[iSeq];
}
int score = mat[totLen - 1];
char* resStr = malloc(score + 1);
resStr[score] = '\0';
for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
posA[iSeq] = lenA[iSeq] - 1;
}
idx = totLen - 1;
int resIdx = score - 1;
while (resIdx >= 0) {
char firstCh = seqA[0][posA[0]];
int isSame = 1;
uint16_t maxVal = mat[idx - 1];
int maxIdx = 0;
for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
char ch = seqA[iSeq][posA[iSeq]];
isSame &= (ch == firstCh);
uint16_t val = mat[idx - offsA[iSeq]];
if (val > maxVal) {
maxVal = val;
maxIdx = iSeq;
}
}
if (isSame) {
resStr[resIdx--] = firstCh;
for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
--posA[iSeq];
}
idx -= offsSum;
} else {
--posA[maxIdx];
idx -= offsA[maxIdx];
}
}
free(mat);
printf("score: %d\n", score);
printf("%s\n", resStr);
return 0;
}
Инструкция по бегу
Бежать:
- Сохраните код в файле, например
lcs.c
.
Компилировать с высокими параметрами оптимизации. Я использовал:
clang -O3 lcs.c
В Linux я бы попробовал:
gcc -Ofast lcs.c
Запустите от 2 до 4 последовательностей, заданных в качестве аргументов командной строки:
./a.out axbycz xaybzc
При необходимости заключите в кавычки аргументы командной строки, так как алфавит, используемый для примеров, содержит специальные символы оболочки.
Полученные результаты
test2.sh
и test3.sh
тестовые последовательности от Денниса. Я не знаю правильных результатов, но результат выглядит как минимум правдоподобным.
$ ./a.out axbycz xaybzc
score: 3
abc
$ time ./test2.sh
score: 391
16638018802020>3??3232270178;47240;24331395?<=;99=?;178675;866002==23?87?>978891>=9<6<9381992>>7030829?255>6804:=3>:;60<9384=081;0:?66=51?0;5090724<85?>>:2>7>3175?::<9199;5=0:494<5<:7100?=95<91>1887>33>67710==;48<<327::>?78>77<6:2:02:<7=5?:;>97<993;57=<<=:2=9:8=118563808=962473<::8<816<464<1==925<:5:22?>3;65=>=;27?7:5>==3=4>>5>:107:20575347>=48;<7971<<245<??219=3991=<96<??735698;62?<98928
real 0m0.012s
user 0m0.008s
sys 0m0.003s
$ time ./test3.sh
score: 269
662:2=2::=6;738395=7:=179=96662649<<;?82?=668;2?603<74:6;2=04=>6;=6>=121>1>;3=22=3=3;=3344>0;5=7>>7:75238;559133;;392<69=<778>3?593?=>9799<1>79827??6145?7<>?389?8<;;133=505>2703>02?323>;693995:=0;;;064>62<0=<97536342603>;?92034<?7:=;2?054310176?=98;5437=;13898748==<<?4
real 0m7.218s
user 0m6.701s
sys 0m0.513s