Написание функции Python, которая находит гамильтонов путь в графе

Я пытался написать функцию, которая будет принимать положительное целое число n в качестве входных данных и размещать целые числа от 1 до n в таком порядке, чтобы сумма каждого соседнего числа была идеальным квадратом (если такой порядок существует). Я понял, что если я создам граф, где вершины — числа, и между двумя вершинами есть ребро, если их сумма — идеальный квадрат, то эта проблема эквивалентна попытке найти гамильтонов путь в графе. Итак, я пытаюсь написать функцию, которая найдет гамильтонов граф, если он существует, в заданном графе. Вот мой код:

def hampath_finder(moves, start, path=None):
    if path is None:
        path = []
    if len(path) == bound:
        return path
    if not path:
        path = path + [start]
    for candidate in moves[start]:
        if candidate not in path:
            path = path + [candidate]
            new_path = hampath_finder(moves, candidate, path)
            if new_path:
                return new_path
            else:
                continue
    else:
        return None
    return None

Moves — это словарь графа (граф переменных уже использовался, и я не умею называть переменные), где каждая вершина — это ключ, а значение каждого ключа — это список, содержащий другие вершины, соседние с ключевой вершиной. Например, когда на входе 15, это словарь:

{1: [3, 8, 15], 2: [7, 14], 3: [1, 6, 13], 4: [5, 12], 5: [4, 11], 6: [3, 10], 7: [2, 9], 8: [1], 9: [7], 10: [6, 15], 11: [5, 14], 12: [4, 13], 13: [3, 12], 14: [2, 11], 15: [1, 10]}

Старт — это начальная точка гамильтоновой траектории. (Я пытался написать эту функцию без начальной точки, чтобы сама функция пробовала каждую точку в качестве отправной точки, но это усложнилось. Сейчас я просто перебираю все вершины самостоятельно.)

Я знаю, что для числа 15 мне полагается дать следующий список:

[9, 7, 2, 14, 11, 5, 4, 12, 13, 3, 6, 10, 15, 1, 8]

Однако вместо этого он дает мне этот список:

[9, 7, 2, 14, 11, 5, 4, 12, 13, 3, 1, 8, 15, 10, 6]

Размышляя о том, как работает функция, я понял, что как только она достигает 1, она сначала добавляет 8 в качестве следующего числа. Однако число 8 не имеет ребра между вершинами, отличными от 1. Честно говоря, я понятия не имею, что оно делает дальше. Я понял, что когда у него нет возможных кандидатов для попытки, ему нужно вернуться назад и вернуться к последней нормальной позиции. Я не знаю, как это реализовать.

Как я могу решить эту проблему? Кроме того, как я могу улучшить свой код?

Я новичок в Python, поэтому прошу прощения, если этот вопрос тривиален или мой код ужасен.

Изменить: я думаю, что исправил основную проблему, и теперь он возвращает правильный список. Вот новый код:

def hampath_finder(moves, start, path=None):
    if path is None:
        path = []
    if len(path) == bound:
        return path
    if not path:
        path = path + [start]
    for candidate in moves[start]:
        if candidate not in path:
            new_path = hampath_finder(moves, candidate, path + [candidate])
            if new_path:
                return new_path

Я думаю, проблема была в том, что как только мы зашли в тупик, неправильный путь уже был добавлен в список path, поэтому в выводе предыдущего кода была цифра 8.

Теперь проблема в том, что функция возвращает None после возврата списка. Итак, вот результат, когда я запускаю эту функцию для числа 15, т.е. график представляет собой словарь, о котором я упоминал ранее:

[8, 1, 15, 10, 6, 3, 13, 12, 4, 5, 11, 14, 2, 7, 9]
None

Как я могу это исправить, чтобы он не возвращал None? Между прочим, я все еще должен попробовать каждый номер в качестве отправной точки. Вот что я делаю:

for number in range(1, 16):
    if hampath_finder(moves, number):
        print(hampath_finder(moves,number))

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

Кроме того, эта функция занимает много времени даже для небольших чисел. Как я могу сделать его более эффективным?

