Como encontrar o caminho mais curto com nós de buraco de minhoca?


25

exemplo

Este é um exemplo do que eu quero fazer via código. Eu sei que você pode usar a pesquisa de pontos de salto para passar facilmente do nó verde para o nó vermelho sem problemas ou mesmo A *. Mas como você calcula isso com deformações.

Na imagem, você pode ver que são necessários apenas oito movimentos para ir do nó verde para o nó vermelho ao seguir o caminho azul. O caminho azul move instantaneamente sua posição de um nó roxo para o próximo. O espaço no meio que custa 2 movimentos é um ponto entre duas zonas de distorção que você deve mover para chegar.

É claramente mais rápido seguir o caminho azul, já que você só precisa mover metade (aproximadamente) até o caminho amarelo, mas como faço isso programaticamente?

Com o objetivo de resolver esse problema, vamos supor que haja vários "warps" roxos ao redor do gráfico que você possa usar, E sabemos exatamente para onde cada ponto roxo se distorcerá e onde estão no gráfico.

Algumas urdiduras roxas são bidirecionais e outras não, o que significa que, às vezes, você só pode inserir uma urdidura de um lado, mas não voltar após a deformação.

Pensei na solução e só concluí que seria capaz de calcular o problema verificando a distância de cada ponto de distorção (menos os pontos unidirecionais) e a diferença entre esses pontos e os pontos próximos a eles. .

O programa teria que descobrir de alguma forma que é mais benéfico realizar o segundo warp, em vez de caminhar desde o primeiro salto. Então, em vez de mover 6 pontos, entortar e depois mover os 8 passos restantes a pé (o que também é mais rápido do que não usar deformações), seriam necessários os 6 movimentos, depois os dois movimentos para o segundo warp.

Edição: Eu percebi que o caminho azul vai realmente levar 12 movimentos, em vez de 8, mas a questão permanece a mesma.


4
O caminho azul não deve ser 12 (incluindo os dois para passar do último roxo para o vermelho)?
BlueRaja - Danny Pflughoeft

5
Azul é na verdade 12 (7 + 3 + 2) jogadas, não?
Daniel Jour

Opa, errei, obrigado pessoal! @DanielJour and Blue
Jeff smith

A maneira "correta" de modelar as distâncias seria usar a topologia e modelá-la como uma superfície dimensional mais alta. Gostaria de saber se tal resposta seria apropriada aqui?
1 em

Respostas:


49

A maioria dos algoritmos de localização de caminhos é definida em termos de gráficos, não em termos de grades. Em um gráfico, uma conexão entre dois nós distantes não é realmente um problema.

No entanto, você deve tomar cuidado com suas heurísticas. Nos buracos de minhoca, a distância mínima entre dois nós não é mais a distância euclidiana e a distância não satisfaz a desigualdade do triângulo. Tais heurísticas são inadmissíveis para A *. Portanto, você não pode usar A * facilmente.

Obviamente, algoritmos de localização de caminhos como o Dijkstra que não usam uma heurística ainda funcionarão. É mais como uma pesquisa abrangente e selecionará seus buracos de minhoca sem esforço extra. No entanto, Dijkstra visitará mais nós que A * com uma boa heurística. (Dijkstra é equivalente a A * com heuristic(x) = 0.)

Acho que A * funcionará se você usar uma heurística que trate todos os buracos de minhoca de saída como um buraco de minhoca diretamente para o alvo: a heurística pode subestimar a distância, mas nunca deve superestimá-la. Ou seja, a heurística seria:

def wormhole_heuristic(x):
  return min(euclidean_distance(x, g) for g in [goal, wormholes...])

Para uma heurística muito precisa, você pode (recursivamente) adicionar a distância do ponto final do buraco de minhoca até a meta ou o próximo buraco de minhoca. Ou seja, como pré-cálculo, você poderia realizar a localização de um caminho no subgrafo (totalmente conectado) contendo todos os buracos de minhoca e a meta, em que a distância entre dois nós é a distância euclidiana. Isso pode ser benéfico se o número de buracos de minhoca for muito menor que o número de células alcançáveis ​​em sua grade. A nova heurística seria:

def wormhole_heuristic(x):
  direct = euclidean_distance(x, goal)
  via_wormhole = min(euclidean_distance(x, w) + wormhole_path_distance(w, goal) for w in wormholes)
  return min(direct, via_wormhole)

Como o @Caleth aponta nos comentários, tudo é muito sintonizável e podemos melhorar a primeira heurística do buraco de minhoca sem fazer um caminho completo pela rede do buraco de minhoca, adicionando a distância entre a última saída do buraco de minhoca e a meta. Como não sabemos qual saída de buraco de minhoca será usada por último e não devemos superestimar, temos que assumir a saída mais próxima da meta:

def wormhole_heuristic(x):
  direct = euclidean_distance(x, goal)
  to_next_wormhole = min(euclidean_distance(x, w) for w in wormholes)
  from_last_wormhole = min(euclidean_distance(w.exit, goal) for w in wormholes)
  via_wormhole = to_next_wormhole + from_last_wormhole
  return min(direct, via_wormhole)

10
Vale notar que Dijkstra é apenas A * comdijkstra_heuristic(x) = 0
Caleth

Eu não entendo o que você quer dizer com [* buracos de minhoca, objetivo], você explicaria isso?
Jeff smith

1
"Distância euclidiana até a saída mais próxima do buraco de minhoca" é uma estimativa mais barata do wormhole_path_distanceque a pesquisa por subgráficos e menos subestimada do que a "todas as saídas estão na meta".
Caleth

