fundo
Esta imagem ilustra o problema:
Eu posso controlar o círculo vermelho. Os alvos são os triângulos azuis. As setas pretas indicam a direção em que os alvos se moverão.
Quero coletar todos os alvos no número mínimo de etapas.
Cada curva devo mover 1 passo para a esquerda / direita / cima ou para baixo.
Cada vez, os alvos também se moverão 1 passo de acordo com as instruções mostradas no tabuleiro.
Demo
Eu coloquei uma demonstração jogável do problema aqui no Google appengine .
Eu estaria muito interessado se alguém pudesse bater a pontuação desejada, pois isso mostraria que meu algoritmo atual está abaixo do ideal. (Uma mensagem de parabéns deve ser impressa se você conseguir isso!)
Problema
Meu algoritmo atual escala muito mal com o número de alvos. O tempo aumenta exponencialmente e para 16 peixes já leva vários segundos.
Eu gostaria de calcular a resposta para tamanhos de placa de 32 * 32 e com 100 alvos móveis.
Questão
O que é um algoritmo eficiente (idealmente em Javascript) para calcular o número mínimo de etapas para coletar todos os alvos?
O que eu tentei
Minha abordagem atual é baseada no memoisation, mas é muito lenta e não sei se sempre gerará a melhor solução.
Eu resolvo o subproblema de "qual é o número mínimo de etapas para coletar um determinado conjunto de alvos e terminar em um determinado alvo?".
O subproblema é resolvido recursivamente examinando cada escolha do destino anterior que foi visitado. Presumo que seja sempre ideal coletar o subconjunto anterior de alvos o mais rápido possível e, em seguida, mover da posição em que você acabou para o alvo atual o mais rápido possível (embora eu não saiba se essa é uma suposição válida).
Isso resulta em n * 2 ^ n estados a serem calculados, que crescem muito rapidamente.
O código atual é mostrado abaixo:
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;
}
O que eu considerei
Algumas opções que me perguntam são:
Cache de resultados intermediários. O cálculo da distância repete muitas simulações e os resultados intermediários podem ser armazenados em cache.
No entanto, não acho que isso impediria de ter complexidade exponencial.Um algoritmo de busca A *, embora não esteja claro para mim qual seria uma heurística admissível apropriada e quão eficaz isso seria na prática.
Investigar bons algoritmos para o problema do caixeiro-viajante e ver se eles se aplicam a este problema.
Tentar provar que o problema é NP-difícil e, portanto, irracional, buscar uma resposta ótima para ele.