Знайдіть найкоротший шлях у графіку, який відвідує певні вузли


84

У мене є неорієнтований графік із приблизно 100 вузлами та близько 200 ребрами. Один вузол позначений як "початок", один - "кінець", і є близько десятка позначених як "mustpass".

Мені потрібно знайти найкоротший шлях через цей графік, який починається з 'start', закінчується в 'end' і проходить через усі вузли 'mustpass' (у будь-якому порядку).

( http://3e.org/local/maize-graph.png / http://3e.org/local/maize-graph.dot.txt - це відповідний графік - він представляє кукурудзяний лабіринт у Ланкастері, Пенсільванія)

Відповіді:


78

Усі інші, які порівнюють це із проблемою «Подорожній продавець», ймовірно, не уважно прочитали ваше запитання. У TSP метою є знайти найкоротший цикл, який відвідує всі вершини (гамільтонівський цикл) - це відповідає наявності кожного вузла з позначкою "mustpass".

У вашому випадку, враховуючи, що у вас є лише близько десятка позначених як "mustpass", а зважаючи на те, що 12! досить невеликий (479001600), ви можете просто спробувати всі перестановки лише вузлів 'mustpass' і подивитися на найкоротший шлях від 'початку' до 'кінця', який відвідує вузли 'mustpass' у такому порядку - це просто бути об’єднанням найкоротших шляхів між кожними двома послідовними вузлами в цьому списку.

Іншими словами, спочатку знайдіть найкоротшу відстань між кожною парою вершин (ви можете використовувати алгоритм Дейкстри або інші, але з цими малими числами (100 вузлів) навіть найпростіший в коді алгоритм Флойда-Варшалла буде працювати вчасно). Потім, як тільки ви отримаєте це в таблиці, спробуйте всі перестановки ваших вузлів 'mustpass', а решта.

Щось на зразок цього:

//Precomputation: Find all pairs shortest paths, e.g. using Floyd-Warshall
n = number of nodes
for i=1 to n: for j=1 to n: d[i][j]=INF
for k=1 to n:
    for i=1 to n:
        for j=1 to n:
            d[i][j] = min(d[i][j], d[i][k] + d[k][j])
//That *really* gives the shortest distance between every pair of nodes! :-)

//Now try all permutations
shortest = INF
for each permutation a[1],a[2],...a[k] of the 'mustpass' nodes:
    shortest = min(shortest, d['start'][a[1]]+d[a[1]][a[2]]+...+d[a[k]]['end'])
print shortest

(Звичайно, це не справжній код, і якщо ви хочете дійсний шлях, вам доведеться відстежувати, яка перестановка дає найменшу відстань, а також те, які є всі пари найкоротшими шляхами, але ви розумієте.)

Він працюватиме щонайбільше за кілька секунд на будь-якій розумній мові :)
[Якщо у вас n вузлів і k 'mustpass' вузлів, час його роботи становить O (n 3 ) для частини Флойда-Варшалла та O (k! N ) для всієї частини перестановок, і 100 ^ 3 + (12!) (100) - це практично арахіс, якщо у вас немає дійсно обмежувальних обмежень.]


7
Враховуючи невеликий розмір вводу, я погоджуюся з відповіддю. Але мене цікавить, чому ви відкидаєте твердження інших про те, що це випадок TSP. Наскільки я це розумію, ви можете витягти всі вузли, які потрібно пройти, і використовувати його для створення підграфа. Ребра між вузлами підграфа мають ваги, що відповідають рішенню APSP на вихідному графіку. Тоді питання не стає екземпляром TSP на підграфі? Ваше рішення, здається, є грубим рішенням цієї проблеми (що добре).
maditya

7
@maditya: По-перше, я сподіваюся, що ви погоджуєтесь з тим, що (цитуючи коментар Стівена Лоу щодо іншої відповіді) відповідь на зразок "TSP - це важко, bwahahaha" - це не відповідна відповідь тому, хто має реальну проблему, особливо дуже легко вирішується на будь-якому комп’ютері за останні кілька десятиліть. По-друге, це не ідентично TSP з тривіальних причин (різний формат введення): крихітний екземпляр TSP, який він містить, призначений для меншого графіку, а не для одного вхідного розміру N. Отже, повнота NP залежить від того, скільки вузлів “mustpass” існують асимптотично: якщо це завжди 12, або O (журнал N), це не NP-повно, і т. д.
ShreevatsaR

1
Я не впевнений, що результатом буде шлях. Уявіть, що вам доведеться пройти від aдо cкінця b. Можливо, найкоротші шляхи від bдо aі cподіляють край. У цьому випадку край повторювався б двічі. Один з двох шляхів повинен бути гіршим за оптимальний, щоб не генерувати цикли.
Спак,

1
@PietroSaccardi З опису у питанні здається, що метою є просто знайти найкоротший "шлях" проходження всіх цих вузлів, і це може бути нормально, якщо якийсь край повториться. Тобто, “шлях” використовується у вільному розумінні. Насправді, якщо повторювані ребра заборонені, можливо, відповіді навіть не буде (наприклад, розглянемо графік B — A — C, де вас просять перейти від A до C під час проходження через B: немає способу не повторити B — край).
ShreevatsaR

