Решатель для TSP-подобной головоломки, возможно, на Javascript

Я создал головоломку, производную от задачи коммивояжера, которую я назвал «Идеальный след».

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

И наконец, ребру присваиваются два веса, по одному для каждого направления обхода.

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

Теперь я знаю, что TSP является NP-трудным. Но мои головоломки обычно имеют лишь небольшое количество ребер и вершин. Ведь они должны быть решаемы по-человечески. Так что грубой силы с базовой оптимизацией может быть достаточно.

Я хотел бы разработать некоторый (Javascript?) код, который извлекает головоломку с сервера и решает алгоритм за разумное время. Кроме того, он может даже опубликовать решение на сервере для регистрации в таблице лидеров.

Я написал базовый решатель грубой силы для него на Java, используя мою внутреннюю Java-модель на сервере, но код слишком толстый и, как и ожидалось, быстро исчерпал место в куче.

Возможен ли и осуществим ли решатель Javascript?

JSON API прост. Вы можете найти его по адресу: http://service.traceperfect.com/api/stov?pdate=20110218, где pdate — это дата головоломки в формате ггггММдд.

В основном головоломка имеет много строк. Каждая линия имеет две вершины (A и B). Каждая строка имеет два веса (время A для прохождения A -> B и время B для прохождения B -> A). И это должно быть все, что вам нужно для построения структуры данных графа. Все остальные свойства в объектах JSON предназначены для визуальных целей.

Если вы хотите познакомиться с головоломкой, вы можете сыграть в нее через флеш-клиент по адресу http://www.TracePerfect.com/

Если кому интересно реализовать решатель для себя, то выложу подробно про API для отправки решения на сервер, что тоже очень просто.

Спасибо, что прочитали этот длинный пост. Я с нетерпением жду ваших мыслей об этом.


person Patrick    schedule 18.02.2011    source источник
comment
Это больше похоже на задачу китайского почтальона, чем на задачу коммивояжера.   -  person Peter Taylor    schedule 18.02.2011
comment
определенно больше похоже на менее популярную проблему китайского почтальона, да   -  person Patrick    schedule 18.02.2011


Ответы (3)


Если вам не хватает места в куче в Java, значит, вы решаете это неправильно.

Стандартный способ решить что-то подобное — выполнить поиск в ширину и отфильтровать дубликаты. Для этого вам нужны три структуры данных. Первый — это ваш график. Следующей является очередь с именем todo из «состояний» для работы, которую вам осталось сделать. И последнее — это хэш, который сопоставляет возможное «состояние», в котором вы находитесь, с парой (стоимость, последнее состояние).

В этом случае «состояние» — это пара (текущий узел, множество уже пройденных ребер).

Предполагая, что у вас есть эти структуры данных, вот псевдокод для полного алгоритма, который должен решить эту проблему достаточно эффективно.

foreach possible starting_point:
  new_state = state(starting_point, {no edges visited})
  todo.add(new_state)
  seen[new_state] = (0, null)

while todo.workleft():
  this_state = todo.get()
  (cost, edges) = seen[this_state]
  foreach directed_edge in graph.directededges(this_state.current_node()):
    new_cost = cost + directed_edge.cost()
    new_visited = directed_edge.to()
    new_edges = edges + directed_edge.edge()
    new_state = state(new_visited, new_edges)
    if not exists seen[new_state] or new_cost < seen[new_state][0]:
      seen[new_state] = (new_cost, this_state)
      queue.add(new_state)

best_cost = infinity
full_edges = {all possible edges}
best_state
foreach possible location:
  end_state = (location, full_edges)
  (cost, last_move) = seen[end_state]
  if cost < best_cost:
    best_state = end_state
    best_cost = cost

# Now trace back the final answer.
path_in_reverse = []
current_state = best_state
while current_state[1] is not empty:
    previous_state = seen[current_state][1]
    path_in_reverse.push(edge from previous_state[0] to current_state[0])
    current_state = previous_state

