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;
	}
	
	@Override
	public boolean isLarger(MyCompare m2) {
		// TODO Auto-generated method stub
		return this.F>((Grid)m2).getF();
	}
	@Override
	public boolean isSmaller(MyCompare m2) {
		// TODO Auto-generated method stub
		return this.F<((Grid)m2).getF();
	}
	@Override
	public boolean isEqual(MyCompare m2) {
		// TODO Auto-generated method stub
		return 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 heap
		
		adjustUp(); //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 work
		size--;
		
		adjustDown();///adjust the heap to minHeap
		
		return 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 parent
			element[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 parent
			child = 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 集的格子的i
					int next_j = j + w;///下一個將加入open 集的格子的j
					
					if(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,否則加1
						double 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>








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章