Manhattan Heuristic function for A-star (A*)

Shawn Mclean picture Shawn Mclean · Dec 26, 2010 · Viewed 8.1k times · Source

I found this algorithm here.

I have a problem, I cant seem to understand how to set up and pass my heuristic function.

    static public Path<TNode> AStar<TNode>(TNode start, TNode destination,
        Func<TNode, TNode, double> distance,
        Func<TNode, double> estimate) where TNode : IHasNeighbours<TNode>
    {
        var closed = new HashSet<TNode>();
        var queue = new PriorityQueue<double, Path<TNode>>();
        queue.Enqueue(0, new Path<TNode>(start));
        while (!queue.IsEmpty)
        {
            var path = queue.Dequeue();
            if (closed.Contains(path.LastStep))
                continue;
            if (path.LastStep.Equals(destination))
                return path;
            closed.Add(path.LastStep);
            foreach (TNode n in path.LastStep.Neighbours)
            {
                double d = distance(path.LastStep, n);
                var newPath = path.AddStep(n, d);
                queue.Enqueue(newPath.TotalCost + estimate(n), newPath);
            }
        }
        return null;
    }

As you can see, it accepts 2 functions, a distance and a estimate function.

Using the Manhattan Heuristic Distance function, I need to take 2 parameters. Do I need to modify his source and change it to accepting 2 parameters of TNode so I can pass a Manhattan estimate to it? This means the 4th param will look like this:

Func<TNode, TNode, double> estimate) where TNode : IHasNeighbours<TNode>

and change the estimate function to:

queue.Enqueue(newPath.TotalCost + estimate(n, path.LastStep), newPath);

My Manhattan function is:

    private float manhattanHeuristic(Vector3 newNode, Vector3 end)
    {
        return (Math.Abs(newNode.X - end.X) + Math.Abs(newNode.Y - end.Y));
    }

Answer

Eric Lippert picture Eric Lippert · Dec 26, 2010

Good question. I agree that the article was confusing. I've updated it to address your question.

First, to answer the question you asked: should you modify the code given to take a different function? If you want, sure, but you certainly don't have to. My advice is to pass the function that the algorithm wants, because that's the function that it needs. Why pass information that the algorithm doesn't need?

How do to that?

The A* algorithm I give takes two functions.

The first function gives the exact distance between two given neighbouring nodes.

The second function gives the estimated distance between a given node and the destination node.

It is the second function that you don't have.

If you have a function that gives the estimated distance between two given nodes and you need a function that gives the estimated distance between a given node and the destination node then just build that function:

Func<Node, Node, double> estimatedDistanceBetweenTwoNodes = whatever;
Func<Node, double> estimatedDistanceToDestination = n=>estimatedDistanceBetweenTwoNodes(n, destination);

And you're done. Now you have the function you need.

This technique of turning a two-parameter function into a one-parameter function by fixing one of the parameters to a certain value is called "partial function application" and it is extremely common in functional programming.

Is that all clear?

Now on to the second and much more serious problem. As I described in my articles, the correct operation of the algorithm is predicated on the estimation function being conservative. Can you guarantee that the Manhattan distance never overestimates? That seems unlikely. If there is a "diagonal" street anywhere in the grid then the Manhattan distance overestimates the optimal distance between two points, and the A* algorithm will not find it. Most people use the Euclidean distance (aka the L2 norm) for the A* algorithm because the shortest distance between two points is by definition not an overestimate. Why are you using the Manhattan distance? I am very confused as to why you think this is a good idea.