И теперь reverse(path_in_reverse) дает вам оптимальный путь.

Обратите внимание, что хэш seen является критическим. Это то, что мешает вам попасть в бесконечные циклы.

Глядя на сегодняшнюю головоломку, этот алгоритм будет иметь максимум около миллиона состояний, которые вам нужно выяснить. (Есть 2 ** 16 возможных наборов ребер и 14 возможных узлов, в которых вы можете находиться.) Это, вероятно, поместится в ОЗУ. Но большинство ваших узлов имеют только 2 соединенных ребра. Я настоятельно рекомендую свернуть их. Это уменьшит вас до 4 узлов и 6 ребер, для верхнего предела 256 состояний. (Не все возможно, и обратите внимание, что несколько ребер теперь соединяют два узла.) Это должно работать очень быстро с небольшим использованием памяти.

person btilly    schedule 18.02.2011
comment
@Patrick: Состояние - это местоположение плюс набор ребер. Существует конечное число возможных состояний, и требование, согласно которому вы должны улучшить видимость, говорит о том, что вы посещаете каждое состояние только конечное число раз. Таким образом, вы выходите из среднего цикла за конечное время. В последнем цикле возможных местоположений вы перебираете все состояния, представляющие окончательные решения, и именно так вы находите лучшее из них для завершения. - person btilly; 18.02.2011
comment
спасибо бтилли. действительно удивительно. мне потребовалось некоторое время, чтобы обдумать стратегию. но это имеет смысл. Не могли бы вы немного подробнее рассказать о том, как, наконец, вернуться, чтобы определить путь к лучшему решению? - person Patrick; 19.02.2011
comment
пс. то, что я делал в своем коде, было похоже, за исключением того, что я использовал рекурсию для сохранения всех возможных состояний. Но мое состояние включало весь путь (а не только текущее местоположение плюс набор ребер) - person Patrick; 19.02.2011
comment
@Patrick: я добавил псевдокод для поиска оптимального пути из этой структуры данных. И да, очень важно, чтобы штатам было все равно, как вы туда попали, чтобы вы могли как можно быстрее убить лишнюю работу. - person btilly; 19.02.2011

Для большинства частей графика можно применить http://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg.

Таким образом, вы можете получить количество строк, которые вы должны повторить, чтобы решить.

В начале вы не должны начинать с узлов, которые имеют короткие вершины, по которым вы должны пройти два раза. Если резюмировать:

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

Простой рекурсивный решатель грубой силы с этой эвристикой может быть хорошим началом.

Или другим способом.
Попробуйте найти кратчайшие вершины, которые, если вы удалите их из графа, напоминающего граф, будут иметь только две вершины с нечетными номерами и будут считаться разрешимыми как Конингсбергский мост. Решение состоит в том, чтобы решить граф, не поднимая карандаш на этом уменьшенном графе, и как только вы нажмете узел с «удаленным» ребром, вы просто вернетесь назад и вперед.

person Luka Rahne    schedule 18.02.2011
comment
спасибо, ralu... это похоже на то, что я делаю на Java на моем бэкенде. Да, это хорошее начало, но я надеюсь на вклад псевдокода или базового алгоритма Javascript. Но, наверное, я слишком многого прошу :) - person Patrick; 18.02.2011
comment
Я не написал даже строки JavaScript. Поддерживает ли он рекурсию? - person Luka Rahne; 18.02.2011
comment
Да, JavaScript — это базовый язык сценариев. Он поддерживает функции, рекурсию, массивы, объекты (объекты также можно использовать как словари Python) и анонимные функции (которые являются полными замыканиями). Этому стоит научиться. - person btilly; 18.02.2011

На вашем сервере Java вы можете использовать этот код TSP (в разработке), который использует Drools Planner (открытый исходный код, java).

person Geoffrey De Smet    schedule 18.02.2011