A * search algorithm

Keywords: Java

Reference resources:
Detailed explanation of A-star algorithm (the most detailed and easy to understand version in my opinion)
The principle of shortest path A * algorithm and java code implementation (it's my failure to understand)

Algorithm Introduction
A * search algorithm combines the advantages of Dijkstra algorithm based on breadth search and BFS best priority search algorithm based on greedy idea to design the shortest path algorithm, which can be used in the graph algorithm with obstacles. The idea is to add the neighboring nodes of the node to the queue with processing first, but not in turn, but through an evaluation function, according to which the nodes in the queue with processing priority are processed. In this way, we can find the end point more quickly.

Algorithmic thought
First of all, nodes should be classified into passable nodes, non passable nodes, start nodes and end nodes. Then the node should also contain a pointer to the parent node.
Starting from the starting point, first search the nodes adjacent to the starting point and add them to the open list queue to be checked. Then enter the loop, take a point from the openlist, take out the principle later, and add the node to the closeList. Since there is only a starting point, the starting point is taken out. Then the points connected with the starting point (not including the barrier points and the points in the closeList) are added to the openlist, and during the adding process, the evaluation function value f of each point is calculated, which is the basis for the principle of taking points from the openlist mentioned earlier (taking the point with the lowest f). F=G+H, where G is the total cost from the starting node to the point, and H is the predicted cost from the point to the end. In the process of joining a point to an openlist, if the parent node of the point is empty, the parent node of the joining point should also be specified as the current node; if the parent node is not empty (indicating that the join point is already in the openlist, at this time, it is necessary to update the state of the join point), then compare the g value from the current node to the join point or the existing g value with the join point, if so If the former is large, the g value and the parent node with join point will be updated. Otherwise, the state of the parent node with join point will not be changed. Loop through the above operations until the end point is added to the openlist. Finally, find the start node in turn according to the parent node pointer of the end point.

Algorithm pseudocode
main(){
openList.add(startNode)
while(!openList.isEmpty()){
node = openList.getMinFNode(); //opt1
closeList.add(node);
if(node==endNode){
isFind = true;
break;
}
addAdjacentNode(node); //opt2
}
getTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
addAdjacentNode(curNode){
nodeList = getAdjacentNode(curNode); //opt3
for(node belong nodeList){
calculate of update F of node; //opt4
openList.add(node); //opt5
}
}
1
2
3
4
5
6
7
optimization
opt1
The getMinFNode method can use the priority queue method to get the minimum F quickly. But there is a problem in priority queue, that is, it is unable to update the nodes in openList at opt5 quickly. Therefore, it is better to implement all the data structures with HashSet.

opt2
When inserting neighbor nodes into openList, we need to do a series of work, such as finding out the list of neighbor nodes, calculating the f value of each neighbor node (including calculation H and calculation / update G), adding the nodes to openList.

opt3
In the following specific implementation code, we take the form of a square node, so we can use the 8 directions of up, down, left and right diagonal to search, to ensure that we will not miss. However, if the tree structure of the graph, we can also use the way of traversing the node's adjacency list to get a series of adjacency nodes.

opt4
The calculation of F value is mainly to calculate g (the calculation of G value is mainly based on the idea of Dijkstra algorithm). If the node is not in the openList, the g value will be calculated directly and the parent node will be updated; if the node is in the openList, the g value will be calculated before. At this time, the g value from the last node to the node will be calculated and compared with the original g value. If the new g value is small, the g value will be calculated Reorient the parent node of the point to the lastNode with the new g value. In our example below, the G value defaults to a horizontal vertical cost of 10 and a diagonal cost of 14. However, if the tree graph is used, it should be calculated according to the weight between nodes (Dijkstra algorithm).
The calculation of H value adopts the best priority search method. In the following example, we use the Manhattan distance ignoring the roadblocks to estimate. Of course, we can also use the European distance, non European distance and other methods according to the situation. I am not clear about the H calculation of the tree graph, which is why I do not intend to use the A-star search algorithm in the tree graph (the number of connections between nodes is uncertain).

opt5
As mentioned in opt1, to use priority queue, we need to solve the problem of how to ensure that the join point is not repeated and the minimum f can be updated in time. That's why I used HashSet instead of priority queue (although it's slow to traverse in opt1 to get the minimum value f)

Algorithm implementation
Node.java
public class Node {
int f;
int g;
int h;
Node father;
Node lastNode;

int x;
int y;

boolean isBarrier;         //Enumeration class can be used
boolean isStart;
boolean isEnd;
boolean isTrace;

public Node() {

}

public Node(int i, int j) {
    x = i;
    y = j;
}

public static Node getMaxFNode() {
    Node node = new Node();
    node.f = Integer.MAX_VALUE;
    return node;
}

@Override
public String toString() {             //Ensure HashSet works

    if(isBarrier)
        return "#";
    else if(isStart)
        return "@";
    else if(isEnd)
        return "$";
    else if(isTrace)
        return "o";
    else 
        return "*";
}

@Override
public int hashCode() {
    final int prime = 31;  
    int result = 1;  
    result = prime * result + x;  
    result = prime * result + y;  
    return result;  
}

@Override  
public boolean equals(Object obj) {  
    if (this == obj)  
        return true;  
    if (obj == null)  
        return false;  
    if (getClass() != obj.getClass())  
        return false;  
    Node other = (Node) obj;  
    if (x != other.x)  
        return false;  
    if (y != other.y)  
        return false;  
    return true;  
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
AStar.java
import java.util.ArrayList;
import java.util.HashSet;

public class AStar {
private Node[][] map;
private int xBound;
private int yBound;
private Node startNode;
private Node endNode;
private HashSet openList;
private ArrayList closeList;

public AStar(Node[][] map, Node sNode, Node eNode) {
    this.map = map;
    xBound = map.length-1;
    yBound = map[0].length-1;
    startNode = sNode;
    endNode = eNode;
    openList = new HashSet<>();
    closeList = new ArrayList<>();
}

public boolean find() {
    boolean isFind = false;
    openList.add(startNode);
    while(!openList.isEmpty()) {
        Node fNode = Node.getMaxFNode();                    // the node of minimum f
        for(Node node : openList) {
            fNode = fNode.f<node.f ? fNode : node;
        }
        openList.remove(fNode);
        closeList.add(fNode);
        checkAdjacent(fNode);
        if(fNode.isEnd) {
            isFind = true;
            break;
        }   
    }
    setTrace();
    return isFind;
}

private void checkAdjacent(Node curNode) {
    ArrayList<Node> nodeList = getAdjacentNode(curNode);

    for(Node node : nodeList) {
        setG(node);
        setH(node);
        node.f = node.g + node.h;
        openList.add(node);
    }
}

private ArrayList<Node> getAdjacentNode(Node curNode) {
    ArrayList<Node> nodeList = new ArrayList<>();

    int x = curNode.x;
    int y = curNode.y;

    for(int i=-1; i<=1; i++) {
        for(int j=-1; j<=1; j++) {
            if(i==0 && j==0)
                continue;
            if(x+i>=0 && x+i<=xBound && y+j>=0 && y+j<=yBound
                    && map[x+i][y+j].isBarrier!=true && map[x+i][y+j].isStart!=true
                    && closeList.contains(map[x+i][y+j])!=true) {
                Node node = map[x+i][y+j];
                node.lastNode = curNode;
                nodeList.add(node);
            }
        }
    }
    return nodeList;
}

private void setG(Node node) {
    int deltaG;
    if(Math.abs(node.lastNode.x-node.x)==1
            && Math.abs(node.lastNode.y-node.y)==1)
        deltaG = 14;
    else
        deltaG = 10;

    int tempG = node.lastNode.g + deltaG;
    if(node.g==0 || node.g > tempG) {
        node.g = tempG;
        node.father = node.lastNode;
    }
}

private void setH(Node node) {
    if(node.h==0)
        node.h = 10*(Math.abs(node.lastNode.x-node.x)+Math.abs(node.lastNode.y-node.y));
}

private void setTrace() {
    Node node = endNode;
    while(node!=startNode) {
        node.isTrace = true;
        node = node.father;
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
Test.java
public class Test {
Node[][] map;
Node startNode;
Node endNode;

public static void main(String[] args) {
    // TODO Auto-generated method stub
    Test test = new Test();
    test.print();

    AStar aStar = new AStar(test.map, test.startNode, test.endNode);
    if(aStar.find()) {
        System.out.println("I find it!");
        test.print();
    }
    else {
        System.out.println("failure!");
    }

}

public Test() {
    int row = 10;
    int rank = 10;
    map = new Node[row][];
    for(int i=0; i<map.length; i++) {
        map[i] = new Node[rank];
        for(int j=0; j<map[0].length; j++) {
            map[i][j] = new Node(i, j);
        }
    }
    map[5][4].isStart = true;
    startNode = map[5][4];
    map[6][6].isEnd = true;
    endNode = map[6][6];
    for(int i=3; i<=7; i++)
        map[i][5].isBarrier = true;
    map[7][3].isBarrier = true;
    map[7][4].isBarrier = true;
    map[3][4].isBarrier = true;
}

public void print() {
    for(Node[] row : map) {
        for(Node node : row) {
            System.out.print(node + " ");
        }
        System.out.println();
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
--------
Copyright notice: This is the original article of CSDN blogger "time magician", following CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.
Original link: https://blog.csdn.net/TimeMagician/article/details/80847359

Published 2 original articles, won 0 praise and 1 visit
Private letter follow

Posted by gogul on Thu, 20 Feb 2020 23:32:31 -0800