Як я можу знайти найкоротший шлях між 100 рухомими цілями? (Демонстрація в прямому ефірі включена.)


89

Передумови

Цей малюнок ілюструє проблему: square_grid_with_arrows_giving_directions

Я можу керувати червоним колом. Мішенями є сині трикутники. Чорні стрілки вказують напрямок руху цілей.

Я хочу зібрати всі цілі за мінімальну кількість кроків.

На кожному повороті я повинен рухатись на 1 крок вліво / вправо / вгору або вниз.

Кожен поворот цілі також рухатиметься на 1 крок відповідно до вказівок, показаних на дошці.

Демо

Я опублікував демонстраційну демонстраційну проблему тут на Google Appendine .

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

Проблема

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

Я хотів би обчислити відповідь для розмірів дошки 32 * 32 і зі 100 рухомими цілями.

Питання

Що таке ефективний алгоритм (в ідеалі в Javascript) для обчислення мінімальної кількості кроків для збору всіх цілей?

Те, що я пробував

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

Я розв'язую підзадачу "яка мінімальна кількість кроків для збору заданого набору цілей і закінчення на певній цілі?"

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

Це призводить до обчислення n * 2 ^ n станів, які дуже швидко зростають.

Поточний код показаний нижче:

var DX=[1,0,-1,0];
var DY=[0,1,0,-1]; 

// Return the location of the given fish at time t
function getPt(fish,t) {
  var i;
  var x=pts[fish][0];
  var y=pts[fish][1];
  for(i=0;i<t;i++) {
    var b=board[x][y];
    x+=DX[b];
    y+=DY[b];
  }
  return [x,y];
}

// Return the number of steps to track down the given fish
// Work by iterating and selecting first time when Manhattan distance matches time
function fastest_route(peng,dest) {
  var myx=peng[0];
  var myy=peng[1];
  var x=dest[0];
  var y=dest[1];
  var t=0;
  while ((Math.abs(x-myx)+Math.abs(y-myy))!=t) {
    var b=board[x][y];
    x+=DX[b];
    y+=DY[b];
    t+=1;
  }
  return t;
}

// Try to compute the shortest path to reach each fish and a certain subset of the others
// key is current fish followed by N bits of bitmask
// value is shortest time
function computeTarget(start_x,start_y) {
  cache={};
  // Compute the shortest steps to have visited all fish in bitmask
  // and with the last visit being to the fish with index equal to last
  function go(bitmask,last) {
    var i;
    var best=100000000;
    var key=(last<<num_fish)+bitmask;
    if (key in cache) {
      return cache[key];
    }
    // Consider all previous positions
    bitmask -= 1<<last;
    if (bitmask==0) {
      best = fastest_route([start_x,start_y],pts[last]);
    } else {
      for(i=0;i<pts.length;i++) {
        var bit = 1<<i;
        if (bitmask&bit) {
          var s = go(bitmask,i);   // least cost if our previous fish was i
          s+=fastest_route(getPt(i,s),getPt(last,s));
          if (s<best) best=s;
        }
      }
    }
    cache[key]=best;
    return best;
  }
  var t = 100000000;
  for(var i=0;i<pts.length;i++) {
    t = Math.min(t,go((1<<pts.length)-1,i));
  }
  return t;
}

Те, що я розглядав

Деякі варіанти, про які я задавався питанням:

  1. Кешування проміжних результатів. Розрахунок відстані повторює багато моделювання, і проміжні результати можуть бути кешовані.
    Однак я не думаю, що це зупинить його експоненціальну складність.

  2. Алгоритм пошуку A *, хоча мені незрозуміло, якою буде прийнятною допустимою евристикою та наскільки це ефективно на практиці.

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

  4. Намагаючись довести, що проблема NP-важка і, отже, нерозумно шукати оптимальну відповідь на неї.


1
Я б пішов на # 4, а згодом - на 3: маючи досить великі плати, він досить добре імітує TSP.
Джон Дворжак,

2
Наскільки мені відомо, TSP є NP-жорстким з евклідовою метрикою, а також з метрикою Манхеттена (квадратна сітка).
Джон Дворжак,

1
Якщо ви зробите це за допомогою простого пошуку по дереву, так, це буде експоненціально. Однак, якщо ви можете знайти гідну евристику на кожному кроці, це може бути не справді оптимальним, але це може бути дуже добре. Однією з можливих евристик було б, дивлячись на поточний набір риб, яку з них можна досягти найшвидше? Може бути вторинною евристикою, до яких 2 риб я міг би дістатись найшвидше?
Mike Dunlavey 18.03.13

