I have been trying to write a function to find the shortest path, implementing the AStar algorithm. I have gone through many solutions on net and on this forum. But my bad, I am having a tough time understanding where exactly I need to 'remove the node from the path' if the path did not hit the destination. Infact the adding and removing nodes as we go along a path and as we come back after reaching a dead-end in the recursion, seemed a bit challenging to understand. At the end of the recursion, if the path is not found, I am clearing the path and returning it, which I know is not the way to implement. However, I am sharing the code here. Could someone kindly help me understand what I am doing wrong?
Here is the Node class;
public class Node
{
public string Name { get; private set; }
public Coordinate Location { get; private set; }
public double g { get; set; }
public double h { get; set; }
public double cost { get { return this.g this.h; } }
public List<Node> Neighbours { get; set; }
public Node(string name, Coordinate location)
{
Name = name;
Location = location;
Neighbours= new List<Node>();
}
public void AddNeighbours(List<Node> neighbours)
{
Neighbours.AddRange(neighbours);
}
public double distanceTo(Node node)
{
return Location.Distance(node.Location);
}
}
...and here is the Graph class.
public class Graph
{
List<Node> Nodes = new List<Node>();
public Graph(List<Node> nodes)
{
Nodes = nodes;
}
public List<Node> GetShortestPath(Node source, Node destination, HashSet<Node> visited = null, List<Node> path = null )
{
if ( visited == null ) { visited = new HashSet<Node>(); } // Initialize the visited nodes list
if(path == null ) { path = new List<Node>() {}; } // initialize the shortest path list
if (source == destination){ path.Add(destination); return path;}
visited.Add(source); // Currently visiting this node. So, add to the visited nodes
path.Add(source); // Add the current source to the path
foreach (Node neighbour in source.Neighbours) // for each neighbour node
{
// Update the g and h distances
neighbour.g = source.g source.distanceTo(neighbour);
neighbour.h = neighbour.distanceTo(destination);
}
// Collect the non-visited neighbours
List<Node> nonVisitedNeighbours = source.Neighbours.Where(n => !visited.Contains(n)).ToList();
if (nonVisitedNeighbours.Count > 0) // if non-visited neighbours not empty
{
// sort the neighbours in ascending order and take the first one.
// that will be the closest neighbour with the lowest cost
Node nextNeighbour = nonVisitedNeighbours.OrderBy(n => n.cost).ToList().First();
return GetShortestPath(nextNeighbour, destination, visited, path);
}
path.Clear(); // I hope this is not the right way, but somewhere path.Remove(source) to be added. But not clear where...
Console.WriteLine("No path found!");
return path; // This should return an empty list
}
}
CodePudding user response:
Ok... I fixed it. Here is the final implementation...
public List<Node> GetPath(
Node startNode,
Node targetNode
)
{
List<Node> openSet = new List<Node>();
HashSet<Node> closedSet = new HashSet<Node>();
openSet.Add(startNode);
while (openSet.Count > 0)
{
Node currentNode = openSet[0];
List<Node> nodesWithLesserCost = openSet
.Skip(1)
.ToList()
.Where(node => node.Cost < currentNode.Cost || node.Cost == currentNode.Cost && node.H < currentNode.H)
.OrderBy(n => n.Cost)
.ToList();
if (nodesWithLesserCost.Any())
{
currentNode = nodesWithLesserCost.First();
}
openSet.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{
return RetracePath(startNode, targetNode);
}
foreach (Node neighbour in currentNode.Neighbours)
{
if (closedSet.Contains(neighbour))
{
continue;
}
double newMovementCostToNeighbour = currentNode.G currentNode.DistanceTo(neighbour);
if(newMovementCostToNeighbour < neighbour.G || !openSet.Contains(neighbour))
{
neighbour.G = newMovementCostToNeighbour;
neighbour.H = neighbour.DistanceTo(targetNode);
neighbour.Parent = currentNode;
if (!openSet.Contains(neighbour))
{
openSet.Add(neighbour);
}
}
}
}
Console.WriteLine("No Path Found...!");
return new List<Node>();
}