Android啟發(fā)式尋路

實現(xiàn)的效果圖

Screenshot_20181213-143828.jpeg

思路分析

根據(jù)啟發(fā)算法理論 f(n) = g(n)+h(n); 其中,g(n)表示實際代價(即已經(jīng)走過的路程)宪哩,h(n)代表預(yù)估代價锚沸,由于使用的網(wǎng)格構(gòu)造,所以使用曼哈頓距離公式谓苟,表示h(n)官脓。

代碼實現(xiàn)

數(shù)據(jù)結(jié)構(gòu)(采用單向鏈表)

  public class Node implements Comparable<Node> {
Coord coord;
Node parent;
int g; // G:是個準(zhǔn)確的值,是起點到當(dāng)前結(jié)點的代價
int h; // H:是個估值涝焙,當(dāng)前結(jié)點到目的結(jié)點的估計代價

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;
}

@Override
public int compareTo(@NonNull Node o) {
    return g+h - (o.g+o.h);
}

@Override
public boolean equals(Object obj) {
    Node node = (Node) obj;
    return coord.x == node.coord.x && coord.y == node.coord.y;
}

}

說明:

由于尋路需要通過一個點找附近最近的點卑笨,最終形成一條多段的線段,在數(shù)據(jù)結(jié)構(gòu)中與單向鏈表吻合仑撞,故選擇單向鏈表存儲赤兴。compareTo方法是按f(n)大小排序。

核心代碼思路

1.將起點加入開放列表
2.從開放列表中移除掉其中f(n)最小的一個Node隧哮,并將該節(jié)點加入到關(guān)閉列表
3.從該節(jié)點向周圍擴(kuò)展桶良,如果符合條件,則加入到開放列表
4.重復(fù)23步驟直至終點與2步驟的節(jié)點重合

代碼:

 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();
        //將節(jié)點放入關(guān)閉列表中
        System.out.println(node.coord);
        closeList.add(node);
        //如果取出來的最近的點為終點沮翔,結(jié)束循環(huán)
        if (end.equals(node)) {
            end = node;
            break;
        }
        //將擴(kuò)展的節(jié)點加入開放列表(朝八個方向)
        addNeighborNode(node);

    }
    //把關(guān)閉列表選中
    Node node = end;
    //將數(shù)據(jù)依次放入棧實現(xiàn)鏈表倒序
    Stack<Node> pathStack = new Stack<>();
    while (node != null) {
        pathStack.push(node);
        node = node.parent;
    }
    return pathStack;
}

/**
 * 向八個方向擴(kuò)展添加節(jié)點
 *
 * @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);
}

/**
 * 單獨添加一個相鄰節(jié)點
 *
 * @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;
}

/**
 * 能否添加進(jìn)開放列表
 *
 * @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;
    }
    //如果節(jié)點在地圖上位墻壁 false
    int mapPoint = map[y][x];
    if ( mapPoint == MapUtils.WALL) {
        return false;
    }
    //如果節(jié)點在開放列表或者關(guān)閉列表中 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;

@Override
protected 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="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/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();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (touchFlag >= 2) {
        return super.onTouchEvent(event);
    }
    float x = event.getX();
    float y = event.getY();
    //每格地圖大小為80*80,注意:數(shù)組和屏幕坐標(biāo)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);
}

@Override
protected 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);
}
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陨帆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子采蚀,更是在濱河造成了極大的恐慌疲牵,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆鼠,死亡現(xiàn)場離奇詭異瑰步,居然都是意外死亡,警方通過查閱死者的電腦和手機璧眠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人责静,你說我怎么就攤上這事袁滥。” “怎么了灾螃?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵题翻,是天一觀的道長。 經(jīng)常有香客問我腰鬼,道長嵌赠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任熄赡,我火速辦了婚禮姜挺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彼硫。我一直安慰自己炊豪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布拧篮。 她就那樣靜靜地躺著词渤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪串绩。 梳的紋絲不亂的頭發(fā)上缺虐,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音礁凡,去河邊找鬼高氮。 笑死,一個胖子當(dāng)著我的面吹牛把篓,可吹牛的內(nèi)容都是我干的纫溃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼韧掩,長吁一口氣:“原來是場噩夢啊……” “哼紊浩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疗锐,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坊谁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滑臊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體口芍,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年雇卷,在試婚紗的時候發(fā)現(xiàn)自己被綠了鬓椭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠猴。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖小染,靈堂內(nèi)的尸體忽然破棺而出翘瓮,到底是詐尸還是另有隱情,我是刑警寧澤裤翩,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布资盅,位于F島的核電站,受9級特大地震影響踊赠,放射性物質(zhì)發(fā)生泄漏呵扛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一筐带、第九天 我趴在偏房一處隱蔽的房頂上張望今穿。 院中可真熱鬧,春花似錦烫堤、人聲如沸荣赶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拔创。三九已至,卻和暖如春富蓄,著一層夾襖步出監(jiān)牢的瞬間剩燥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工立倍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灭红,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓口注,卻偏偏與公主長得像变擒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寝志,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容