Java開源-astar:A 星算法

Iverson76er 7年前發布 | 32K 次閱讀 算法 Java Java開發

astar

A星算法Java實現

一、適用場景

在一張地圖中,繪制從起點移動到終點的最優路徑,地圖中會有障礙物,必須繞開障礙物。

二、算法思路

1. 回溯法得到路徑

(如果有路徑)采用“結點與結點的父節點”的關系從最終結點回溯到起點,得到路徑。

2. 路徑代價的估算:F = G+H

A星算法的代價計算使用了被稱作是啟發式的代價函數。 先說明一下各符號意義:G表示的是 從起點到當前結點的實際路徑代價 (為啥叫實際?就是已經走過了,邊走邊將代價計算好了);H表示 當前結點到達最終結點的估計代價 (為啥叫估計?就是還沒走過,不知道前面有沒障礙、路通不通,所以只能用估計);F表示 當前結點所在路徑從起點到最終點預估的總路徑代價

G的計算方式:計算方式有挺多種的,這里我們就用這種吧,假設每個結點代表一個正方形,橫豎移動距離:斜移動距離=1:1.4(根號2),我們取個整數10和14吧,也就是說當前結點G值=父節點的G+(10或14)。

H的計算方式:估價計算也有很多種方式,我們這里使用“曼哈頓”法,H=|當前結點x值-最終結點x值|+|當前結點y值-最終結點y值|("||"表示絕對值)。

如下圖(圖不是自己做的,從網上借來的,自己畫的話~...慘不忍睹!)

3. 輔助表:Open、Close列表

在A星算法中,需要使用兩個輔助表來記錄結點。 一個用于 記錄可被訪問的結點 ,成為Open表;一個是 記錄已訪問過的結點 ,稱為Close表。 這兩個表決定了算法的結束:條件是最終結點在Close表中(找到路徑)或Open表為空(找不到了路徑)。

4. 移動結點、相鄰結點的處理

上面的理解的話,現在就來移動當前的節點,尋找路徑。

每次從Open表中取出F值最小的結點出來( 這里我們使用優先隊列來處理比較好 ),作為當前結點;然后將當前結點的所有鄰結點按照 鄰結點規則 加入到Open表中;最后將當前結點放入Close表中,這里就是每次循環的執行內容。

鄰結點規則: (1) 當鄰結點不在地圖中,不加入Open表; (2) 當鄰結點是障礙物,不加入Open表; (3) 當鄰結點在Close表中,不加入Open表; (4) 當鄰結點不在Open中,加入Open表, 設該鄰結點的父節點為當前結點 ; (5) **當鄰結點在Open表中,我們需要做個比較:如果鄰結點的G值>當前結點的G值+當前結點到這個鄰結點的代價,那么修改該鄰結點的父節點為當前的結點(因為在Open表中的結點除了起點,都會有父節點),修改G值=當前結點的G值+當前結點到這個鄰結點的代價 **

藍色框框表示在Close表中,綠色的框框表示在Open表中

最后回溯得到路徑

三、代碼實現(Java)

1. 輸入

(1) 代表地圖二值二維數組(0表示可通路,1表示路障)

