Шаг 111.
Рекурсия на Python.
Множественная рекурсия II: пазлы, фракталы и прочее. Путь через болото

    На этом шаге мы разберем особенности решения этой задачи.

    В этой задаче (также известной как "проход по трясине") прямоугольное болото из квадратных участков задаётся матрицей A размерности n*m и начальной строкой r. Элементы матрицы (ai,j) могут иметь только два значения - "суша" ("кочка") или "топь". Цель задачи - найти путь через болото (от левого его края до правого), начав с заданного на его левом краю участка суши ar,0, согласно следующим правилам (см. рисунок 1(a)):


Рис.1. Задача о пути по болоту

    Рисунок 1(b) показывает путь через болото, где участки суши и топи изображаются светлыми и темными квадратами соответственно.

    Можно предположить, что размер задачи - это ширина болота (m). В первом начальном условии метод может просто вернуть False, если r выходит за пределы болота (r < 0 или r > n) или если начальный участок ar,0 - топь (отметим, что это последнее условие было бы ненужным, если бы мы сразу условились, что первый участок - это суша). Иначе функция должна будет вернуть True, если ширина болота равна 1 (это обеспечивается предыдущим начальным условием, проверяющим, что участок является сушей).

    В примере 7.1 приведена одна из многих возможных реализаций функции. Матрица (из пакета NumPy) содержит символы, где символ 'W' обозначает топь. Строки 2-5 определяют начальные условия. В строках 7, 8 и 9 метод определяет, можно ли перейти к ar-1,1 (по диагонали вверх), ar,1 (по горизонтали вправо) или ar+1,1 (по диагонали вниз) соответственно.


Пример 7.1. Функция поиска пути через болото
1
2
3
4
5
6
7
8
9
def exists_path_swamp(A, r):
    if r < 0 or r >= A.shape[0] or A[r, 0] == 'W':
        return False
    elif A.shape[1] == 1:
        return True
    else:
        return (exists_path_swamp(A[:, 1:], r - 1) or    
                exists_path_swamp(A[:, 1:], r) or
                exists_path_swamp(A[:, 1:], r + 1))
Архив с файлом можно взять здесь.

    В рекурсивном условии задача разбивается на три подзадачи размера m - 1 (см. рисунок 2).


Рис.2. Декомпозиция задачи о пути через болото

    В частности, можно предположить, что нам известен правильный путь через болото, начинающийся с участков ar-1,1, ar,1 или ar+1,1. Иными словами, можно считать, что нам известны решения трех меньших подзадач, возникающих после отбрасывания первого столбца болота и начинающихся с участков в строках r - 1, r и r + 1. Таким образом, рекурсивное условие будет состоять из трёх соответствующих подзадачам рекурсивных вызовов.

    Интересно выяснить, как влияют на алгоритм предварительные условия. В примере 7.2 есть предварительное условие для входа ar,0 ≠ 'W'. Поэтому ему не нужна проверка ar,0 = 'W' (см. строку 2 в примере 7.1). Кроме того, любой вызов метода, которому предшествует условный оператор, должен убедиться, что ar,0 ≠ 'W'. В итоге эффективность кода возросла за счёт включения условных операторов в строках 10 и 17, которые устраняют лишние рекурсивные вызовы, если путь через болото уже найден. Например, если уже существует путь вверх по диагонали, то не нужно проверять другие направления - по горизонтали или вниз по диагонали.


Пример 7.2. Альтернативная функция поиска пути через болото
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def exists_path_swamp_alt(A, r):
    if A.shape[1] == 1:
        return A[r, 0] != 'W'
    else:
        if r == 0 or A[r - 1, 1] == 'W':
            diag_up = False
        else:
            diag_up = exists_path_swamp_alt(A[:, 1:], r - 1)    

        if not diag_up:
            if r == A.shape[0] - 1 or A[r + 1, 1] == 'W':
                diag_down = False
            else:
                diag_down = exists_path_swamp_alt(
                    A[:, 1:], r + 1)

            if not diag_down:
                if A[r, 1] == 'W':
                    horizontal = False
                else:
                    horizontal = exists_path_swamp_alt(
                        A[:, 1:], r)

    return diag_up or diag_down or horizontal    
Архив с файлом можно взять здесь.

    Наконец, в примерах 7.1 и 7.2 размер матрицы уменьшается явно - вызовом метода с параметром A[:, 1:]. Другая возможность состоит в том, чтобы всегда передавать матрицу n*m целиком и управлять размером задачи с помощью входного параметра, задающего столбец с, с которого метод должен начать поиск пути. При таком сценарии отправной точкой пути была бы ar,c. В результате код оказался бы более общим, позволяя найти правильный путь через болото не только из его левого края, но и из любого участка суши ar,c.

    На следующем шаге мы рассмотрим ханойскую башню.




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