Android啟發式尋路

wangsys發表於2021-09-09

實現的效果圖

圖片描述

Screenshot_20181213-143828.jpeg

思路分析

根據啟發演算法理論 f(n) = g(n)+h(n); 其中,g(n)表示實際代價(即已經走過的路程),h(n)代表預估代價,由於使用的網格構造,所以使用曼哈頓距離公式,表示h(n)。

程式碼實現

資料結構(採用單向連結串列)

  public class Node implements Comparable<Node> {
Coord coord;
Node parent;int g; // G:是個準確的值,是起點到當前結點的代價int h; // H:是個估值,當前結點到目的結點的估計代價public Node(int x, int y) {    this.coord = new Coord(x,y);
}public Node(int x, int y, Node parent, int h, int g) {    this.coord = new Coord(x,y);    this.parent = parent;    this.h = h;    this.g = g;
}public Node getParent() {    return parent;
}public void setParent(Node parent) {    this.parent = parent;
}public int getH() {    return h;
}public void setH(int h) {    this.h = h;
}public int getG() {    return g;
}public void setG(int g) {    this.g = g;
}@Overridepublic int compareTo(@NonNull Node o) {    return g+h - (o.g+o.h);
}@Overridepublic boolean equals(Object obj) {
    Node node = (Node) obj;    return coord.x == node.coord.x && coord.y == node.coord.y;
}

}

說明:

由於尋路需要透過一個點找附近最近的點,最終形成一條多段的線段,在資料結構中與單向連結串列吻合,故選擇單向連結串列儲存。compareTo方法是按f(n)大小排序。

核心程式碼思路

1.將起點加入開放列表
2.從開放列表中移除掉其中f(n)最小的一個Node,並將該節點加入到關閉列表
3.從該節點向周圍擴充套件,如果符合條件,則加入到開放列表
4.重複23步驟直至終點與2步驟的節點重合