int[][] maps = {
                { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
                { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
                { 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0 },
                { 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
                { 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
                { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
                { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }
                };

(2) 按照二維數組的特點,坐標原點在左上角,所以y是高,x是寬,y向下遞增,x向右遞增,我們將x和y封裝成一個類,好傳參,重寫equals方法比較坐標(x,y)是不是同一個。

public class Coord
{
    public int x;
    public int y;

public Coord(int x, int y)
{
    this.x = x;
    this.y = y;
}

@Override
public boolean equals(Object obj)
{
    if (obj == null) return false;
    if (obj instanceof Coord)
    {
        Coord c = (Coord) obj;
        return x == c.x && y == c.y;
    }
    return false;
}

}</code></pre>

(3) 封裝路徑結點類,字段包括:坐標、G值、F值、父結點,實現Comparable接口,方便優先隊列排序。

public class Node implements Comparable<Node>
{

public Coord coord; // 坐標
public Node parent; // 父結點
public int G; // G:是個準確的值,是起點到當前結點的代價
public int H; // H:是個估值,當前結點到目的結點的估計代價

public Node(int x, int y)
{
    this.coord = new Coord(x, y);
}

public Node(Coord coord, Node parent, int g, int h)
{
    this.coord = coord;
    this.parent = parent;
    G = g;
    H = h;
}

@Override
public int compareTo(Node o)
{
    if (o == null) return -1;
    if (G + H > o.G + o.H)
        return 1;
    else if (G + H < o.G + o.H) return -1;
    return 0;
}

}</code></pre>

(4) 最后一個數據結構是A星算法輸入的所有數據,封裝在一起,傳參方便。 :grin:

public class MapInfo
{
    public int[][] maps; // 二維數組的地圖
    public int width; // 地圖的寬
    public int hight; // 地圖的高
    public Node start; // 起始結點
    public Node end; // 最終結點

public MapInfo(int[][] maps, int width, int hight, Node start, Node end)
{
    this.maps = maps;
    this.width = width;
    this.hight = hight;
    this.start = start;
    this.end = end;
}

}</code></pre>

2. 處理

(1) 在算法里需要定義幾個常量來確定:二維數組中哪個值表示障礙物、二維數組中繪制路徑的代表值、計算G值需要的橫縱移動代價和斜移動代價。

public final static int BAR = 1; // 障礙值
    public final static int PATH = 2; // 路徑
    public final static int DIRECT_VALUE = 10; // 橫豎移動代價
    public final static int OBLIQUE_VALUE = 14; // 斜移動代價

(2) 定義兩個輔助表:Open表和Close表。Open表的使用是需要取最小值,在這里我們使用Java工具包中的優先隊列PriorityQueue,Close只是用來保存結點,沒其他特殊用途,就用ArrayList。

Queue<Node> openList = new PriorityQueue<Node>(); // 優先隊列(升序)
    List<Node> closeList = new ArrayList<Node>();

(3) 定義幾個布爾判斷方法:最終結點的判斷、結點能否加入open表的判斷、結點是否在Close表中的判斷。

/**

 * 判斷結點是否是最終結點
 */
private boolean isEndNode(Coord end,Coord coord)
{
    return coord != null && end.equals(coord);
}

/**
 * 判斷結點能否放入Open列表
 */
private boolean canAddNodeToOpen(MapInfo mapInfo,int x, int y)
{
    // 是否在地圖中
    if (x < 0 || x >= mapInfo.width || y < 0 || y >= mapInfo.hight) return false;
    // 判斷是否是不可通過的結點
    if (mapInfo.maps[y][x] == BAR) return false;
    // 判斷結點是否存在close表
    if (isCoordInClose(x, y)) return false;

    return true;
}

/**
 * 判斷坐標是否在close表中
 */
private boolean isCoordInClose(Coord coord)
{
    return coord!=null&&isCoordInClose(coord.x, coord.y);
}

/**
 * 判斷坐標是否在close表中
 */
private boolean isCoordInClose(int x, int y)
{
    if (closeList.isEmpty()) return false;
    for (Node node : closeList)
    {
        if (node.coord.x == x && node.coord.y == y)
        {
            return true;
        }
    }
    return false;
}</code></pre> 

(4) 計算H值,“曼哈頓” 法,坐標分別取差值相加

private int calcH(Coord end,Coord coord)
{
    return Math.abs(end.x - coord.x) + Math.abs(end.y - coord.y);
}

(5) 從Open列表中查找結點

private Node findNodeInOpen(Coord coord)
{
    if (coord == null || openList.isEmpty()) return null;
    for (Node node : openList)
    {
        if (node.coord.equals(coord))
        {
            return node;
        }
    }
    return null;
}

(6) 添加鄰結點到Open表

/**
 * 添加所有鄰結點到open表
 */
private void addNeighborNodeInOpen(MapInfo mapInfo,Node current)
{
    int x = current.coord.x;
    int y = current.coord.y;
    // 左
    addNeighborNodeInOpen(mapInfo,current, x - 1, y, DIRECT_VALUE);
    // 上
    addNeighborNodeInOpen(mapInfo,current, x, y - 1, DIRECT_VALUE);
    // 右
    addNeighborNodeInOpen(mapInfo,current, x + 1, y, DIRECT_VALUE);
    // 下
    addNeighborNodeInOpen(mapInfo,current, x, y + 1, DIRECT_VALUE);
    // 左上
    addNeighborNodeInOpen(mapInfo,current, x - 1, y - 1, OBLIQUE_VALUE);
    // 右上
    addNeighborNodeInOpen(mapInfo,current, x + 1, y - 1, OBLIQUE_VALUE);
    // 右下
    addNeighborNodeInOpen(mapInfo,current, x + 1, y + 1, OBLIQUE_VALUE);
    // 左下
    addNeighborNodeInOpen(mapInfo,current, x - 1, y + 1, OBLIQUE_VALUE);
}

/**
 * 添加一個鄰結點到open表
 */
private void addNeighborNodeInOpen(MapInfo mapInfo,Node current, int x, int y, int value)
{
    if (canAddNodeToOpen(mapInfo,x, y))
    {
        Node end=mapInfo.end;
        Coord coord = new Coord(x, y);
        int G = current.G + value; // 計算鄰結點的G值
        Node child = findNodeInOpen(coord);
        if (child == null)
        {
            int H=calcH(end.coord,coord); // 計算H值
            if(isEndNode(end.coord,coord))
            {
                child=end;
                child.parent=current;
                child.G=G;
                child.H=H;
            }
            else
            {
                child = new Node(coord, current, G, H);
            }
            openList.add(child);
        }
        else if (child.G > G)
        {
            child.G = G;
            child.parent = current;
            // 重新調整堆
            openList.add(child);
        }
    }
}

(7) 回溯法繪制路徑

private void drawPath(int[][] maps, Node end)
{
    if(end==null||maps==null) return;
    System.out.println("總代價:" + end.G);
    while (end != null)
    {
        Coord c = end.coord;
        maps[c.y][c.x] = PATH;
        end = end.parent;
    }
}

(8) 開始算法,循環移動結點尋找路徑,設定循環結束條件,Open表為空或者最終結點在Close表

public void start(MapInfo mapInfo)
{
    if(mapInfo==null) return;
    // clean
    openList.clear();
    closeList.clear();
    // 開始搜索
    openList.add(mapInfo.start);
    moveNodes(mapInfo);
}

/**
 * 移動當前結點
 */
private void moveNodes(MapInfo mapInfo)
{
    while (!openList.isEmpty())
    {
        if (isCoordInClose(mapInfo.end.coord))
        {
            drawPath(mapInfo.maps, mapInfo.end);
            break;
        }
        Node current = openList.poll();
        closeList.add(current);
        addNeighborNodeInOpen(mapInfo,current);
    }
}

 

項目主頁:http://www.baiduhome.net/lib/view/home/1499763020937

 本文由用戶 Iverson76er 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!