Алгоритм Флойда-Варшалла тут реалізований неправильно: оскільки всі комірки масиву ініціалізовані INF, рядок d[i][j] = min(d[i][j], d[i][k] + d[k][j])стає, d[i][j] = min(INF, INF + INF)і всі комірки завжди залишаються рівними INF. Вам потрібно додати крок, щоб заповнити цей масив довжинами ребер з графіку.
Стеф

25

запустіть Алгоритм Джикстри, щоб знайти найкоротший шлях між усіма критичними вузлами (початком, кінцем та обов'язковим проходженням), тоді обхід першої глибини повинен сказати вам найкоротший шлях через отриманий підграф, який торкається всіх початків вузлів. ... повинен пройти ... кінець


Здається, він може бути більш ефективним, ніж обране рішення. Б'юся об заклад, якби ви розробили це трохи більше, він би набрав більше балів. Спочатку мені довелося задуматися над тим, чи гарантує найкоротший шлях між усіма парами шлях між усіма необхідними вузлами .. але я вважаю, що він би (припускаючи, що не спрямований).
galactikuh

Я думаю, це не працює, якщо шлях не повинен повторювати краї.
tuket

1
Як би обхід першої глибини знайшов найкоротший шлях на прикладі Адвенту Коду 24-го дня? adventofcode.com/2016/day/24
Erwin Rooijakkers

17

Це дві проблеми ... Стівен Лоу вказав на це, але недостатньо поважав другу половину проблеми.

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

Однак, коли у вас є цей новий графік, у вас виникає саме проблема подорожуючого продавця (ну, майже ... Не потрібно повертатися до вихідної точки). Буде застосовано будь-яку з публікацій, що стосується цього, згадану вище.


14

Насправді проблема, яку ви опублікували, подібна до продавця, але я думаю, що вона ближча до простої проблеми пошуку шляхів. Замість того, щоб відвідувати кожен вузол, вам просто потрібно відвідати певний набір вузлів за найкоротший час (відстань).

Причиною цього є те, що, на відміну від проблеми продавця, кукурудзяний лабіринт не дозволить вам подорожувати безпосередньо з якоїсь однієї точки до будь-якої іншої точки на карті, не маючи необхідності проходити через інші вузли, щоб туди дістатися.

Насправді я б порадив пошук * шляху як спосіб розгляду. Ви встановлюєте це, вирішуючи, які вузли мають доступ до яких інших вузлів безпосередньо, і яка "вартість" кожного стрибка з конкретного вузла. У цьому випадку, схоже, кожен "стрибок" може мати однакову вартість, оскільки ваші вузли здаються відносно близькими. A * може використовувати цю інформацію, щоб знайти найнижчий шлях між будь-якими двома точками. Оскільки вам потрібно дістатися з точки А до точки Б і відвідати близько 12 між ними, навіть підхід грубої сили за допомогою пошуку шляхів взагалі не зашкодить.

Просто альтернатива для розгляду. Це справді виглядає надзвичайно як проблема мандрівного продавця, і це хороші статті, щоб прочитати їх, але придивіться уважніше, і ви побачите, що це лише надто ускладнює речі. ^ _ ^ Це випливає з думки програміста відеоігор, який раніше займався подібними речами.


2
+1 - це набагато краща відповідь, ніж "проблема подорожуючого продавця важка, бвахахаха"
Стівен А. Лоу

6

Ендрю Топ має правильну ідею:

1) Алгоритм Джикстри 2) Деякі евристики TSP.

Я рекомендую евристику Ліна-Кернігана: це одна з найвідоміших для будь-якої проблеми NP Complete. Єдине, що слід пам’ятати, це те, що після того, як ви знову розширили графік після кроку 2, у вашому розширеному шляху можуть з’явитися цикли, тому вам слід обійти їх, коротко замикаючи (подивіться на ступінь вершин на вашому шляху).

Я насправді не впевнений, наскільки це рішення буде відносно оптимального. Ймовірно, є деякі патологічні випадки, пов’язані з коротким замиканням. Зрештою, ця проблема виглядає БАГАТО, як Дерево Штайнера: http://en.wikipedia.org/wiki/Steiner_tree, і Ви точно не можете наблизити Дерево Штайнера, просто уклавши графік та запустивши, наприклад, Крускала.


6

Це не проблема TSP і не важко для NP, оскільки вихідне запитання не вимагає, щоб вузли, які повинні проходити, відвідувалися лише один раз. Це робить відповідь значно, набагато простішою - просто груба сила після складання списку найкоротших шляхів між усіма вузлами, що проходять через алгоритм Дейкстри. Можливо, є кращий шлях, але простим було б просто обробити бінарне дерево назад. Уявіть собі список вузлів [початок, a, b, c, кінець]. Підсумуйте прості відстані [початок-> a-> b-> c-> кінець] це ваша нова цільова відстань, яку потрібно бити. Тепер спробуйте [start-> a-> c-> b-> end], і якщо це краще, встановіть це як ціль (і пам’ятайте, що це походить від цього шаблону вузлів). Працюйте назад над перестановками:

  • [початок-> a-> b-> c-> кінець]
  • [початок-> a-> c-> b-> кінець]
  • [початок-> b-> a-> c-> кінець]
  • [початок-> b-> c-> a-> кінець]
  • [початок-> c-> a-> b-> кінець]
  • [початок-> c-> b-> a-> кінець]

