(C++) Obtener ruta más corta

Me encuentro en un problema un poco elemental. A ver si alguien me puede dar alguna pista de como resolverlo.

Tenemos una cuadrícula tal que:

Imagen

Ahí tenemos dos cuadrados pintados:

Imagen

Partiendo del cuadrado rojo, tenemos que obtener una ruta para llegar al cuadrado azul (moviendose verticalmente/horizontalmente). Para complicar más la cosa ponemos algunos obstaculos aleatorios:

Imagen

La ruta más corta sería:

(7,2)
(7,9)
(10,9)


¿Cómo podríamos implementar esto en C++?
1) Defines las estructuras de datos, que en este caso podría ser generar un grafo con estas condiciones:

- Cada nodo (vértice) lo identificas por sus coordenadas (en este caso, tendrías 10x13=130 nodos)
- Las casillas con obstáculo no pueden ser nodo. O en todo caso, un nodo pero sin relación alguna (aislado).
- Las relaciones (aristas) las defines recorriendo todos los nodos y en función de si el paso de una casilla a otra es posible, de modo que indiques para cada casilla todas aquellas a las que es posible pasar. Las distancias de una casilla a la contigua (es decir, de todas las aristas) siempre será 1.

2) Con el grafo bien definido, implementas un algoritmo de caminos mínimos como el de Dijkstra (encontrarás mil ejemplos sin problemas, por ejemplo la Wikipedia), que te defina el recorrido según nodos de origen y destino.

3) Por último, Si no lo tienes aún, traduce el pseudocódigo que hayas desarrollado a C++. Esta es la parte más fácil.

Un saludo!


PD/ Los problemas de optimización nunca son "elementales".
#include<iostream>
#include<vector>
#include<queue>

using namespace std;

class Cell{
int x;
int y;
public:
int getx(){return x;}
int gety(){return y;}
void setx(int x){this->x=x;}
void sety(int y){this->y=y;}

bool operator==(Cell o){return x==o.x && y==o.y;}
Cell operator=(Cell o){x=o.x; y=o.y; return *this;}

Cell(int x,int y):x(x),y(y){}
Cell():x(0),y(0){}
};
vector<Cell> getShortestPath(Cell ori, Cell dest, int array[], int width, int height);



int main(){
int ejemplo[]={
0,1,0,1,0,0,0,0,                //0: void
0,1,0,1,0,0,0,0,                //1: obstacle
0,1,0,1,0,0,0,0,
0,1,0,1,0,0,0,0,
0,1,0,1,0,0,0,0,
0,1,0,1,0,0,0,0,
0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0};

vector<Cell> camino= getShortestPath(Cell(0,0),Cell(7,0),ejemplo,8,8);
for(int i=0;i<camino.size();i++){
cout<<"("<<camino[i].getx()<<", "<<camino[i].gety()<<")"<<endl;
}

}

vector<Cell> getShortestPath(Cell ori, Cell dest, int array[], int width, int height){

if(ori==dest) return vector<Cell>();
unsigned int *sizes=new unsigned int[width*height];
Cell *prev=new Cell[width*height];
for(int i=0;i<width*height;i++){sizes[i]=-1; prev[i]=Cell(-1,-1);}

sizes[ori.getx()+ori.gety()*width]=0;
prev[ori.getx()+ori.gety()*width]=ori;


queue<Cell> porVisitar;
porVisitar.push(ori);

while(!porVisitar.empty()){
Cell cur=porVisitar.front();
porVisitar.pop();
cout<<porVisitar.size()<<endl;
for(int i=-1;i<2;i++)
for(int j=-1;j<2;j++)
if(     (cur.getx()+j)>=0 && (cur.getx()+j)<width && (cur.gety()+i)>=0 && (cur.gety()+i)<height &&              //is not out of bounds
array[(cur.getx()+j)+(cur.gety()+i)*width]==0 &&                        //is not an obstable
sizes[cur.getx()+cur.gety()*width]+1 < sizes[(cur.getx()+j)+(cur.gety()+i)*width]               //there is not a better path
){
sizes[(cur.getx()+j)+(cur.gety()+i)*width]=sizes[cur.getx()+cur.gety()*width]+1;
prev[(cur.getx()+j)+(cur.gety()+i)*width]=Cell(cur.getx(),cur.gety());
porVisitar.push(Cell(cur.getx()+j,cur.gety()+i));
}

}

if(prev[dest.getx()+dest.gety()*width]==Cell(-1,-1)) return vector<Cell>();

Cell pp=dest;
vector<Cell> res(sizes[dest.getx()+dest.gety()*width]+1);
for(int i=res.size()-1; !(pp==ori); i--){
res[i]=pp;
pp=prev[pp.getx()+pp.gety()*width];
}

return res;

}


Gracias
Lo que estás buscando es un algoritmo de pathfinding. Si buscas en google por los algoritmos A* (A-Star), Dijkstra... encontrarás las explicaciones que necesitas para comprender mejor el tema.
Como nota anecdótica sobre la complejidad (de implementación) de los algoritmos propuestos:

(Menos complejo) BFS -> Dijkstra o Bellman-Ford (si las aristas de los nodos tienen pesos negativos) -> A* (Más complejo)

Y una página sobre cálculo de caminos cortos: http://www-cs-students.stanford.edu/~am ... html#paths
Sólo decir que A* no te calcula necesariamente el camino óptimo, a diferencia de Dijkstra, que sí lo hace siempre. Como contrapartida, A* es idóneo para cálculos en tiempo real (videojuegos)

Saludos
5 respuestas