Решение задачи о ходе коня

Алгоритм, который мы используем для решения задачи о ходе коня, называется поиск в глубину (DFS - от англ. depth first search). В отличие от алгоритма поиска в ширину, обсуждавшегося в предыдущем разделе и строящего один уровень дерева поиска за раз, поиск в глубину создаёт дерево поиска исследуя одну из ветвей настолько глубоко, насколько это возможно. В этом разделе мы рассмотрим два алгоритма для реализации DFS. Первый будет непосредственно решать задачу о ходе коня, явно запрещая посещать исследованный узел более, чем один раз. Второй - более общий случай, позволяющий в процессе конструирования дерева посещать узлы больше одного раза. Он будет использоваться в следующих разделах при разработке дополнительных алгоритмов для графов.

Исследование графа в глубину - в точности то, что нам необходимо для поиска пути ровно в 63 ребра. Мы увидим, что когда алгоритм заходит в тупик (место на графе, откуда больше нет возможных ходов), он возвращается вверх по дереву на следующую по глубине вершину, что позволяет сделать допустимый ход.

Функция knightTour принимает четыре параметра: n - текущую глубину дерева, path - список посещённых вершин, u - вершину, которую мы бы хотели исследовать, и limit - количество узлов в графе. Эта функция рекурсивная. Когда она вызыввается, то прежде всего проверяет базовое условие. Если путь содержит 64 вершины, то мы возвращаем knightTour со статусом True, показывающим, что поиск маршрута успешно завершён. В противном случае исследование продолжается на уровне ниже, для чего выбирается новая вершина и вызвавается knightTour.

DFS тоже использует окрашивание для отслеживания, какие вершины графа были посещены. Непосещённые узлы имеют белый цвет, посещённые - серый. Если все соседи данной вершины уже исследованы, но цель в 64 узла нами не достигнута, то мы зашли в тупик. Следовательно, необходимо вернуться назад. Бэктрекинг происходит в том случае, когда knightTour возвращает False. В поиске в ширину для отслеживания вершины, которую нужно посетить следующей, мы использовали очередь. Поскольку поиск в глубину рекурсивен, для помощи с бэктрекингом неявно используется стек. Когда вызов knightTour возвращает False (строка 11), мы попрежнему находимся внутри цикла while и рассматриваем следующую вершину из nbrList.

Листинг 3

from pythonds.graphs import Graph, Vertex
def knightTour(n,path,u,limit):
        u.setColor('gray')
        path.append(u)
        if n < limit:
            nbrList = list(u.getConnections())
            i = 0
            done = False
            while i < len(nbrList) and not done:
                if nbrList[i].getColor() == 'white':
                    done = knightTour(n+1, path, nbrList[i], limit)
                i = i + 1
            if not done:  # prepare to backtrack
                path.pop()
                u.setColor('white')
        else:
            done = True
        return done

Давайте рассмотрим в действии простой пример работы knightTour. Для отслеживания шагов поиска вы можете обратиться к приведённым ниже рисункам. Будем полагать, что вызов метода getConnections в строке 6 происходит в алфавитном порядке. Начнём с вызова knightTour(0,path,A,6).

knightTour начинает работу с узла А (рисунок 3). А имеет смежные вершины B и D. Поскольку в алфавите B идёт перед D, то DFS выбирает её для дальнейшего исследования, как показано на рисунке 4. Для этого рекурсивно вызывается knightTour. В смежна с C и D, поэтому следующим knightTour выбирает исследование С. Однако, как вы можете видеть из рисунка 5, вершина С - тупик без смежных белых узлов. В этот момент мы меняем цвет С обратно на белый, и вызов knightTour возвращает False. Возврат из рекурсивного вызова эффективно откатывает поиск к вершине В (см. рисунок 6). Следующая вершина в списке исследования - узел D, так что knightTour делает рекурсивный вызов для D (см. рисунок 7). Из неё knightTour может продолжить делать рекурсивные вызовы до тех пор, пока вновь не дойдёт до узла С (см. рисунок 8, рисунок 9 и рисунок 10). Однако в этот раз проверка n < limit даёт отрицательный результат, поэтому мы знаем, что узлы графа закончились. Теперь можно вернуть True, показывая, что маршрут по графу успешно проложен. Когда мы вернём список, path будет иметь значение [A,B,D,E,F,C] - порядок, в котором нужно обойти граф, чтобы посетить каждую вершину всего один раз.

../_images/ktdfsa.png

Рисунок 3: Начинаем с узла A

../_images/ktdfsb.png

Рисунок 4: Исследуем B

../_images/ktdfsc.png

Рисунок 5: Узел C - тупик

../_images/ktdfsd.png

Рисунок 6: Возврат в B

../_images/ktdfse.png

Рисунок 7: Исследуем D

../_images/ktdfsf.png

Рисунок 8: Исследуем E

../_images/ktdfsg.png

Рисунок 9: Исследуем F

../_images/ktdfsh.png

Рисунок 10: Конец

На рисунке 11 показано, как целиком выглядит маршрут по доске 8х8. Таких маршрутов может быть много; некоторые из них будут симметричными. Добавив несколько изменений, вы можете получить замкнутые маршруты, начинающиеся и заканчивающиеся в одной клетке.

../_images/completeTour.png

Рисунок 11: Маршрут по доске целиком

Next Section - Анализ задачи о ходе коня