História
Indiana Jones estava explorando uma caverna onde um tesouro precioso está localizado. De repente, um terremoto aconteceu.
Quando o terremoto terminou, ele notou que algumas pedras que caíam do teto bloqueavam o caminho para o tesouro. Ele também percebeu que podia empurrar uma pedra, mas como as pedras eram muito pesadas, ele não pôde empurrar duas pedras consecutivas .
Seu objetivo é ajudar Indiana Jones a obter o tesouro. Como é muito difícil empurrar uma única pedra, o número de empurrões é muito importante.
Problema
Encontre o melhor caminho (onde Indiana Jones empurra as pedras o mínimo possível), para encontrar o tesouro.
Mapa (entrada)
O mapa é um m
por n
(tanto maior do que 1) da matriz, que pode conter cinco tipos de células:
0
o que significa a célula em branco,1
o que significa a parede,2
em que Indiana Jones está localizado (apenas um existe),3
em que o tesouro está localizado (apenas um existe),- e
4
, o que significa uma rocha.
Na primeira linha do mapa, a dimensão do mapa é especificada como 4 6
, e da segunda linha para a última linha do mapa, a estrutura da caverna é especificada algo como isto.
110131
104040
100101
200000
Portanto, o mapa completo é:
4 6
110131
104040
100101
200000
que significa
O mapa é fornecido por stdin, um arquivo (você pode especificar o nome do arquivo) ou uma matriz no código que contém apenas as informações acima.
Resultado
O valor mínimo que Indiana Jones deve aumentar. Se não houver, saída X
.
No caso acima , ele pode empurrar uma pedra da esquerda para cima e, em seguida, pode empurrar uma pedra da direita para obter o tesouro. Portanto, a saída neste caso é 2
.
Contudo. nesse caso :
4 6
111131
104040
100101
200000
(veja a seção abaixo) ele não pode empurrar a pedra certa, porque ela destruirá o tesouro. Além disso, empurrar a pedra esquerda para a direita não muda nada. Portanto, a saída é X
.
Regras
- Ele pode se mover em apenas quatro direções, para cima, para baixo, esquerda e direita.
- Ele não pode empurrar duas pedras consecutivas .
- Ele não pode puxar nenhuma pedra e só pode empurrá-la em uma direção ('para frente').
- Ele não pode atravessar paredes. Apenas os lugares que ele pode ir são as células em branco e a célula do tesouro.
- A pedra não pode ser colocada no tesouro. Isso destruirá o tesouro. :(
- Ele não pode sair do mapa.
Metas
O programa que pode lidar com o maior número de mapas (fornecido na seção 'Exemplo' e outros) em um tempo razoável (especificamente, 10 segundos) e produz a resposta certa ganha.
Aqui 'outros' significa exemplos de entradas fornecidas nas respostas. Isso significa que você deve criar um algoritmo inteligente para que outros programas não consigam resolver mapas que seu programa possa resolver, e mapas resolvidos por outros programas possam ser resolvidos por seu programa. No entanto, colocar soluções no código não será considerado válido.
Nota
Originalmente, esse era um projeto de médio prazo de uma aula de IA que eu ouvia; apenas uma coisa era diferente: dizia-se que havia apenas duas rochas.
Foi dito que esse problema é NP, mas também foi dito que um bom algoritmo heurístico pode resolver o problema com bastante eficiência. Usei algumas idéias e heurísticas para resolver o problema com eficiência, e meu código conseguiu encontrar todas as soluções de amostras muito rapidamente (menos de um segundo).
No entanto, quando havia mais de duas pedras, havia alguns casos em que o código não conseguia encontrar a resposta em um tempo razoável. Eu tinha algumas idéias, mas algumas estavam 'erradas' e não pude expressar outras no código. Eu queria ver quais algoritmos inteligentes existem para resolver esse problema, então escrevi isso.
Como já concluí o projeto (aliás, as imagens não são minhas - pesquisei no Google), você não precisa se preocupar com isso.
Exemplos
Exemplos podem ser vistos aqui. Você também pode ver exemplos e testar seus resultados aqui (isso deve funcionar em navegadores modernos). Você pode obter o mapa no formato descrito acima, digitando whatisthis()
no console JS.
http://bit.sparcs.org/~differ/sokoban/#0 ~ http://bit.sparcs.org/~differ/sokoban/#19 contém exemplos dos quais originalmente era a classe fornecida.
Resultado
Desculpe, eu estava atrasado .. bastante na verdade. : P (eu estava com preguiça de marcar. Desculpe.)
Abaixo está o resultado. (X: errado, O: certo,?: Leva pelo menos 10s, interrompido)
Map#: 0 1 2 3 4 5 12 15 19 T1 T2 T3 T4 T5 T6 T7
Ruby: X O O ? O O O X ? ? O ? ? ? ? ?
Java: O O X O X X O O ? ? O O O X O O
(Java 19: demorou 25s, o resultado estava correto.) (Eu usei o ruby 1.9.3 e o javac 1.7.0_13)
Parece que o algoritmo Java era realmente melhor. (A propósito, pensei em um método semelhante, mas percebi que existem mapas como o mapa de teste 5).
fonte
Respostas:
Java - Um pouco mais inteligente / rápido
Bastante código lá. Estou tentando ser mais rápido avaliando os impulsos na ordem de "qual a probabilidade de liberar um caminho para o tesouro", que é baseado em dois percursos Dijkstra (um para quando encontra pedras, o outro ignora rochas). Está funcionando muito bem, e o exemplo da pasta que parece ser problemático para o autor é resolvido em aproximadamente 2 segundos por essa implementação. Alguns outros exemplos levam de 30 a 40 segundos, o que eu ainda acho muito longo, mas não consegui encontrar uma maneira de melhorar isso sem quebrar as coisas :)
Dividi minhas coisas em vários arquivos para obter uma melhor estrutura (também porque mudei para Java do ruby):
Ponto de entrada:
Enum do auxiliar de direção:
Uma classe abstrata para representar uma parte localizada do "labirinto":
E, finalmente, o próprio labirinto:
fonte
Ruby - Enorme e empolgado
Implementação um tanto ingênua que força bruta em seu caminho através do labirinto. Não é super rápido em alguns casos (não tão) estranhos. Pode ser aprimorado encontrando heurísticas melhores do que apenas "se estiver mais próximo do tesouro, vamos investigar primeiro", mas as idéias gerais estão lá.
Também mostrará como Indiana colocou as mãos no tesouro, caso ele possa, isso é bônus.
Edit: Pensei em várias maneiras de melhorar significativamente o desempenho disso em situações não óbvias (onde atualmente chupa ovos verdes) descartando uma simples avaliação de movimento (por exemplo, só me importo quando indy empurra pedras e / ou chega ao tesouro). Provavelmente atualizarei o código mais tarde, quando tiver tempo de implementar.
fonte
C ++ 14 em 16
O algoritmo é ineficiente e tem muita memória. Além disso, eu não tinha tempo para arrumar tudo, mas terei quando tiver mais tempo;) Um ponto interessante é que meu algoritmo falha nos mesmos mapas de teste que o questionador. No meu notebook antigo, o processo começa a ser trocado pelos mapas T4 e T6. O mapa 3 leva muito tempo, mas é resolvido com o tempo. Todos os outros são resolvidos quase instantaneamente. Então terei que descobrir como resolver T4 e T6 e tentar o algoritmo em uma máquina com mais memória. Eventualmente eu posso resolver T4 e T6 lá. Vou manter a postagem atualizada ...
Abaixo está o resultado. (X: errado, O: certo,?: Leva pelo menos 10s, interrompido)
Como o código fonte é bastante longo e não é muito agradável de ler ... Basicamente, ele apenas procura todas as rochas que podem ser alcançadas por Indiana Jones. Para as rochas que podem ser alcançadas, ele armazena as informações em quais direções ele pode ser movido. Portanto, é criada uma lista de possíveis movimentos para o mapa atual. Para cada um desses movimentos possíveis, uma cópia do mapa é criada e o movimento aplicado. Para os mapas recém-criados, o algoritmo verificará novamente quais movimentos podem ser aplicados ... Isso é feito até que nenhum movimento adicional seja possível ou um caminho para a arca do tesouro seja encontrado. À medida que o algoritmo tenta primeiro todos os movimentos que precisariam de apenas um movimento para chegar ao peito, tudo o que precisaria de dois, e assim por diante ... a primeira maneira encontrada também é automaticamente a mais curta. Para evitar loops, o algoritmo lembra para cada mapa que movimentos poderiam ser aplicados. Se for criado outro mapa que resulte em uma lista de movimentos que já foram encontrados antes, eles serão eliminados silenciosamente, pois já estão sendo processados. Infelizmente, não é possível executar todos os movimentos apenas uma vez, pois pode haver mapas que exigem que uma rocha seja movida sobre o mesmo campo várias vezes. Caso contrário, eu poderia economizar muita memória. Além disso, para resolver mapas como o mapa 3 a tempo, o algoritmo ignora todas as rochas que podem ser percorridas ... Portanto, no mapa 3, a rocha no meio do nada será movida, mas apenas até que não haja mais paredes ao redor. O código pode ser compilado com g ++ --std = c ++ 0x com g ++ versão 4.4.3 ou mais recente. não é possível executar todos os movimentos apenas uma vez, pois pode haver mapas que exigem que uma rocha seja movida sobre o mesmo campo várias vezes. Caso contrário, eu poderia economizar muita memória. Além disso, para resolver mapas como o mapa 3 a tempo, o algoritmo ignora todas as rochas que podem ser percorridas ... Portanto, no mapa 3, a rocha no meio do nada será movida, mas apenas até que não haja mais paredes ao redor. O código pode ser compilado com g ++ --std = c ++ 0x com g ++ versão 4.4.3 ou mais recente. não é possível executar todos os movimentos apenas uma vez, pois pode haver mapas que exigem que uma rocha seja movida sobre o mesmo campo várias vezes. Caso contrário, eu poderia economizar muita memória. Além disso, para resolver mapas como o mapa 3 a tempo, o algoritmo ignora todas as rochas que podem ser percorridas ... Portanto, no mapa 3, a rocha no meio do nada será movida, mas apenas até que não haja mais paredes ao redor. O código pode ser compilado com g ++ --std = c ++ 0x com g ++ versão 4.4.3 ou mais recente. mas apenas até que não haja mais paredes ao seu redor. O código pode ser compilado com g ++ --std = c ++ 0x com g ++ versão 4.4.3 ou mais recente. mas apenas até que não haja mais paredes ao seu redor. O código pode ser compilado com g ++ --std = c ++ 0x com g ++ versão 4.4.3 ou mais recente.
Editar: O programa pega sua entrada do stdin e ignora a primeira linha que contém o tamanho do mapa. Ele verifica se apenas os caracteres permitidos no mapa são usados, mas não verifica se há apenas um Indiana Jones e um baú do tesouro. Portanto, é possível colocar mais de um e o mínimo de movimentos necessários para alcançar um dos baús é impresso em stdout. Quaisquer caracteres inválidos no mapa são ignorados e o programa tentará calcular a menor quantidade de movimentos para o mapa resultante. O cálculo começará quando o stdin estiver fechado (no meu sistema ctrl + d).
fonte