Проблема комівояжера (TSP):
За наявності набору міст і відстані між кожною парою міст проблема полягає в тому, щоб знайти найкоротший можливий маршрут, який відвідує кожне місто рівно один раз і повертається до початкової точки. Зверніть увагу на різницю між Гамільтоновим циклом і TSP. Проблема Гамільтонового циклу полягає в тому, щоб знайти, чи існує тур, який відвідує кожне місто рівно один раз. Тут ми знаємо, що гамільтонів тур існує (оскільки граф повний), і насправді таких турів існує багато, проблема полягає в тому, щоб знайти гамільтонів цикл мінімальної ваги.
Для прикладу розглянемо графік, зображений на малюнку праворуч. Тур TSP на графіку має вигляд 1-2-4-3-1. Вартість туру 10+25+30+15, тобто 80. Задача є відомою NP-складною задачею. Для цієї проблеми не існує поліноміального розв’язку. Нижче наведено різні рішення проблеми комівояжера.
Наївне рішення:
1) Розглянемо місто 1 як початкову та кінцеву точки.
2) Згенерувати всі (n-1)! Перестановки міст.
3) Розрахуйте вартість кожної перестановки та відстежуйте мінімальну вартість перестановки.
4) Повернути перестановку з мінімальною вартістю.
Часова складність: ?(n!)
Динамічне програмування:
Нехай заданий набір вершин буде {1, 2, 3, 4,….n}. Розглянемо 1 як початкову та кінцеву точки виведення. Для кожної іншої вершини I (крім 1) ми знаходимо шлях мінімальної вартості з 1 як початковою точкою, I як кінцевою точкою, і всі вершини з’являються рівно один раз. Нехай вартість цього шляху коштує (i), а вартість відповідного циклу буде коштувати (i) + dist(i, 1), де dist(i, 1) — відстань від I до 1. Нарешті, ми повертаємо мінімум усіх значень [cost(i) + dist(i, 1)]. Поки що це виглядає просто.
Тепер питання полягає в тому, як отримати вартість (i)? Щоб обчислити вартість (i) за допомогою динамічного програмування, нам потрібно мати деяке рекурсивне відношення в термінах підпроблем.
Давайте визначимо термін C(S, i) — вартість шляху з мінімальною вартістю, що відвідує кожну вершину в наборі S рівно один раз, починаючи з 1 і закінчуючи i . Ми починаємо з усіх підмножин розміру 2 і обчислюємо C(S, i) для всіх підмножин, де S є підмножиною, потім обчислюємо C(S, i) для всіх підмножин S розміром 3 і так далі. Зауважте, що 1 має бути присутнім у кожній підмножині.
If size of S is 2, then S must be {1, i}, C(S, i) = dist(1, i) Else if size of S is greater than 2. C(S, i) = min { C(S-{i}, j) + dis(j, i)} where j belongs to S, j != i and j != 1.> Нижче наведено рішення динамічного програмування для проблеми з використанням рекурсивного підходу «згори донизу»+мемоізованого підходу:-
Prime немає коду в java
Для підтримки підмножин ми можемо використовувати бітові маски для представлення решти вузлів у нашій підмножині. Оскільки біти працюють швидше, а на графі лише кілька вузлів, краще використовувати бітові маски.
centos проти rhel
Наприклад: -
10100 означає вузол 2, а вузол 4 залишився в наборі для обробки
010010 представляє вузол 1, а 4 залишилося в підмножині.
ПРИМІТКА: - ігноруйте 0-й біт, оскільки наш графік базується на 1
C++
#include> using> namespace> std;> // there are four nodes in example graph (graph is 1-based)> const> int> n = 4;> // give appropriate maximum to avoid overflow> const> int> MAX = 1000000;> // dist[i][j] represents shortest distance to go from i to j> // this matrix can be calculated for any given graph using> // all-pair shortest path algorithms> int> dist[n + 1][n + 1] = {> >{ 0, 0, 0, 0, 0 }, { 0, 0, 10, 15, 20 },> >{ 0, 10, 0, 25, 25 }, { 0, 15, 25, 0, 30 },> >{ 0, 20, 25, 30, 0 },> };> // memoization for top down recursion> int> memo[n + 1][1 << (n + 1)];> int> fun(>int> i,>int> mask)> > >// base case> >// if only ith bit and 1st bit is set in our mask,> >// it implies we have visited all other nodes already> >if> (mask == ((1 << i)> // Driver program to test above logic> int> main()> {> >int> ans = MAX;> >for> (>int> i = 1; i <= n; i++)> >// try to go from node 1 visiting all nodes in> >// between to i then return from i taking the> >// shortest route to 1> >ans = std::min(ans, fun(i, (1 << (n + 1)) - 1)> >+ dist[i][1]);> >printf>(>'The cost of most efficient tour = %d'>, ans);> >return> 0;> }> // This code is contributed by Serjeel Ranjan> |
>
>
Java
import> java.io.*;> import> java.util.*;> public> class> TSE {> >// there are four nodes in example graph (graph is> >// 1-based)> >static> int> n =>4>;> >// give appropriate maximum to avoid overflow> >static> int> MAX =>1000000>;> >// dist[i][j] represents shortest distance to go from i> >// to j this matrix can be calculated for any given> >// graph using all-pair shortest path algorithms> >static> int>[][] dist = {> >{>0>,>0>,>0>,>0>,>0> }, {>0>,>0>,>10>,>15>,>20> },> >{>0>,>10>,>0>,>25>,>25> }, {>0>,>15>,>25>,>0>,>30> },> >{>0>,>20>,>25>,>30>,>0> },> >};> >// memoization for top down recursion> >static> int>[][] memo =>new> int>[n +>1>][>1> << (n +>1>)];> >static> int> fun(>int> i,>int> mask)> >> >// base case> >// if only ith bit and 1st bit is set in our mask,> >// it implies we have visited all other nodes> >// already> >if> (mask == ((>1> << i)> >// Driver program to test above logic> >public> static> void> main(String[] args)> >{> >int> ans = MAX;> >for> (>int> i =>1>; i <= n; i++)> >// try to go from node 1 visiting all nodes in> >// between to i then return from i taking the> >// shortest route to 1> >ans = Math.min(ans, fun(i, (>1> << (n +>1>)) ->1>)> >+ dist[i][>1>]);> >System.out.println(> >'The cost of most efficient tour = '> + ans);> >}> }> // This code is contributed by Serjeel Ranjan> |
підкреслити за допомогою css
>
>
Python3
n>=> 4> # there are four nodes in example graph (graph is 1-based)> # dist[i][j] represents shortest distance to go from i to j> # this matrix can be calculated for any given graph using> # all-pair shortest path algorithms> dist>=> [[>0>,>0>,>0>,>0>,>0>], [>0>,>0>,>10>,>15>,>20>], [> >0>,>10>,>0>,>25>,>25>], [>0>,>15>,>25>,>0>,>30>], [>0>,>20>,>25>,>30>,>0>]]> # memoization for top down recursion> memo>=> [[>->1>]>*>(>1> << (n>+>1>))>for> _>in> range>(n>+>1>)]> def> fun(i, mask):> ># base case> ># if only ith bit and 1st bit is set in our mask,> ># it implies we have visited all other nodes already> >if> mask>=>=> ((>1> << i) |>3>):> >return> dist[>1>][i]> ># memoization> >if> memo[i][mask] !>=> ->1>:> >return> memo[i][mask]> >res>=> 10>*>*>9> # result of this sub-problem> ># we have to travel all nodes j in mask and end the path at ith node> ># so for every node j in mask, recursively calculate cost of> ># travelling all nodes in mask> ># except i and then travel back from node j to node i taking> ># the shortest path take the minimum of all possible j nodes> >for> j>in> range>(>1>, n>+>1>):> >if> (mask & (>1> << j)) !>=> 0> and> j !>=> i>and> j !>=> 1>:> >res>=> min>(res, fun(j, mask & (~(>1> << i)))>+> dist[j][i])> >memo[i][mask]>=> res># storing the minimum value> >return> res> # Driver program to test above logic> ans>=> 10>*>*>9> for> i>in> range>(>1>, n>+>1>):> ># try to go from node 1 visiting all nodes in between to i> ># then return from i taking the shortest route to 1> >ans>=> min>(ans, fun(i, (>1> << (n>+>1>))>->1>)>+> dist[i][>1>])> print>(>'The cost of most efficient tour = '> +> str>(ans))> # This code is contributed by Serjeel Ranjan> |
сортувати список масивів у java
>
>
C#
using> System;> class> TSE> {> >// there are four nodes in example graph (graph is> >// 1-based)> >static> int> n = 4;> >// give appropriate maximum to avoid overflow> >static> int> MAX = 1000000;> >// dist[i][j] represents shortest distance to go from i> >// to j this matrix can be calculated for any given> >// graph using all-pair shortest path algorithms> >static> int>[, ] dist = { { 0, 0, 0, 0, 0 },> >{ 0, 0, 10, 15, 20 },> >{ 0, 10, 0, 25, 25 },> >{ 0, 15, 25, 0, 30 },> >{ 0, 20, 25, 30, 0 } };> >// memoization for top down recursion> >static> int>[, ] memo =>new> int>[(n + 1), (1 << (n + 1))];> >static> int> fun(>int> i,>int> mask)> > 3))> >return> dist[1, i];> > >// memoization> >if> (memo[i, mask] != 0)> >return> memo[i, mask];> >int> res = MAX;>// result of this sub-problem> >// we have to travel all nodes j in mask and end the> >// path at ith node so for every node j in mask,> >// recursively calculate cost of travelling all> >// nodes in mask> >// except i and then travel back from node j to node> >// i taking the shortest path take the minimum of> >// all possible j nodes> >for> (>int> j = 1; j <= n; j++)> >if> ((mask & (1 << j)) != 0 && j != i && j != 1)> >res = Math.Min(res,> >fun(j, mask & (~(1 << i)))> >+ dist[j, i]);> >return> memo[i, mask] = res;> >> >// Driver program to test above logic> >public> static> void> Main()> >{> >int> ans = MAX;> >for> (>int> i = 1; i <= n; i++)> >// try to go from node 1 visiting all nodes in> >// between to i then return from i taking the> >// shortest route to 1> >ans = Math.Min(ans, fun(i, (1 << (n + 1)) - 1)> >+ dist[i, 1]);> >Console.WriteLine(> >'The cost of most efficient tour = '> + ans);> >}> }> // This code is contributed by Tapesh(tapeshdua420)> |
>
>
Javascript
>// JavaScript code for the above approach> >// there are four nodes in example graph (graph is 1-based)> >let n = 4;> > >// give appropriate maximum to avoid overflow> >let MAX = 1000000;> >// dist[i][j] represents shortest distance to go from i to j> >// this matrix can be calculated for any given graph using> >// all-pair shortest path algorithms> >let dist = [> >[0, 0, 0, 0, 0], [0, 0, 10, 15, 20],> >[0, 10, 0, 25, 25], [0, 15, 25, 0, 30],> >[0, 20, 25, 30, 0],> >];> >// memoization for top down recursion> >let memo =>new> Array(n + 1);> >for> (let i = 0; i memo[i] = new Array(1 << (n + 1)).fill(0) } function fun(i, mask) // base case // if only ith bit and 1st bit is set in our mask, // it implies we have visited all other nodes already if (mask == ((1 << i) // Driver program to test above logic let ans = MAX; for (let i = 1; i <= n; i++) // try to go from node 1 visiting all nodes in // between to i then return from i taking the // shortest route to 1 ans = Math.min(ans, fun(i, (1 << (n + 1)) - 1) + dist[i][1]); console.log('The cost of most efficient tour ' + ans); // This code is contributed by Potta Lokesh> |
>
>Вихід
The cost of most efficient tour = 80>
Часова складність: O(n2*2п) де O(n* 2п)максимальна кількість унікальних підпроблем/станів і O(n) для переходу (через цикл for, як у коді) у кожному стані.
Допоміжний простір: O(n*2п), де n – кількість вузлів/міст тут.
Для набору розміром n ми розглядаємо n-2 підмножини розміром n-1 кожна, так що всі підмножини не мають у собі n-го. Використовуючи наведене вище рекурентне співвідношення, ми можемо написати рішення на основі динамічного програмування. Є не більше O(n*2п) підпроблем, і для вирішення кожної з них потрібен лінійний час. Отже, загальний час роботи дорівнює O(n2*2п). Часова складність набагато менша, ніж O(n!), але все ще експоненціальна. Необхідний простір також експоненціальний. Отже, цей підхід також неможливий навіть для трохи більшої кількості вершин. Незабаром ми обговоримо наближені алгоритми задачі комівояжера.
jpa навесні
Наступна стаття: Проблема комівояжера | Набір 2
Література:
http://www.lsi.upc.edu/~mjserna/docencia/algofib/P07/dynprog.pdf
http://www.cs.berkeley.edu/~vazirani/algorithms/chap6.pdf