Шаг 193.
Рекурсия на Python. Множественная рекурсия III: ... . Проверка правильности частичных решений без использования дополнительных структур данных

    На этом шаге мы рассмотрим пример, реализующий такой подход.

    В примере 12.3 приводится первый метод, соответствующий дереву рекурсии на рисунке 1 предыдущего шага.


Пример 12.3. Печать всех перестановок элементов множества из списка
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def generate_permutations(i, sol, elements):
    # Базовый вариант
    if i == len(elements):
        print_permutations(sol)  # комплексное решение
    else:
        # Генерация элементов-кандидатов
        for k in range(0, len(elements)):

            # Проверка валидности кандидатов
            if not elements[k] in sol[0:i]:

                # Включить кандидата в частное решение
                sol[i] = elements[k]

                # Генерация элемента в позиции i+1
                generate_permutations(i + 1, sol, elements)  

                # Удалить кандидата из частного решения
                sol[i] = None  # по желанию


def generate_permutations_wrapper(elements):
    sol = [None] * (len(elements))
    generate_permutations(0, sol, elements)


def print_permutations(sol):
    for i in range(0, len(sol)):
        print(sol[i], ' ', end='')
    print()
Архив с файлом можно взять здесь.

    Поскольку i - это число элементов в частичном решении sol, первый условный оператор if выполняет проверку начального условия. Если результат - True, процедура просто печатает перестановку, сохранённую в sol. Иначе алгоритм в цикле начинает добавлять в частичное решение новых кандидатов из исходного списка. Для каждого значения от 0 до n - 1 счётчика цикла k в роли индекса элемента списка алгоритм выполняет проверку на отсутствие кандидата в частичном решении (строка 10). Стоит отметить, что, несмотря на лаконичность записи условия в строке 10, оно может потребовать в самом худшем случае i проверок значений кандидатов. Если кандидата ещё нет в частичном решении, метод добавляет его (строка 13) и выполняет рекурсивный вызов с увеличением i на единицу (строка 16). Следующая за рекурсивным вызовом строка 19 с "отклонением" кандидата не обязательна, так как метод всё равно перезаписывает значение sol[i]. Иными словами, нет нужды присваивать sol[i] значение None. Тем не менее эта строка включена в код, чтобы он точно соответствовал дереву рекурсии на рисунке 1 192 шага.

    Метод generate_permutations_wrapper() - это процедура-оболочка, необходимая для инициализации частичного решения n значениями None и вызова рекурсивного метода generate_permutations() с нулевым значением i. В заключение код содержит процедуру печати через пробел элементов списка (перестановки) sol.

    Итак, подобно остальным алгоритмам этого раздела, метод использует цикл для подбора возможных кандидатов на включение в частичное решение. Без учёта проверок ограничений во внутренних узлах дерева данный алгоритм может привести к полному n-арному дереву рекурсии (у каждого внутреннего узла дерева n дочерних узлов) с nn листьями. Конечно, можно проверять правильность частичных решений в каждом из этих nn листьев. Но гораздо лучше проверять допустимость кандидата во внутреннем узле и отказываться от ненужных рекурсивных вызовов, если алгоритм обнаруживает совершенно недопустимое частичное решение. Условие в строке 6 обеспечивает такую проверку и резко ускоряет алгоритм, сокращая дерево рекурсии, как показано на рисунке 1.


Рис.1. Сокращение дерева рекурсии за счёт исключения недопустимых частичных решений

    Более светлые узлы и ветви дерева - это те вызовы метода полного троичного дерева, которые не выполняются (сокращаются) благодаря условию в строке 6. Получающееся дерево рекурсии (изображенное более тёмными узлами и ветвями) становится идентичным изображённому на рисунке 1 192 шага и содержит n! листьев. Хотя эта величина тоже внушительна даже для относительно малых значений n, она всё же значительно меньше nn.

    На следующем шаге мы рассмотрим проверку правильности частичных решений с использованием дополнительных структур данных.




Предыдущий шаг Содержание Следующий шаг