I am trying to implement Dirjkstra's Algorithm to give me the optimal path between 2 nodes in a weighted graph representing a 2d matrix.
Summary:
If I use equal edge weights for the graph, the optimal path has been (as far as I can tell) always been returned correctly
If I introduce a "wall" within the path, most of the time the search result won't be valid, often with missing edges or invalid jumps
Valid movements are up/down/left/right
An example of the error:
-- matrix nodeIds: --
[ 0][ 1][ 2][ 3][ 4]
[ 5][ 6][ 7][ 8][ 9]
[10][11][12][13][14]
[15][16][17][18][19]
[20][21][22][23][24]
-- matrix node Weights: --
[ 1][99][ 1][ 1][ 1]
[ 1][99][ 1][99][ 1]
[ 1][99][ 1][99][ 1]
[ 1][99][ 1][99][ 1]
[ 1][ 1][ 1][99][ 1]
-- Optimal Path Taken --
[*][ ][*][ ][*]
[*][ ][*][ ][*]
[ ][ ][*][ ][*]
[*][*][*][ ][*]
[ ][*][*][ ][*]
-- Optimal Path String --
-- NodeId->(Weight) --
24->(1)->19->(1)->22->(1)->9->(1)->16->(99)->7->(1)->12->(1)->17->(1)->14->(1)->21->(1)->4->(1)->15->(1)->2->(1)->5->(1)->0->(1)->
-- all paths searched: --
start->end(weight) 0->1(99)
start->end(weight) 5->2(1)
start->end(weight) 15->4(1)
start->end(weight) 0->5(1)
start->end(weight) 5->6(99)
start->end(weight) 12->7(1)
start->end(weight) 15->8(99)
start->end(weight) 16->9(1)
start->end(weight) 2->11(99)
start->end(weight) 17->12(1)
start->end(weight) 12->13(99)
start->end(weight) 21->14(1)
start->end(weight) 2->15(1)
start->end(weight) 7->16(99)
start->end(weight) 14->17(1)
start->end(weight) 17->18(99)
start->end(weight) 22->19(1)
start->end(weight) 4->21(1)
start->end(weight) 9->22(1)
start->end(weight) 14->23(99)
start->end(weight) 19->24(1)
Here is my code, which you should be able to copy/paste and run if you like (C++98):
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
const int BOARD_SIZE = 5;
const int NUM_ELEMENTS = BOARD_SIZE * BOARD_SIZE;
int gMatrix[BOARD_SIZE][BOARD_SIZE];
// The Weighted queue always returns the lowest weight value node from front()
class WeightedQueue {
public:
WeightedQueue();
int get(); // return the item with the lowest weight value and remove it from the map
void push(int weight, int NodeId); // add item
bool empty(); // is it empty
private:
std::map<int, int> mWeightedPositions; // weightValue, NodeId
};
WeightedQueue::WeightedQueue()
{
}
void WeightedQueue::push(int weight, int NodeId)
{
mWeightedPositions[weight] = NodeId;
}
bool WeightedQueue::empty()
{
return mWeightedPositions.empty();
}
int WeightedQueue::get()
{
std::map<int, int>::iterator iter = mWeightedPositions.begin();
int nodeId = iter->second; // nodeId
mWeightedPositions.erase(iter);
return nodeId;
}
// Matrix position row,col
struct MatrixPos
{
int row;
int col;
};
// get linear index from row, col
int getLinearIndex(int row, int col)
{
int linearIndex = BOARD_SIZE * col + row;
return linearIndex;
}
// convert linear index to matrix position row, col
MatrixPos matrixPos(int nodeId)
{
MatrixPos matrixPos = { nodeId / BOARD_SIZE, nodeId % BOARD_SIZE };
return matrixPos;
}
// reconstruct the optimal path and print it
void reconstructPath(int start, int goal, std::map<int, int> & cameFrom, std::map<int, int> & weights)
{
std::vector<int> path;
int current = goal;
path.push_back(current);
while (current != start)
{
current = cameFrom[current];
path.push_back(current);
}
printf("\n -- Optimal Path Taken -- \n");
for (int i = 0; i < NUM_ELEMENTS; i++)
{
if (matrixPos(i).col == 0)
{
printf("\n");
}
char tileValue = ' ';
for (int j = 0; j < NUM_ELEMENTS; j++)
{
if (path[j] == i)
{
tileValue = '*';
break;
}
}
printf("[%c]", tileValue);
}
printf("\n -- Optimal Path String -- \n");
printf("\n -- NodeId->(Weight) -- \n");
for (int i = 0; (int) i < path.size(); i++)
{
printf("%d->(%d)->", path[i], weights[path[i]]);
}
printf("\n");
}
// print all the paths taken by the search + the weight of the destination node
void printPaths(std::map<int, int> & pathMap, std::map<int, int> & weightMap)
{
printf("\n -- all paths searched: -- \n");
for (std::map<int, int>::iterator it = pathMap.begin(); it != pathMap.end(); ++it)
{
int destX = matrixPos(it->first).row;
int destY = matrixPos(it->first).col;
int startX = matrixPos(it->second).row;
int startY = matrixPos(it->second).col;
int startWeight = weightMap[it->second];
int endWeight = weightMap[it->first];
printf("start->end(weight) %d->%d(%d) \n", it->second, it->first, endWeight);
}
}
// populate the Matrix and weights map
void buildMatrix(std::map<int, int> & weightMap)
{
for (int i = 0; i < NUM_ELEMENTS; i++)
{
gMatrix[matrixPos(i).row][matrixPos(i).col] = i;
weightMap[i] = 1;
}
weightMap[1] = 99;
weightMap[6] = 99;
weightMap[11] = 99;
weightMap[16] = 99;
//
weightMap[23] = 99;
weightMap[18] = 99;
weightMap[13] = 99;
weightMap[8] = 99;
}
// print matrix displaying nodeIds and Weights
void printMatrix(std::map<int, int> & weightMap)
{
printf("\n -- matrix nodeIds: -- \n");
for (int i = 0; i < NUM_ELEMENTS; i++)
{
if (matrixPos(i).col == 0)
{
printf("\n");
}
printf("[%2d]", gMatrix[matrixPos(i).row][matrixPos(i).col]);
}
printf("\n");
printf("\n -- matrix node Weights: -- \n");
for (int i = 0; i < NUM_ELEMENTS; i++)
{
if (matrixPos(i).col == 0)
{
printf("\n");
}
printf("[%2d]", weightMap[i]);
}
printf("\n");
}
// identify the neighboring nodes for nodeId, up down left right
void collectNeighbors(int nodeId, std::vector<int> & neighbors)
{
int curRow = nodeId / BOARD_SIZE;
int curCol = nodeId % BOARD_SIZE;
// left
if (curRow - 1 > 0)
{
int shiftLeft = curRow - 1;
int neighborIndex = getLinearIndex(shiftLeft, curCol);
neighbors.push_back(neighborIndex);
}
// right
if (curRow + 1 < BOARD_SIZE)
{
int shiftRight = curRow + 1;
int neighborIndex = getLinearIndex(shiftRight, curCol);
neighbors.push_back(neighborIndex);
}
// up
if (curCol - 1 > 0)
{
int shiftUp = curCol - 1;
int neighborIndex = getLinearIndex(curRow, shiftUp);
neighbors.push_back(neighborIndex);
}
// down
if (curCol + 1 < BOARD_SIZE)
{
int shiftDown = curCol + 1;
int neighborIndex = getLinearIndex(curRow, shiftDown);
neighbors.push_back(neighborIndex);
}
}
void searchMatrix(int startNodeId, int goalNodeId, std::map<int, int> & weightMap)
{
std::map<int, int> cameFrom; // neighbor NodeId, current NodeId
std::map<int, int> costSoFar; // nodeId, cost
std::vector<int> neighbors; // list of the neighboring NodeIds
WeightedQueue weightedQueue;
weightedQueue.push(0, startNodeId); // the Queue of nodes, the lowest weight node is returned first by front()
costSoFar[startNodeId] = 0;
while (!weightedQueue.empty())
{
// current index we are working with
int currentNode = weightedQueue.get();
// exit if we've reached the goal node
if (currentNode == goalNodeId)
{
break;
}
// get all the neighbors for this position
neighbors.clear();
collectNeighbors(currentNode, neighbors);
for (int i = 0; i < neighbors.size(); i++)
{
int neighborNode = neighbors[i];
int totalCost = costSoFar[currentNode] + weightMap[neighborNode];
if (!costSoFar.count(neighborNode) || totalCost < costSoFar[neighborNode])
{
// if we haven't been here yet, add it to the weightedQueue
weightedQueue.push(weightMap[neighborNode], neighborNode);
cameFrom[neighborNode] = currentNode;
costSoFar[neighborNode] = totalCost;
}
}
}
printMatrix(weightMap);
reconstructPath(startNodeId, goalNodeId, cameFrom, weightMap);
printPaths(cameFrom, weightMap);
}
int main()
{
std::map<int, int> weightMap;
buildMatrix(weightMap);
searchMatrix(0, 24, weightMap);
}