2
@MikeDunlavey, що відповідало б жадібному алгоритму TSP, і це дуже добре працює на практиці. Піти за найближчою рибою здається гарною ідеєю
Джон Дворжак,

1
+1 за одне з найкращих запитань, які я бачив останнім часом, як щодо змісту, так і за структурою.
surfitscrollit

Відповіді:


24

Ви шукали літературу? Я знайшов ці статті, які, схоже, аналізують вашу проблему:

ОНОВЛЕННЯ 1:

Ці два статті, здається, зосереджені на лінійному русі для евклідової метрики.


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

13

Жадібний метод

Один із підходів, запропонованих у коментарях, - це перейти до найближчої цілі спочатку.

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

Код:

function greedyMethod(start_x,start_y) {
  var still_to_visit = (1<<pts.length)-1;
  var pt=[start_x,start_y];
  var s=0;
  while (still_to_visit) {
    var besti=-1;
    var bestc=0;
    for(i=0;i<pts.length;i++) {
      var bit = 1<<i;
      if (still_to_visit&bit) {
        c = fastest_route(pt,getPt(i,s));
        if (besti<0 || c<bestc) {
          besti = i;
          bestc = c;
        }
      }
    }
    s+=c;
    still_to_visit -= 1<<besti;
    pt=getPt(besti,s);
  }
  return s;
}

Для 10 цілей це приблизно вдвічі більше оптимальної відстані, але іноді набагато більше (наприклад, * 4), а іноді навіть досягає оптимальної.

Цей підхід є дуже ефективним, тому я можу дозволити собі кілька циклів для покращення відповіді.

Далі я розглядаю можливість використання методів колонії мурашок, щоб перевірити, чи зможуть вони ефективно дослідити простір рішень.

Метод колонії мурашок

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

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

function antMethod(start_x,start_y) {
  // First establish a baseline based on greedy
  var L = greedyMethod(start_x,start_y);
  var n = pts.length;
  var m = 10; // number of ants
  var numrepeats = 100;
  var alpha = 0.1;
  var q = 0.9;
  var t0 = 1/(n*L);

  pheromone=new Array(n+1); // entry n used for starting position
  for(i=0;i<=n;i++) {
    pheromone[i] = new Array(n);
    for(j=0;j<n;j++)
      pheromone[i][j] = t0; 
  }

  h = new Array(n);
  overallBest=10000000;
  for(repeat=0;repeat<numrepeats;repeat++) {
    for(ant=0;ant<m;ant++) {
      route = new Array(n);
      var still_to_visit = (1<<n)-1;
      var pt=[start_x,start_y];
      var s=0;
      var last=n;
      var step=0;
      while (still_to_visit) {
        var besti=-1;
        var bestc=0;
        var totalh=0;
        for(i=0;i<pts.length;i++) {
          var bit = 1<<i;
          if (still_to_visit&bit) {
            c = pheromone[last][i]/(1+fastest_route(pt,getPt(i,s)));
            h[i] = c;
            totalh += h[i];
            if (besti<0 || c>bestc) {
              besti = i;
              bestc = c;
            }
          }
        }
        if (Math.random()>0.9) {
          thresh = totalh*Math.random();
          for(i=0;i<pts.length;i++) {
            var bit = 1<<i;
            if (still_to_visit&bit) {
              thresh -= h[i];
              if (thresh<0) {
                besti=i;
                break;
              }
            }
          }
        }
        s += fastest_route(pt,getPt(besti,s));
        still_to_visit -= 1<<besti;
        pt=getPt(besti,s);
        route[step]=besti;
        step++;
        pheromone[last][besti] = (1-alpha) * pheromone[last][besti] + alpha*t0;
        last = besti;
      }
      if (ant==0 || s<bestantscore) {
        bestroute=route;
        bestantscore = s;
      }
    }
    last = n;
    var d = 1/(1+bestantscore);
    for(i=0;i<n;i++) {
      var besti = bestroute[i];
      pheromone[last][besti] = (1-alpha) * pheromone[last][besti] + alpha*d;
      last = besti;
    }
    overallBest = Math.min(overallBest,bestantscore);
  }
  return overallBest;
}

Результати

Цей метод колонії мурашок із використанням 100 повторів 10 мурах все ще дуже швидкий (37 мс для 16 цілей у порівнянні з 3700 мс для вичерпного пошуку) і здається дуже точним.