Один із них буде найкоротшим.

(де знаходяться вузли "відвідані кілька разів", якщо такі є? Вони просто приховані на етапі ініціалізації найкоротшого шляху. Найкоротший шлях між a і b може містити c або навіть кінцеву точку. Вам не потрібно дбати )


Відвідати лише один раз потрібно за умови, що це найкоротший шлях.
Азіут

Га Я був упевнений хвилину тому, але так, ти маєш рацію. Очевидно, не на дереві з декількома гілками. Але справа в тому, що якщо ми абстрагуємо проблему в новий графік, який містить лише вузли mustpass, які повністю зв’язані, причому вузли мають відстань найкоротшого шляху у вихідному графіку, ми приходимо до TSP. Отже, ви впевнені, що це не NP-важко? Я б припустив, що в загальній задачі кількість вузлів mustpass залежить від кількості загальних вузлів, і якщо, наприклад, total є поліномом mustpass, ми отримуємо NP-твердість, так?
Aziuth

Шлях, скажімо, від a-> b може проходити через c. Тож жоден брнач не заважає будь-якому іншому. Це просто перестановка.
bjorke

Так? Але перестановка дорівнює O (n!), Якщо ми припустимо, що кількість вузлів mustpass має певний зв’язок із сумою загальних вузлів, наприклад «загальні вузли - це поліном вузлів mustpass». Ви щойно вирішили TSP грубою силою.
Азіут

2

Враховуючи, що кількість вузлів і ребер відносно скінченна, ви, ймовірно, можете розрахувати кожен можливий шлях і взяти найкоротший.

Як правило, це відома як проблема продавця, і вона має недетермінований поліном, незалежно від того, який алгоритм ви використовуєте.

http://en.wikipedia.org/wiki/Traveling_salesman_problem


1

Питання говорить про обов’язкове проходження в ЛЮБОМУ порядку . Я намагався знайти рішення щодо визначеного порядку вузлів, що повинні проходити. Я знайшов свою відповідь, але оскільки жодне запитання щодо StackOverflow не мало подібного питання, я розміщую його тут, щоб максимальна кількість людей отримала від цього користь.

Якщо порядок або обов’язковий прохід визначено, ви можете запустити алгоритм Дейкстри кілька разів. Наприклад, припустимо, що ви повинні починати з sпроходу k1, k2і k3(у відповідному порядку) і зупинятися на e. Тоді ви могли б запустити алгоритм Дейкстри між кожною послідовною парою вузлів. Вартість і шлях буде за формулою:

dijkstras(s, k1) + dijkstras(k1, k2) + dijkstras(k2, k3) + dijkstras(k3, 3)


0

Як щодо використання грубої сили на десятках вузлів, що мають відвідувати. Ви можете досить просто охопити всі можливі комбінації з 12 вузлів, і це залишає вам оптимальну схему, за якою ви можете їх покрити.

Тепер ваша проблема спрощена - знайти оптимальні маршрути від стартового вузла до ланцюга, яким ви потім рухаєтесь до тих пір, поки не покриєте їх, а потім знайдете маршрут від цього до кінця.

Остаточний шлях складається з:

старт -> шлях до ланцюга * -> ланцюг відвідуваних вузлів -> шлях до кінця * -> кінець

Ви знайдете такі шляхи, які я позначив *

Виконайте пошук A * від початкового вузла до кожної точки ланцюга для кожного з них, виконайте пошук A * від наступного та попереднього вузла ланцюга до кінця (оскільки ви можете слідувати по колу в будь-якому напрямку). в кінцевому підсумку це багато шляхів пошуку, і ви можете вибрати той, що має найнижчу вартість.

Є багато місця для оптимізації шляхом кешування пошукових запитів, але я думаю, що це породить хороші рішення.

Хоча це і не наближається до пошуку оптимального рішення, оскільки це може передбачати залишення схеми обов’язкового відвідування в межах пошуку.


0

Одне, про що ніде не згадується, - це нормально, щоб одну і ту ж вершину відвідували більше одного разу на шляху. Більшість відповідей тут припускають, що нормально відвідувати один і той самий край кілька разів, але мій приклад, заданий питанням (шлях не повинен відвідувати одну і ту ж вершину більше одного разу!), Полягає в тому, що це не нормально відвідувати одну і ту ж вершину двічі.

Отже, як і раніше застосовуватиметься метод грубої сили, але вам доведеться видалити вже використані вершини, коли ви намагаєтесь обчислити кожну підмножину шляху.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.