標籤:a演算法 最短路勁 遊戲尋路 尋路 java源碼
偶然看到最短路勁問題,在遊戲、導航等領域都有所應用,覺著挺有意思的,便打算自己也實現一版 。最後選擇了高效簡潔的A*演算法。
A*確實是一個非常優秀的實現,比起迪杰特斯拉、best-first等演算法,這裡省去1萬字的讚美……
A*演算法簡紹可以看該文:
http://blog.csdn.net/pi9nc/article/details/8779503
A*的實現卻並不複雜,關鍵第一點:判斷當前每一步後,下一步怎麼走,一般用一個開集和一個閉集分別來儲存下一步待走的格子 和已經走過的格子;第二點:如何判斷下一步走哪一個格子,這也是A*的優秀之處,它考慮了走過的距離(成本)和預期將要走的距離(期望),擁有快速有效尋路能力;此處再省略1萬字的讚美……
本文稍加改進,用最小堆來儲存下一步可以走的格子,並用倒樹(指結點中僅有指向父結點的指標的樹,姑且讓我這麼說吧)來記錄路勁。
最小堆參看:http://blog.csdn.net/abcd_d_/article/details/40379125
總共四各類:
1、MyCompare.java 是一個介面
2、MinHeap.java 泛型最小堆 , 實現參照了java API中的ArrayList ,代碼可重用
3、Grid.java格子類,用於記錄格子資訊和簡單操作
4、AStar.javaA* 演算法主要邏輯類
上代碼:
package com.study.algorithm;/** * 比較大小的函數介面 * @author zhangshaoliang * 2015-5-7下午12:28:12 */public interface MyCompare {public boolean isLarger(MyCompare m2);public boolean isSmaller(MyCompare m2);public boolean isEqual(MyCompare m2);}
package com.study.algorithm;/** * pojo ,格子 * <pre> F = G + H * G 表示從起點 A 移動到網格上指定方格的移動耗費 (可沿斜方向移動,斜方向的代價為對角線長度) * H 表示從指定的方格移動到終點 B 的預計耗費 (H 有很多計算方法, 這裡我們設定只可以上下左右移動).</pre> * @author zhangshaoliang * 2015-5-7下午1:00:09 */public class Grid implements MyCompare{private double F;private double H;private double G;private int i ;private int j;private Grid parent; ///該格子的父格子/** * pojo ,格子 * @param F F = G + H * @param G 表示從起點 A 移動到網格上指定方格的移動耗費 (可沿斜方向移動,斜方向的代價為對角線長度) * @param H表示從指定的方格移動到終點 B 的預計耗費 (H 有很多計算方法, 這裡我們設定只可以上下左右移動). * @param i 縱座標i * @param j 橫座標j * @param parent 父結點 */public Grid(double F,double G,double H,int i,int j,Grid parent){this.F = F;this.G = G;this.H = H;this.i = i;this.j = j;this.parent = parent;}public Grid(){}public Grid getParent() {return parent;}public void setParent(Grid parent) {this.parent = parent;}public int getI() {return i;}public int getJ() {return j;}public void setI(int i) {this.i = i;}public void setJ(int j) {this.j = j;}/** * 經過當前點到終點B的總耗費 期望值 * @return */public double getF() {return F;}/** * H 表示從指定的方格移動到終點 B 的預計耗費 (H 有很多計算方法, 這裡我們設定只可以上下左右移動) * @return */public double getH() {return H;}/** * 表示從起點 A 移動到當前網格上的移動耗費 (可沿斜方向移動,斜方向的代價為對角線長度) * @return */public double getG() {return G;}public void setF(double f) {F = f;}public void setH(double h) {H = h;}public void setG(double g) {G = g;}@Overridepublic boolean isLarger(MyCompare m2) {// TODO Auto-generated method stubreturn this.F>((Grid)m2).getF();}@Overridepublic boolean isSmaller(MyCompare m2) {// TODO Auto-generated method stubreturn this.F<((Grid)m2).getF();}@Overridepublic boolean isEqual(MyCompare m2) {// TODO Auto-generated method stubreturn this.F==((Grid)m2).getF();}}
package com.study.algorithm;/** * 最小堆 * @author zhangshaoliang * 2015-5-7上午11:08:20 */public class MinHeap<E extends MyCompare> {private int size;private Object[] element;public MinHeap(int maxSize){size = 0;element = new Object[maxSize];}public MinHeap(){this(10);}/** * 元素入堆 * @param e */public void append(E e){ensureCapacity(size+1);element[size++] = e;///put the element to the end of the heapadjustUp(); //adjust the heap to minHeap}/** * 取出堆頂元素(最小元素) * @return */@SuppressWarnings("unchecked")public E poll(){if(isEmpty()){return null;}E min = (E) element[0];element[0] = element[size-1];///replace the min element with the last element element[size-1] = null ;///let gc do its worksize--;adjustDown();///adjust the heap to minHeapreturn min;}/** * 查看堆頂元素(最小元素) * @return */@SuppressWarnings("unchecked")public E peek(){if(isEmpty()){return null;}return (E) element[0];}/** * 是否為空白堆 * @return */public boolean isEmpty(){return size == 0 ;}/** * 確保容量空間足夠 * @param minCapacity */private void ensureCapacity(int minCapacity){int oldCapacity = element.length;if(minCapacity > oldCapacity){int newCapacity = (oldCapacity*3)/2+1;///每次擴容至1.5倍Object[] copy = new Object[newCapacity];///調用本地C方法進行數組複製System.arraycopy(element, 0, copy, 0, element.length);element = copy;}}/** * 向上調整為堆,將小值往上調 */@SuppressWarnings("unchecked")private void adjustUp(){E temp = (E) element[size-1]; ///get the last element int parent = size - 1;while(parent>0&&((E)element[(size - 1)/2]).isLarger(temp)){///if smaller than it parentelement[parent] = element[(parent - 1)/2];parent = (parent - 1)/2;}element[parent] = temp;}/** * 向下調整為堆 */@SuppressWarnings("unchecked")private void adjustDown(){E temp = (E) element[0]; ///get the first element int child = 1;while(child<size){E left = (E) element[child];E right = (E) element[child+1];///這裡的child+1不會越界(想想為什麼)if(right!=null&&left.isLarger(right)){child++;} if(temp.isSmaller((E)element[child])){break; ////如果比兩個孩子中較小者都還小,則結束}element[(child-1)/2] = element[child]; ///assign the smaller to its parentchild = child*2 + 1;}element[(child-1)/2] = temp;}}
package com.study.algorithm;/** * A*尋路演算法 * <pre> * 思路:每次取期望值最小的位置作為下一步要走的位置,F = G + H * G 表示從起點 A 移動到網格上指定方格的移動耗費 (可沿斜方向移動,斜方向的代價為對角線長度). * H 表示從指定的方格移動到終點 B 的預計耗費 (H 有很多計算方法, 這裡我們設定只可以上下左右移動). * * 此處用一個最小堆來記錄開啟列表中的格子,每個格子有一個指向父格子的指標,以此記錄路勁 </pre> * @author zhangshaoliang * 2015-5-7上午10:58:54 */public class AStar {private static MinHeap<Grid> open ;//= new MinHeap<Grid>();//private static MTree close ;//= new MTree();private Grid last; //記錄最後一個格子private final String obstacle = "1";//障礙物標記值private String end = "e";////目標標記值private String start = "s";////開始標記值 //目標座標private int end_i = -1; private int end_j = -1;//開始目標private int start_i = -1;private int start_j = -1;/** * 初始化操作 * @param boxs */public void init(String[][] boxs){for(int i=0;i<boxs.length;i++){for(int j=0;j<boxs[0].length;j++){if(boxs[i][j].equals(start)){start_i = i;start_j = j;}if(boxs[i][j].equals(end)){end_i = i;end_j = j;}}}Grid sGrid = new Grid(0, 0, 0, start_i, start_j, null);open = new MinHeap<Grid>();open.append(sGrid);///、將開始位置加入開集}/** * 開始搜尋 */public void search(String[][] boxs){int height = boxs.length;int width = boxs[0].length;while(open.peek()!=null){//對開集進行遍曆,直到找到目標或者找不到通路Grid g = open.poll();int i = g.getI();int j = g.getJ();double pre_G = g.getG();///已耗費for(int h=-1;h<=1;h++){for(int w=-1;w<=1;w++){int next_i = i + h;///下一個將加入open 集的格子的iint next_j = j + w;///下一個將加入open 集的格子的jif(next_i>=0 && next_i<=height-1 && next_j>=0 && next_j<=width-1){////數組不越界,則進行計算if(boxs[next_i][next_j].equals(obstacle) || boxs[next_i][next_j].equals("-1") ||(h==0&&w==0)){//如果該格子是障礙,或者格子本身,跳過continue;}////計算該點到終點的最短路勁double H = Math.abs(end_i - next_i) + Math.abs(end_j - next_j) ;if(H<1){///找到目標,記錄並結束last = new Grid(0, pre_G, 0, next_i, next_j,g); ;return ;}////如果是對角線則加1.4,否則加1double G = Math.sqrt((next_i-i)*(next_i-i)+(next_j-j)*(next_j-j))>1 ? pre_G+1.4 : pre_G+1;//產生新格子Grid temp = new Grid(H+G, G, H, next_i, next_j,g);////加入open集open.append(temp);boxs[i][j] = "-1";///表示此處已經計算過了}}}last = g;}}/** * 列印路勁 */public void printPath(){if(end_i!=last.getI()||end_j!=last.getJ()){System.out.println("無法到達終點!");return ;}System.out.println("路勁逆序為:");while(true){System.out.print("("+last.getI()+","+last.getJ()+")");last = last.getParent();if(last==null){break;}System.out.print(" <———");}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub/*Grid g1 = new Grid(2, 1, 2, 0, 0,null);Grid g2 = new Grid(5, 1, 2, 0, 0,g1);Grid g3 = new Grid(1, 1, 2, 0, 0,g1);Grid g4 = new Grid(6, 1, 2, 0, 0,g2);Grid g5 = new Grid(3, 1, 2, 0, 0,g3);open = new MinHeap<Grid>();open.append(g1);open.append(g2);open.append(g3);open.append(g4);open.append(g5);//、測試最小堆while(null!=open.peek()){System.out.println(open.poll().getF());}*/String[][] boxs = {//{"0","g"},{"s","0"}};{"0","0","1","0","0"},{"0","0","1","e","0"},{"0","0","1","1","0"},{"0","0","0","1","0"},{"s","0","1","0","0"},};AStar star = new AStar();star.init(boxs);star.search(boxs);star.printPath();}}
輸出結果:
<span style="font-size:18px;">路勁逆序為:(1,3) <———(2,4) <———(3,4) <———(4,3) <———(3,2) <———(3,1) <———(4,0)</span>
A*演算法求最短路徑 java 源碼(拿來即可用)