Есть задачка: Дана последовательность целых чисел. Найти ее максимальную возрастающую подпоследовательность. Нашла решение в интернете, но никак не могу вникнуть(
+1000000 к карме тому, кто сможет мне объяснить принцип действия этой программы
#include
/* Finds longest strictly increasing subsequence. O(n log k) algorithm. */
template
if (a.size() < 1) return b;
b.push_back(0);
for (int i = 1; i < (int)a.size(); i++) {
if (a[b.back()] < a[i]) {
p[i] = b.back();
b.push_back(i);
continue;
}
for (u = 0, v = b.size()-1; u < v;) {
int c = (u + v) / 2;
if (a[b[c]] < a[i]) u=c+1; else v=c;
}
if (a[i] < a[b[u]]) {
if (u > 0) p[i] = b[u-1];
b[u] = i;
}
}
for (u = b.size(), v = b.back(); u--; v = p[v]) b[u] = v;
return b;
}
/* Example of usage: */
#include
for (unsigned i = 0; i < lis.size(); i++)
printf(i+1 < lis.size() ? "%d " : "%d
", seq[lis[i]]);
getch();
return 0;
}
Ответ
Смотрите, всё не так уж сложно.
Код отлично написан с точки зрения мелочей имплементации, но конечно названия переменных и повторное их использование с другим смыслом не добавляет понятности алгоритму.
Я переименовал переменные, разделил их где надо и снабдил комментариями.
/* Finds longest strictly increasing subsequence. O(n log k) algorithm. */
template
// (бывший массив p)
// вот это интересная штука. тут для каждого индекса i мы рассматриваем
// наилучшую из подпоследовательностей, заканчивающихся **на этом элементе**,
// и записываем в этот массив индекс предыдущего элемента.
// мы пользуемся таким важным свойством: для наилучшей подпоследовательности,
// заканчивающейся на i-ом элементе, её кусок без финального элемента обязан быть
// наилучшей подпоследовательностью, заканчивающейся на соответствующем
// элементе. поэтому для хранения наилучших подпоследовательностей достаточно
// лишь одного предыдущего индекса
vector
// бывший массив b
// а здесь мы храним индексы тех элементов, которые **в принципе** могут стать
// чьим-то предыдущим элементом в наилучшей последовательности.
// список хранится в таком порядке, чтобы соответствующие данные
// были отсортированы (чтобы можно было искать двоичным поиском)
vector
// тривиальный случай
if (data.size() < 1) return vector
possibleprev.push_back(0); // для одного элемента всё просто
for (int i = 1; i < (int)data.size(); i++) {
// вводим в рассмотрение следующий элемент и обновляем списки
T curr = data[i];
// если новый элемент больше всех возможных предыдущих элементов...
if (data[possibleprev.back()] < curr) {
// ... то всё просто: его предыдущий элемент - наибольший из возможных
previdx[i] = possibleprev.back();
// и он сам - тоже возможный наибольший для кого-то после
possibleprev.push_back(i);
continue; // всё, переходим к следующему элементу
}
// теперь более интересный случай: мы попали в середину
int leftidx, rightidx;
// старый знакомый -- поиск половинным делением.
// ищем в упорядоченном списке предыдущих элементов,
// куда можно вставить новый элемент
for (leftidx = 0, rightidx = possibleprev.size()-1; leftidx < rightidx;) {
int mididx = (leftidx + rightidx) / 2;
if (data[possibleprev[mididx]] < curr)
leftidx=mididx+1;
else
rightidx=mididx;
}
int foundidx = leftidx;
if (curr < data[possibleprev[foundidx]]) {
// нашли наш предыдущий индекс!
// записываем найденное значение в таблицу previdx:
if (foundidx > 0) previdx[i] = possibleprev[foundidx-1];
// и корректируем текущий список possibleprev
// ключевой момент - теперь найденный элемент в таблице possibleprev
// индекс больше не нужен
possibleprev[foundidx] = i;
}
}
// отлично, собираем ответ. это будет последовательность индексов,
// "связанная" через previdx
vector
Комментариев нет:
Отправить комментарий