程式碼:

 public class Astart {
   private int[][] map;   private int width;   private int height;    private Node start;   private Node end;   private PriorityQueue<Node> openList = new PriorityQueue<>();   private List<Node> closeList = new ArrayList<>();   public Astart(@NonNull int[][] map, @NonNull Node start, @NonNull Node end)     
    {    this.map = map;    this.start = start;    this.end = end;
    height = map.length;
    width = map[0].length;
  }public Stack<Node> start() {    //將起點放入開放列表
    openList.add(start);    while (!openList.isEmpty()) {        //將曼哈頓距離最近的點取出
        Node node = openList.poll();        //將節點放入關閉列表中
        System.out.println(node.coord);
        closeList.add(node);        //如果取出來的最近的點為終點,結束迴圈
        if (end.equals(node)) {
            end = node;            break;
        }        //將擴充套件的節點加入開放列表(朝八個方向)
        addNeighborNode(node);

    }    //把關閉列表選中
    Node node = end;    //將資料依次放入棧實現連結串列倒序
    Stack<Node> pathStack = new Stack<>();    while (node != null) {
        pathStack.push(node);
        node = node.parent;
    }    return pathStack;
}/**
 * 向八個方向擴充套件新增節點
 *
 * @param node
 */private void addNeighborNode(Node node) {
    Node left = new Node(node.coord.x - 1, node.coord.y);
    left.setParent(node);
    left.setG(generateG(left));
    left.setH(generateH(left));
    Node right = new Node(node.coord.x + 1, node.coord.y);
    right.setParent(node);
    right.setG(generateG(right));
    right.setH(generateH(right));
    Node top = new Node(node.coord.x, node.coord.y - 1);
    top.setParent(node);
    top.setG(generateG(top));
    top.setH(generateH(top));
    Node bottom = new Node(node.coord.x, node.coord.y + 1);
    bottom.setParent(node);
    bottom.setG(generateG(bottom));
    bottom.setH(generateH(bottom));
    Node leftTop = new Node(node.coord.x - 1, node.coord.y - 1);
    leftTop.setParent(node);
    leftTop.setG(generateG(leftTop));
    leftTop.setH(generateH(leftTop));
    Node leftBottom = new Node(node.coord.x - 1, node.coord.y +1);
    leftBottom.setParent(node);
    leftBottom.setG(generateG(leftBottom));
    leftBottom.setH(generateH(leftBottom));
    Node rightTop = new Node(node.coord.x + 1, node.coord.y - 1);
    rightTop.setParent(node);
    rightTop.setG(generateG(rightTop));
    rightTop.setH(generateH(rightTop));
    Node rightBottom = new Node(node.coord.x + 1, node.coord.y + 1);
    rightBottom.setParent(node);
    rightBottom.setG(generateG(rightBottom));
    rightBottom.setH(generateH(rightBottom));
    addNeighborSingleNode(left);
    addNeighborSingleNode(right);
    addNeighborSingleNode(top);
    addNeighborSingleNode(bottom);    //        addNeighborSingleNode(leftTop);
    //        addNeighborSingleNode(leftBottom);
    //        addNeighborSingleNode(rightTop);
    //        addNeighborSingleNode(rightBottom);}/**
 * 單獨新增一個相鄰節點
 *
 * @param node
 */private void addNeighborSingleNode(Node node) {    if (canAdd(node)) {
        openList.add(node);
    }
}private int generateG(Node node) {    if (node.parent != null) {
        Node parent = node.parent;        int c = (Math.abs(start.coord.x - node.coord.x) + Math.abs(start.coord.y - node.coord.y))*100;        return parent.h + c;
    }    return 0;
}private int generateH(Node node) {    return Math.abs(end.coord.x - node.coord.x) + Math.abs(end.coord.y - node.coord.y)*100;
}/**
 * 能否新增進開放列表
 *
 * @param node
 * @return
 */private boolean canAdd(Node node) {    if (node == null || node.coord == null) {        return false;
    }    int x = node.coord.x;    int y = node.coord.y;    //如果超過邊界 false
    if (x < 0 || x > width - 1) {        return false;
    }    if (y < 0 || y > height - 1) {        return false;
    }    //如果節點在地圖上位牆壁 false
    int mapPoint = map[y][x];    if ( mapPoint == MapUtils.WALL) {        return false;
    }    //如果節點在開放列表或者關閉列表中 false
    if (openList.contains(node)) {        return false;
    }    if (closeList.contains(node)) {        return false;
    }    return true;
}

介面程式碼

public class MainActivity extends AppCompatActivity {private Node start;private Node end;private ShowMapView showMapView;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    showMapView = findViewById(R.id.show);
    showMapView.init(MapUtils.map, new ShowMapView.OnTouchListener() {        @Override
        public void onTouchPoint(int type, int row, int col) {            if (type == 1) {
                start = new Node(col, row);
            } else {
                end = new Node(col, row);
            }
        }
    });
}private void findWayAndDraw() {    if (start != null && end != null) {
        Stack<Node> pathList = new Astart(MapUtils.map, start, end).start();
        Path path = new Path();
        path.moveTo(start.coord.x * 80 + 40, start.coord.y * 80 + 40);        while (!pathList.empty()) {
            Node node = pathList.pop();
            path.lineTo(node.coord.x * 80 + 40, node.coord.y * 80 + 40);
        }
        showMapView.setPath(path);
    }


}public void cal(View view) {
    findWayAndDraw();
}public void reset(View view) {
    showMapView.reset();
}
}

佈局檔案

<RelativeLayout xmlns:android=""xmlns:app=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.example.n011210.findway.ShowMapView
    android:id="@+id/show"
    android:layout_width="match_parent"
    android:layout_height="match_parent" /><Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:onClick="cal"
    android:text="計算" /><Button
    android:id="@+id/reset"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@id/btn"
    android:onClick="reset"
    android:text="重新整理" />


 </RelativeLayout>

說明

ShowMapView為地圖控制元件

   public class ShowMapView extends View {
   private int touchFlag;   private int[][] map;   private Path path;     private OnTouchListener listener;  public ShowMapView(Context context) {
      super(context);
  }public ShowMapView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}public ShowMapView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}public void init(int[][] map, OnTouchListener listener) {    this.map = map;    this.listener = listener;
    invalidate();
}public void setPath(Path path) {    this.path = path;
    invalidate();
}public void reset() {
    touchFlag = 0;    for (int i = 0; i < map.length; i++) {        for (int j = 0; j < map[i].length; j++) {            if (map[i][j] == 2) {                map[i][j] = 0;
            }
        }
    }
    path.reset();
    invalidate();
}

@Overridepublic boolean onTouchEvent(MotionEvent event) {    if (touchFlag >= 2) {        return super.onTouchEvent(event);
    }    float x = event.getX();    float y = event.getY();    //每格地圖大小為80*80,注意:陣列和螢幕座標X和Y相反
    int row = (int) y / 80;    int col = (int) x / 80;    if (map[row][col] == 0) {
        touchFlag++;        if (listener != null) {
            listener.onTouchPoint(touchFlag, row, col);
        }        map[row][col] = 2;
    }    this.invalidate();    return super.onTouchEvent(event);
}

@Overrideprotected void onDraw(Canvas canvas) {
    super.onDraw(canvas);    if (map == null) {        return;
    }
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(Color.BLUE);
    paint.setStrokeWidth(5);
    paint.setStyle(Paint.Style.STROKE);    for (int i = 0; i < map.length; i++) {        for (int j = 0; j < map[i].length; j++) {            if (map[i][j] == 0) {
                Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.route);
                canvas.drawBitmap(bm, j * 80, i * 80, paint);
            } else if (map[i][j] == 1) {
                Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.wall);
                canvas.drawBitmap(bm, j * 80, i * 80, paint);
            } else if (map[i][j] == 2) {
                Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.path);
                canvas.drawBitmap(bm, j * 80, i * 80, paint);
            }
        }
    }    if (path != null) {
        canvas.drawPath(path, paint);
    }

}public interface OnTouchListener {    void onTouchPoint(int type, int row, int col);
}
}



作者:夜亦明
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3244/viewspace-2821636/,如需轉載,請註明出處,否則將追究法律責任。

相關文章