Изменить: я понимаю, что включение функции всей вместо только части пути Гамильтона более полезно, поскольку в противном случае некоторые переменные не определены.

from math import sqrt


def adjacent_square(bound):
    def blueprint(bound):
        graph = {}
        for number in range(1, bound + 1):
            pos_neighbours = []
            for candidate in range(1, bound + 1):
                if sqrt(number + candidate) == int(sqrt(number + candidate)) and number != candidate:
                    pos_neighbours.append(candidate)
            graph[number] = pos_neighbours
        return graph

    graph = blueprint(bound)

    def hampath_finder(mapping, start, path=None):
        if path is None:
            path = []
        if len(path) == bound:
            return path
        if not path:
            path = path + [start]
        for candidate in mapping[start]:
            if candidate not in path:
                new_path = hampath_finder(mapping, candidate, path + [candidate])
                if new_path:
                    return new_path

    for num in range(1, bound+1):
        if hampath_finder(graph, num):
            print(hampath_finder(graph, num))
            break
    else:
        print("No such order exists.")

Функция blueprint создает график, проверяя сумму всех возможных пар. Я уже объяснил hampath_finder. После этого я пробую каждое число как начало пути, используя цикл for.


person rmdnusr    schedule 03.07.2020    source источник
comment
Я с трудом следую. Что такое moves и start для ожидаемого вывода и сгенерированного вывода? Кроме того, path нет? или вы проезжаете в path?   -  person gnodab    schedule 03.07.2020
comment
Должны ли вы просто вернуть False вместо None в else?   -  person gnodab    schedule 03.07.2020
comment
Я пробовал оба, но результат не меняется. В чем разница?   -  person rmdnusr    schedule 03.07.2020
comment
if None должна быть ошибка. Похоже, в вашем коде есть неточность. С каким оператором if должен быть связан else: return None?   -  person gnodab    schedule 03.07.2020
comment
Я отредактировал сообщение сейчас, чтобы удалить эту часть. Это могло быть полезно для чего-то, пока я писал эту функцию, но прямо сейчас удаление всей части else: return None не влияет на вывод. Прошу прощения, если я утомительна. Рекурсивные функции, такие как эта, всегда смущают меня, и я всегда нахожу решение методом проб и ошибок, что означает, что я часто понятия не имею, что на самом деле делают некоторые части.   -  person rmdnusr    schedule 03.07.2020
comment
Без проблем. Я пока не вижу решения. Так что я просто пытаюсь понять полностью...   -  person gnodab    schedule 03.07.2020
comment
Что такое bound? Когда я пытаюсь запустить код, он не определен...   -  person gnodab    schedule 03.07.2020
comment
Я добавил всю функцию, чтобы сделать все более понятным.   -  person rmdnusr    schedule 03.07.2020


Ответы (1)


Я думаю, что причина, по которой вы получаете None, заключается в том, что в функции hampath_finder вы возвращаете только значение if new_path:. Если нового пути нет и функция возвращается, то Python вернет None. Вы можете увидеть это на этом примере:

def testfunct(test):
  if test:
    return True

print(testfunct(False))
>>> None

Кроме того, вы дважды вычисляете hampath_finder. Один раз проверить, существует ли он, а затем снова распечатать. Я бы изменил этот раздел вашего кода:

for num in range(1, bound+1):
    if hampath_finder(graph, num):
       print(hampath_finder(graph, num))
       break

Чтобы было что-то вроде этого:

for num in range(1, bound+1):
    this_path = hampath_finder(graph, num)
    if len(this_path) > 0:
       print(this_path)
       break

Это поможет со скоростью небольшое количество.

Однако беглый взгляд на задачу о гамильтоновом пути покажет, что это NP-полная задача. Так что это будет очень медленно. Есть некоторые исследовательские работы, которые имеют более быстрые реализации, выходящие за рамки StackOverflow. Кроме того, если вам нужна скорость, вы, вероятно, захотите переключить реализацию на что-то вроде C или C++.

person gnodab    schedule 03.07.2020