РУҚА
Тапсырмалар № 24, 27 · ЕГЭ

Построение перестановок

Рекурсивный перебор всех нұсқа с возвратом
6 мин чтенияҚиындық: Обновлено 29 қыркүйек 2026

Перестановка — это расположение всех данных элементов в некотором порядке. На этой странице разберём, как с помощью рекурсии и возврата получить каждую перестановку ровно один раз и применить такой перебор к задачам ЕГЭ-24 и ЕГЭ-27.

Идея построения перестановки

Пусть дан набор различных элементов \(a_1, a_2, \ldots, a_n\). Перестановку удобно строить слева направо. На первой позиции можно выбрать любой элемент, на второй — любой ещё не использованный, затем на третьей — любой из оставшихся. Когда все позиции заполнены, получен один готовый вариант.

D
Перестановка

Перестановка \(n\) различных элементов — последовательность длины \(n\), в которой каждый исходный элемент встречается ровно один раз.

\[P_n=n!\]1

Здесь \(P_n\) — число всех перестановок. Например, для трёх букв существует \(3!=6\) нұсқа: ABC, ACB, BAC, BCA, CAB, CBA. Рекурсивная программа должна перебрать именно эти \(6\) листьев дерева нұсқа, не повторяя их.

Перед написанием кода полезно вспомнить дерево рекурсивных вызовов. Каждый уровень дерева отвечает за одну позицию перестановки, а переход к следующему уровню означает выбор очередного элемента.

Состояние и возврат

Для перебора достаточно хранить два объекта: текущую последовательность и множество использованных элементов. Это состояние перебора: состояние перебора описывает, где находится алгоритм и какие шешімдер уже приняты.

  1. Выбрать элемент, который ещё не использован.
  2. Добавить его в текущую последовательность и отметить использованным.
  3. Рекурсивно заполнить следующую позицию.
  4. Удалить элемент из последовательности и снять отметку использования.

Последний шаг называется возвратом, или откатом. Он возвращает программу к состоянию до сделанного выбора, чтобы попробовать другой вариант. Поэтому метод относят к бэктрекингу. В специальной странице построение перестановок перебором рассматривается тот же приём как частный случай полного перебора.

T
Правило корректного отката

Каждое изменение состояния перед рекурсивным вызовом должно иметь обратное действие после него: добавили элемент — удалили его, отметили использованным — освободили.

\[\text{выбор}\;\to\;\text{рекурсия}\;\to\;\text{отмена выбора}\]
Python
1def generate(current, used):
2    if len(current) == n:
3        print(''.join(current))
4        return
5
6    for i in range(n):
7        if not used[i]:
8            used[i] = True
9            current.append(items[i])
10            generate(current, used)
11            current.pop()
12            used[i] = False
13
14items = ['A', 'B', 'C']
15n = len(items)
16generate([], [False] * n)

В условии остановки длина текущей последовательности равна \(n\). В цикле рассматриваются все индексы, но выбираются только элементы с признаком \(used[i]=False\). После возврата состояние снова такое же, как перед выбором.

Микро-проверка

Что произойдёт, если после рекурсивного вызова не выполнить current.pop() и used[i] = False?

Разобранный пример

Построим все перестановки чисел \(1,2,3\) и проследим первую ветвь. Сначала текущая последовательность пуста. На первом уровне выбирается \(1\), затем \(2\), затем \(3\). Получается \(123\). После печати программа отменяет выбор \(3\), возвращается на деңгей выбора второй позиции и пробует \(3\). Так появляется \(132\).

1
В начале ни один элемент не выбран, поэтому доступны все три.
\(\displaystyle current=[];\quad used=\{\}\)
2
Выбираем \(1\) для первой позиции.
\(\displaystyle current=[1];\quad used=\{1\}\)
3
Для второй позиции доступны \(2\) и \(3\); сначала выбираем \(2\).
\(\displaystyle current=[1,2];\quad used=\{1,2\}\)
4
Для последней позиции остаётся \(3\), получаем готовый нұсқа.
\(\displaystyle current=[1,2,3]\Rightarrow 123\)
5
Откатываем выбор \(3\), затем откатываем \(2\) и пробуем второй нұсқа второй позиции.
\(\displaystyle current=[1,3];\quad used=\{1,3\}\)
6
Оставшийся элемент \(2\) завершает ветвь.
\(\displaystyle current=[1,3,2]\Rightarrow 132\)
7
После полного отката первой позиции пробуются варианты, начинающиеся с \(2\), затем с \(3\).
123,132,213,231,312,321
№
Жауап примера

