Задний план
Это изображение иллюстрирует проблему:
Я могу контролировать красный круг. Цели - синие треугольники. Черные стрелки указывают направление, в котором будут двигаться цели.
Я хочу собрать все мишени за минимальное количество шагов.
Каждый ход я должен делать 1 шаг влево / вправо / вверх или вниз.
Каждый ход мишени также перемещаются на 1 шаг в соответствии с указаниями на доске.
Демо
Я разместил игровую демонстрацию проблемы здесь, в Google appengine .
Мне было бы очень интересно, сможет ли кто-нибудь побить целевой показатель, поскольку это покажет, что мой текущий алгоритм не оптимален. (Если вам это удастся, следует распечатать поздравительное сообщение!)
Проблема
Мой текущий алгоритм очень плохо масштабируется с количеством целей. Время растет экспоненциально и для 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;
}
Что я рассмотрел
Вот некоторые варианты, о которых я задумался:
Кеширование промежуточных результатов. Расчет расстояния повторяет множество симуляций, и промежуточные результаты можно кэшировать.
Однако я не думаю, что это остановит экспоненциальную сложность.Алгоритм поиска A *, хотя мне не ясно, какой будет подходящая допустимая эвристика и насколько она эффективна на практике.
Изучите хорошие алгоритмы задачи коммивояжера и посмотрите, применимы ли они к этой проблеме.
Пытаться доказать, что проблема NP-сложна и, следовательно, неразумно искать для нее оптимальный ответ.
источник
Ответы:
Вы искали литературу? Я нашел эти документы, которые, кажется, анализируют вашу проблему:
«Отслеживание движущихся целей и проблема нестационарного коммивояжера»: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.85.9940
«Задача коммивояжера с движущейся мишенью»: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.57.6403
ОБНОВЛЕНИЕ 1:
Две вышеуказанные статьи, кажется, сосредоточены на линейном движении евклидовой метрики.
источник
Жадный метод
Один из подходов, предложенных в комментариях, - сначала перейти к ближайшей цели.
Я мириться версию демо , которая включает в себя стоимость рассчитывается по этому жадному методу здесь .
Код такой:
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), а иногда даже достигает оптимального.
Этот подход очень эффективен, поэтому я могу позволить себе несколько циклов, чтобы улучшить ответ.
Затем я подумываю об использовании методов колонии муравьев, чтобы увидеть, могут ли они эффективно исследовать пространство решений.
Метод колонии муравьев
Метод Ant колонии , кажется, работает замечательный хорошо для этой задачи. Ссылка в этом ответе теперь сравнивает результаты при использовании как жадного метода, так и метода муравьиной колонии.
Идея состоит в том, что муравьи выбирают свой путь вероятностно в зависимости от текущего уровня феромона. После каждых 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
Муравьиный метод кажется значительно лучше жадного и часто очень близким к оптимальному.
источник
Задача может быть представлена в терминах обобщенной задачи коммивояжера, а затем преобразована в обычную задачу коммивояжера. Это хорошо изученная проблема. Возможно, что наиболее эффективные решения проблемы OP не более эффективны, чем решения TSP, но ни в коем случае не уверены (я, вероятно, не могу воспользоваться некоторыми аспектами структуры проблем OP, которые позволили бы быстрее решить , например, его цикличность). В любом случае, это хорошая отправная точка.
Из К. Нун и Дж. Бина, Эффективное преобразование обобщенной задачи коммивояжера :
Для проблемы OP:
N
- это определенная рыба в определенное время. Представьте это как(x, y, t)
, где(x, y)
- координата сетки, аt
время, в которое рыба будет находиться в этой координате. Для самой левой рыбы в примере с OP первые несколько из них (на основе 1):(3, 9, 1), (4, 9, 2), (5, 9, 3)
когда рыба движется вправо.fish(n_i)
вернуть идентификатор рыбы, представленной узлом. Для любых двух членов N мы можем вычислитьmanhattan(n_i, n_j)
манхэттенское расстояние между двумя узлами иtime(n_i, n_j
) для временного смещения между узлами.S_i
будет состоять только из узлов, для которыхfish(n) == i
.i
аj
fish(n_i) != fish(n_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-трудная ... но это один из подходов к поиску оптимального или ограниченного решения .
источник
Я думаю, что другой подход был бы:
Цитата из Википедии:
В математике диаграмма Вороного - это способ разделения пространства на несколько областей. Набор точек (называемых семенами, сайтами или генераторами) указывается заранее, и для каждого семени будет соответствующая область, состоящая из всех точек, более близких к этому семени, чем к любому другому.
Итак, вы выбираете цель, проходите по ней несколько шагов и устанавливаете там начальную точку. Проделайте то же самое со всеми другими целями, и вы получите диаграмму Ворони. В зависимости от того, в какой области вы находитесь, вы переходите к ее исходной точке. Виола, у тебя первая рыба. Теперь повторяйте этот шаг, пока не выловите их все.
источник