3
@ Caleth certamente! Há muito potencial de ajuste fino aqui, por exemplo, podemos decidir olhar para frente n = 3 saltos. A busca do subgráfico corresponde ao fechamento de todos os saltos de buracos de minhoca acíclicos. Sua sugestão de olhar em frente n = 1 saltos é muito elegante, pois tem praticamente zero custo extra :)
amon

1
Para simplificar, suponha que haja apenas um buraco de minhoca (dois nós), então você pode converter esse plano de um buraco de minhoca em dois planos de espelho, copiando um plano simétrico com a linha equidistante entre esses dois pontos como eixo de simetria. Agora você tem dois planos, vamos chamá-los de plano real (você não pega o buraco de minhoca) e o plano imaginário (você pegou o buraco de minhoca). Agora, apresentamos a coordenada Z. Essa coordenada será 0 para cada ponto no plano real e será dist (buraco de minhoca, ponto) para cada ponto do plano imaginário. Depois disso, aplique A * para o espaço tridimensional.
lilezek

5

Você tem um gráfico com 6 vértices em uma grade com coordenadas:

A ( 0,0)
B ( 4,7)
C ( 7,4)
D (10,4)
E (16,2)
F (16,0)

Você pode gerar um gráfico completo nesses vértices e atribuir um custo a cada aresta, onde o custo é MAX( ABS( x1 - x2 ), ABS( y1 - y2 ) )para arestas padrão e um custo de 0 para buracos de minhoca.

Isso fornecerá os custos (como uma matriz de adjacência):

   A  B  C  D  E  F
- -- -- -- -- -- --
A  -  7  7 10 16 16
B  7  -  0  6 12 12
C  7  0  -  3  9  9
D 10  6  3  -  0  6
E 16 12  9  0  -  2
F 16 12  9  6  2  -

Se houver distorções unidirecionais, crie apenas arestas no gráfico (ou matriz de adjacência) que vão nessa direção, mas não no oposto.

Você pode usar o algoritmo de Dijkstra com uma fila de prioridade .

Comece Ae empurre cada extremidade adjacente para a fila de prioridade:

Formato: (caminho: custo)

queue     = [ (A-B : 7), (A-C : 7), (A-D : 10), (A-E : 16), (A-F : 16) ]

À medida que os itens são empurrados para a fila - acompanhe o custo mínimo de cada vértice e só empurre caminhos para a fila se for um custo menor que o custo mínimo existente.

min-costs = { A: 0, B: 7, C: 7, D: 10, E: 16, F: 16 }

Remova o primeiro item da fila e, se o custo ainda corresponder ao custo mínimo, empurre de volta todos os caminhos compostos formados por esse caminho e suas bordas adjacentes para a fila de prioridade (se os caminhos compostos tiverem um custo menor que o mínimo existente):

Remover: (A-B : 7)

  • Tente (A-B-A : 14)- rejeite como custo mais alto
  • Tente (A-B-C : 7)- rejeite como o mesmo custo
  • Tente (A-B-D : 13)- rejeite como custo mais alto
  • Tente (A-B-E : 19)- rejeite como custo mais alto
  • Tente (A-B-F : 19)- rejeite como custo mais alto

Remover (A-C : 7)

  • Tente (A-C-A : 14)- rejeite como custo mais alto
  • Tente (A-C-B : 7)- rejeite como o mesmo custo
  • Tente (A-C-D : 10)- rejeite como o mesmo custo
  • Tente (A-C-E : 16)- rejeite como o mesmo custo
  • Tente (A-C-F : 16)- rejeite como o mesmo custo

Remover (A-D : 10)

  • Tente (A-D-A : 20)- rejeite como custo mais alto
  • Tente (A-D-B : 16)- rejeite como custo mais alto
  • Tente (A-D-C : 13)- rejeite como custo mais alto
  • Tentar (A-D-E : 10)- inserir na fila
  • Tente (A-D-F : 16)- rejeite como o mesmo custo

Agora a fila terá a seguinte aparência:

queue     = [ (A-D-E : 10), (A-E : 16), (A-F : 16) ]
min-costs = { A: 0, B: 7, C: 7, D: 10, E: 10, F: 16 }

Remover (A-D-E : 10)

  • Tente (A-D-E-A : 26)- rejeite como custo mais alto
  • Tente (A-D-E-B : 22)- rejeite como custo mais alto
  • Tente (A-D-E-C : 19)- rejeite como custo mais alto
  • Tente (A-D-E-D : 10)- rejeite como o mesmo custo
  • Tentar (A-D-E-F : 12)- inserir na fila

Então a fila é:

queue     = [ (A-D-E-F : 12), (A-E : 16), (A-F : 16) ]
min-costs = { A: 0, B: 7, C: 7, D: 10, E: 10, F: 12 }

Remova (A-D-E-F : 12), verifique se você chegou ao nó de destino com um custo de 12.

Nota: os caminhos (A-B-C-D-E-F), (A-C-D-E-F)e (A-D-E-F)todos têm o mesmo custo mínimo de 12.


0

Configure uma matriz contendo todos os vértices e use o algoritmo de Floyd-Wallenstein ou o algoritmo de Bellman-Ford. Ambos resultarão em uma matriz com os caminhos mais curtos possíveis entre todos os pontos. Você pode percorrer a matriz para encontrar o caminho mais curto que conecta dois pontos. (seu problema é o mesmo que um TSP assimétrico).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.