Сортировка стеком
Вагоны, тупик и один разъезд. Задача, где жадность единственно возможна, — а количество ответов оказывается числом Каталана.
4 мин
Вагоны стоят на первом пути в каком-то порядке. Разрешено загонять вагоны с первого пути в тупик и вытаскивать их из тупика на второй путь. Обратно — нельзя. Требуется получить на втором пути вагоны в порядке .
Тупик работает как стек: вагон, загнанный последним, выйдет первым. Значит, вопрос звучит так: какие перестановки сортируются одним стеком?
Почему жадность здесь не выбор, а необходимость
Обычно жадный алгоритм надо доказывать. Здесь доказывать нечего: на каждом шаге допустимо ровно одно осмысленное действие.
Пусть мы уже вывели вагоны и ищем вагон .
- Если лежит на вершине тупика — снимаем и выводим. Не сделать этого нельзя: пока не выведен, дальше двигаться некуда, а закопать его глубже мы не можем.
- Если ещё на первом пути — придётся загнать в тупик все вагоны до него включительно. Других способов добраться до нет.
- Если в тупике, но не на вершине — проиграли. Сверху лежит вагон с большим номером, он обязан выйти раньше, а нужен нам позже.
Выбора нет ни в одной ветке. Поэтому алгоритм, который просто следует этим правилам, либо приводит к цели, либо доказывает, что цели нет.
bool sortable(const vector<int>& a) {
int n = a.size();
vector<int> st;
int need = 1, i = 0;
while (need <= n) {
if (!st.empty() && st.back() == need) { st.pop_back(); need++; continue; }
if (i == n) return false; // на первом пути пусто, а нужного нет
st.push_back(a[i++]);
}
return true;
}
Каждый вагон входит в стек не более одного раза и выходит не более одного раза, поэтому — несмотря на вложенный цикл. Это амортизированная оценка в чистом виде.
Проверено: на всех перестановках длины до восьми жадный алгоритм совпал с полным перебором состояний.
Критерий
Есть и локальное описание. Перестановка сортируется стеком тогда и только тогда, когда в ней нет образца 231 — то есть нет тройки индексов с .
Смысл прямой: попадёт в стек раньше и окажется под ним, а вывести надо сначала , потом , потом . Вагон лежит сверху и мешает.
Проверять критерий напрямую не нужно — симуляция и короче, и быстрее. Но он объясняет следующий факт.
Сколько таких перестановок
| сортируемых | всего | |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 5 | 6 |
| 4 | 14 | 24 |
| 5 | 42 | 120 |
| 6 | 132 | 720 |
| 7 | 429 | 5 040 |
| 8 | 1 430 | 40 320 |
| 9 | 4 862 | 362 880 |
Это в точности числа Каталана — те же, что считают правильные скобочные последовательности. Совпадение не случайное: последовательность действий со стеком — это и есть скобочная последовательность, где «загнать в тупик» — открывающая скобка, «вытащить» — закрывающая.
Соответствие взаимно однозначное, поэтому и количества совпадают. Посчитано перебором всех перестановок до девяти элементов.
Заодно это оценка, насколько задача жёсткая: доля сортируемых перестановок падает экспоненциально. При это уже 1,3%.
Вариации
Два стека или дек вместо одного стека. Класс сортируемых перестановок расширяется, критерий усложняется, а жадность перестаёт быть единственно возможной — появляется настоящий выбор, и задача становится содержательно другой.
Нужен не ответ «да/нет», а последовательность операций. Тот же алгоритм, только записываем каждое действие: push при загоне в тупик, pop при выводе. Длина протокола ровно .
Целевой порядок не , а произвольный. Перенумеруйте вагоны так, чтобы целевой порядок стал возрастающим, и запустите то же самое.