У таблиці нижче наведені результати 10 випробувань з використанням 16 цілей:

   Greedy   Ant     Optimal
   46       29      29
   91       38      37
  103       30      30
   86       29      29
   75       26      22
  182       38      36
  120       31      28
  106       38      30
   93       30      30
  129       39      38

Мурашиний метод здається значно кращим за жадібний і часто дуже близький до оптимального.


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

8

Проблема може бути представлена ​​з точки зору Загальної проблеми мандрівного продавця, а потім перетворена на звичайну проблему мандрівного продавця. Це добре вивчена проблема. Можливо, що найефективніші рішення проблеми ОП є не ефективнішими, ніж рішення ТСП, але аж ніяк не певні (я, мабуть, не можу скористатися деякими аспектами структури проблем ОП, що дозволило б швидше вирішити , наприклад, його циклічність). У будь-якому випадку, це хороша відправна точка.

Від C. Noon & J.Bean, Ефективна трансформація узагальненої проблеми мандрівного продавця :

Узагальнена задача комівояжера (GTSP) є корисною моделлю для вирішення проблем , пов'язаних з відбору та послідовності. Асиметричний варіант задачі визначається на спрямованому графіку з вузлами N, що з'єднують дуги A і вектором відповідних витрат дуги c. Вузли попередньо групуються у m взаємовиключних та вичерпних вузлів. З'єднуючі дуги визначаються лише між вузлами, що належать до різних наборів, тобто не існує внутрішніх дуг. Кожна визначена дуга має відповідну невід’ємну вартість. GTSP можна сформулювати як проблему пошуку мінімального циклу m-дугового циклу, який включає рівно по одному вузлу з кожного вузла .

Щодо проблеми OP:

  • Кожен учасник N- це конкретне місце розташування риби в певний час. Уявіть це як (x, y, t), де (x, y)- координатна сітка, а tтакож час, коли риба буде знаходитися в цій координаті. Для крайньої лівої риби в прикладі OP перші кілька з них (на основі 1): (3, 9, 1), (4, 9, 2), (5, 9, 3)коли риба рухається праворуч.
  • Для будь-якого члена N нехай fish(n_i)повертається ідентифікатор риби, представлений вузлом. Для будь-яких двох членів N ми можемо розрахувати manhattan(n_i, n_j)відстань між Манхеттеном між двома вузлами, та time(n_i, n_j) для часового зсуву між вузлами.
  • Кількість несуміжних підмножин m дорівнює кількості риб. Неперервна підмножина S_iскладатиметься лише з вузлів, для яких fish(n) == i.
  • Якщо для двох вузлів iі j fish(n_i) != fish(n_j)тоді є дуга між iі j.
  • Вартість між вузлом i та вузлом j є time(n_i, n_j)або невизначена, якщо time(n_i, n_j) < distance(n_i, n_j)(тобто місцезнаходження неможливо досягти до того, як риба потрапить туди, можливо, тому, що воно відсталене в часі). Дуги цього останнього типу можна видалити.
  • Потрібно буде додати додатковий вузол, що відображає розташування гравця з дугами та витрати на всі інші вузли.

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

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

Є очевидні проблеми зі складністю. Зокрема, простір вузлів нескінченний! Це можна полегшити, лише генеруючи вузли до певного часового горизонту. Якщо t- це кількість кроків для створення вузлів для і f- це кількість риби, тоді розмір простору вузлів буде t * f. Вузол за часом jматиме максимум(f - 1) * (t - j) вихідних дуг (оскільки він не може повернутися назад у часі або до власної підмножини). Загальна кількість дуг буде в порядку t^2 * f^2дуг. Структуру дуги, напевно, можна привести в порядок, щоб скористатися тим, що рибні шляхи з часом циклічні. Риба повторюватиме свою конфігурацію раз на кожний найнижчий загальний знаменник своєї тривалості циклу, тому, можливо, цей факт можна використовувати.

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


Дякую, це для мене нове і дуже цікаве. Я думаю, я мав би змогу використовувати це перетворення в поєднанні з алгоритмом Христофідеса, щоб ефективно знайти рішення в межах коефіцієнта наближення 3/2 від оптимального. Якщо я змушу його працювати, я додаю створені маршрути на демонстраційну сторінку.
Пітер де Ріваз,

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

Так, я забув згадати, що нерівність трикутника більше не виконується. Однак це хороша точка відліку для евристичних рішень та більш загальних наближень.
timxyz

1

Я думаю, ще одним підходом буде:

Цитата wikipedia:

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

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

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