Все перестановки чисел \(1,2,3\) в порядке работы программы: \(123\), \(132\), \(213\), \(231\), \(312\), \(321\). Каждый нұсқа получен на отдельном листе дерева, а каждый элемент использован ровно один раз.

В задачах может требоваться не печатать варианты, а посчитать подходящие. Тогда в условии остановки проверяют готовую перестановку и увеличивают счётчик, если выполнено требование. Если условие уже нарушено на промежуточном шаге, можно применить правило отсечения ветвей и не продолжать эту ветвь.

Количество вызовов и сложность

В корне дерева есть \(n\) нұсқа выбора, на следующем уровне — \(n-1\), затем \(n-2\) и так далее. Поэтому число листьев равно факториалу \(n!\). Если программа формирует и выводит каждую перестановку целиком, на вывод требуется ещё \(n\) действий для бір листа.

\[T(n)=n\cdot T(n-1)+O(n),\qquad T(n)=O(n\cdot n!)\]

Память для текущей последовательности, массива признаков и стека рекурсивных вызовов составляет \(O(n)\), не считая сохранённых результатов. Глубина рекурсии равна \(n\), то есть используется глубина рекурсии \(n\).

nЧисло перестановок \(n!\)Число Деңгейлер
363
4244
51205
67206
750407
Как оценивать перебор

Сначала посчитайте число листьев: если алгоритм перебирает все перестановки, это \(n!\). Затем оцените работу на одном листе и глубину рекурсии. Для больших \(n\) полный перебор быстро становится непрактичным.

Важные варианты алгоритма

Если элементы могут повторяться, простое хранение индексов приведёт к одинаковым результатам. Например, для букв A, A, B выбор первой и второй A различается в программе, но не различается в строке. Для удаления дублей элементы на каждом уровне перебирают по значениям и не выбирают одинаковое значение дважды.

Другой способ — сначала отсортировать элементы, а затем пропускать повторный выбор одинаковых элементов на одном уровне. Если задача требует перестановки только части элементов, условие остановки меняется: например, при длине \(k\) печатают последовательность, когда \(len(current)=k\), не дожидаясь использования всех \(n\) элементов.

Иногда условие относится к соседним элементам: сумма соседей должна быть чётной, цифры не должны повторяться, запрещены определённые пары. Проверять такие ограничения можно сразу после добавления нового элемента. Это соответствует общей идее отсечения ветвей и уменьшает число вызовов.

!
Частые ошибки

1. Условие остановки проверяют после выбора, но забывают return, и программа продолжает обращаться к несуществующим позициям. 2. Признак used отмечают после рекурсивного вызова — тогда один элемент может попасть в несколько позиций. 3. После возврата не снимают used и не удаляют последний элемент. 4. Путают \(n!\) с \(2^n\): \(2^n\) обычно возникает при выборе «взять или не взять» каждый элемент, а перестановки требуют порядка. 5. При повторяющихся элементах считают одинаковые последовательности несколько раз.

Как решать экзаменационную тапсырманы

  • Определите, что именно строится: полная перестановка, часть длины \(k\) или перестановка с ограничениями.
  • Выберите состояние: текущая последовательность, used и, при необходимости, дополнительные параметры.
  • Запишите условие завершения рекурсии.
  • Добавьте выбор, рекурсивный вызов и обязательный откат в обратном порядке.
  • Проверьте малый пример вручную: для \(n=3\) должно получиться \(6\) нұсқа.
  • Оцените число нұсқа и убедитесь, что перебор укладывается в ограничения.

Если задача просит найти количество последовательностей, не обязательно хранить все варианты. Достаточно возвращать число подходящих ветвей или менять общий счётчик. Если нужно найти первый вариант, после успешного результата можно передать сигнал наверх и завершить поиск.

Быстрая проверка

Q
Жылдам тест по теме

Проверьте себя

~ 2 мин4 вопроса
Вопрос 1 / 4
Вопрос 1 из 4 · формула
Сколько полных перестановок имеют 5 различных элементов?
Главное за минуту

Главное

  • Полная перестановка \(n\) различных элементов имеет \(n!\) нұсқа.
  • Рекурсивный алгоритм на каждом уровне выбирает ещё не использованный элемент.
  • После рекурсивного вызова обязательно выполняется откат: удаление элемента и снятие отметки used.
  • Число листьев равно \(n!\), время полного перебора — обычно \(O(n\cdot n!)\), глубина стека — \(O(n)\).
  • Ограничения можно проверять сразу после выбора и отсекать заведомо неподходящие ветви.