I'm studying A* algorithm, and there is some confusion about re-visiting.
When my professor explains A*, he said if I re-visit a node that is already in closed list,
I have to check cost of re-visiting versus original cost.
If re-visiting is cheaper, I should abandon a node in closed list and add re-visited node on it.
so pseudo code is like this :
GeneralGraphSearch( v )
Prepare two empty lists: OPEN, CLOSED
Insert v with Coste(v) into OPEN
While forever
If OPEN is empty, return failure
while forever
v = the node with the lowest cost in OPEN
remove v from OPEN
if v is not in CLOSED // v is not visited
break
else if the new cost is cheaper than the cost of v in CLOSED
remove v in CLOSED
break
end if
end while
If v is a goal, return success
Insert v into CLOSED
Expand v
Insert all children of v with their costs into OPEN
end while
End
However, when I lookup wikipedia, it seems like they just ignore a node if it is already in closed list.
Instead, they deal with a node that is already in open list.
Their version of pseudo code is like this :
function A*(start, goal)
// The set of nodes already evaluated.
closedSet := {}
// The set of currently discovered nodes that are not evaluated yet.
// Initially, only the start node is known.
openSet := {start}
// For each node, which node it can most efficiently be reached from.
// If a node can be reached from many nodes, cameFrom will eventually contain the
// most efficient previous step.
cameFrom := the empty map
// For each node, the cost of getting from the start node to that node.
gScore := map with default value of Infinity
// The cost of going from start to start is zero.
gScore[start] := 0
// For each node, the total cost of getting from the start node to the goal
// by passing by that node. That value is partly known, partly heuristic.
fScore := map with default value of Infinity
// For the first node, that value is completely heuristic.
fScore[start] := heuristic_cost_estimate(start, goal)
while openSet is not empty
current := the node in openSet having the lowest fScore[] value
if current = goal
return reconstruct_path(cameFrom, current)
openSet.Remove(current)
closedSet.Add(current)
for each neighbor of current
if neighbor in closedSet
continue // Ignore the neighbor which is already evaluated.
// The distance from start to a neighbor
tentative_gScore := gScore[current] + dist_between(current, neighbor)
if neighbor not in openSet // Discover a new node
openSet.Add(neighbor)
else if tentative_gScore >= gScore[neighbor]
continue // This is not a better path.
// This path is the best until now. Record it!
cameFrom[neighbor] := current
gScore[neighbor] := tentative_gScore
fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)
return failure
function reconstruct_path(cameFrom, current)
total_path := [current]
while current in cameFrom.Keys:
current := cameFrom[current]
total_path.append(current)
return total_path
So which way is correct??
Or both are same?
EDIT: actually both are right, Correct formulation of the A* algorithm
However you would only use your professors algorithm if your hueristic was only admissible but not consistent, taken from namin's answer:
[Wikipedia's approach is optimal] if the optimal path to any repeated state is always the first to be followed. This property holds if the heuristic function has the property of consistency (also called monoticity). A heuristic function is consistent if, for every node n and every successor n' of n, the estimated cost of reaching the goal from n is no greater than the step cost of getting to n' from n plus the estimated cost of reaching the goal from n.
and
[Your proffessors version] is optimal if the heuristic function is merely admissible, that is, it never overestimates the cost to reach the goal.
Given that your hueristic is something like euclidean distance in euclidean space (and hence consistent and admissible), you shouldn't be adding anything into the closed set that isn't the smallest cost value for that node. Think about it this way, if any arbitrary node's cost could be invalidated to be smaller that what is in the closed set it would cause all other dependent path steps to have to be re-evaluated, and would ruin run time complexity for this algorithm.
Wikipedia is right in these cases, and its the whole reason you even make the open set a priority queue in the first place. Ideally, you have your open set a PQ, and your closed set as a list that is in some order with relation to which steps you have to take first.
The idea that you need to modify the cost of paths in the open set practically means you need a priority queue with removal, which is possible in log time with hash-table-index-into binary heap structure/pairing heap/Fibonacci heap etc (answers for how to accomplish this are found elsewhere). This actually becomes the hardest part of the implementation of the algorithm IMO, since merely following the algorithm won't actually show you how to accomplish this, and will leave you stuck on figuring out what piece is missing from pseudo implementation.
You will notice that other implementations will talk about delete key/update key operations, these make no sense in the closed set, only the priority queue based open set.