1. 程式人生 > >BFS廣度優先遍歷尋找最短路徑(超詳細實現過程)

BFS廣度優先遍歷尋找最短路徑(超詳細實現過程)

廣度優先遍歷尋找最短路徑

      最近一直想搞A*演算法,發現有部分沒理解清楚。於是找到了廣度優先遍歷尋路演算法學習了下,想看看可不可以對寫A*有什麼幫助。廣度優先遍歷尋路演算法本身並不難,概括來說就是像雷達一樣,一層一層進行尋找目標點。當找到目標點後進行回溯。從而找到最佳路徑。也就是說每走一步都要找到到達該點的最短的路徑,最終得到到達所有點的最短路徑。

廢話不多說上程式碼。具體解釋在程式碼後面

Point.java

public class Point {
	private int x;
	private int y;
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	public Point(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	
}

MyMap.java

public class MyMap {
	//記錄上一個父親位置
	private int preX;
	private int preY;
	//記錄權值
	private int price;
	public MyMap() {
		preX = 0;
		preY = 0;
		price = 0;
	}
	public int getPreX() {
		return preX;
	}
	public void setPreX(int preX) {
		this.preX = preX;
	}
	public int getPreY() {
		return preY;
	}
	public void setPreY(int preY) {
		this.preY = preY;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
}

test.java

import java.util.LinkedList;
import java.util.Queue;

public class test {
	static  char[][] M = {
			{' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
			{' ',' ',' ',' ','#','#','#',' ',' ',' '},
			{' ',' ',' ',' ',' ',' ','#',' ',' ',' '},
			{' ',' ',' ',' ','#','E','#',' ',' ',' '},
			{' ',' ',' ',' ','#','#','#',' ',' ',' '},
			{' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
			{' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
			{'#','#','#','#','#','#','#','#','#',' '},
			{' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
			{'S',' ',' ',' ',' ',' ',' ',' ',' ',' '}
	};
	static int[] dx = { -1,0,1,1,1,0,-1,-1 };  //x方向
	static int[] dy = { -1,-1,-1,0,1,1,1,0};//y方向
	public static Point start = null;
	public static Point end = null;
	
	public static void main(String[] args) {
		
		//獲取起始點和結束點
		for(int i = 0;i < 10;i++) {
			for(int j = 0;j < 10;j++) {
				if(M[i][j] == 'S') {
					start = new Point(j,i);
				}
				if(M[i][j] == 'E') {
					end = new Point(j,i);
				}
			}
		}
		//開始遍歷
		bfs();
		
		//從結束點開始打印出路徑
		for(int i = 0;i < 10;i++) {
			for(int j = 0;j < 10;j++) {
				System.out.print(M[i][j]);
			}
			System.out.println();
		}
		
		
	}
	/////////////廣度優先部分,主要尋路在這裡//////////
	public static void bfs() {
		//佇列進行記錄待遍歷的點
		Queue<Point> queue = new LinkedList<>();
		//將起始點加入佇列
		queue.add(start);
		//設定與地圖等大的MyMap
		MyMap[][] my = new MyMap[10][10];
		for(int m = 0;m < 10;m++) {
			for(int n = 0;n < 10;n++) {
				my[m][n] = new MyMap();
			}
		}
		//開始遍歷
		while(queue.size() > 0) {
			//佇列頭進行遍歷
			Point p = queue.poll();
			//8個方向
			for(int i = 0;i < 8;i++) {
				int nx = p.getX() + dx[i];
				int ny = p.getY() + dy[i];
				
				if(nx >= 0 && nx < 10 && ny >= 0 && ny < 10 && M[ny][nx] != '#') {
					//第一次訪問
					if(my[ny][nx].getPrice() == 0) {
						queue.add(new Point(nx,ny));
						if((dx[i] == -1 && dy[i] == -1) || (dx[i] == -1 && dy[i] == 1) || (dx[i] == 1 && dy[i] == -1) || (dx[i] == 1 && dy[i] == 1)) {
							my[ny][nx].setPrice(my[p.getY()][p.getX()].getPrice() + 14);
						}else {
							my[ny][nx].setPrice(my[p.getY()][p.getX()].getPrice() + 10);
						}
						my[ny][nx].setPreX(p.getX());
						my[ny][nx].setPreY(p.getY());
						
					}else {
						//二次訪問不用加入佇列,
						if((dx[i] == -1 && dy[i] == -1) || (dx[i] == -1 && dy[i] == 1) || (dx[i] == 1 && dy[i] == -1) || (dx[i] == 1 && dy[i] == 1)) {
							if(my[p.getY()][p.getX()].getPrice() + 14 <= my[ny][nx].getPrice()) {
								my[ny][nx].setPrice(my[p.getY()][p.getX()].getPrice() + 14);
								my[ny][nx].setPreX(p.getX());
								my[ny][nx].setPreY(p.getY());
							}
						}else {
							if(my[p.getY()][p.getX()].getPrice() + 10 < my[ny][nx].getPrice()) {
								my[ny][nx].setPrice(my[p.getY()][p.getX()].getPrice() + 10);
								my[ny][nx].setPreX(p.getX());
								my[ny][nx].setPreY(p.getY());
							}
						}
					}
					
					
				}
				
			}
			
			//////////////////以下部分只是列印不重要//////////////////
			//到達目的地後
			if(p.getX() == end.getX() && p.getY() == end.getY()) {
				int x = p.getX();
				int y = p.getY();
				int tmpx = 0;
				int tmpy = 0;
				//列印
				while(x != start.getX() || y != start.getY()) {
					tmpx = my[y][x].getPreX();
					tmpy = my[y][x].getPreY();
					x = tmpx;
					y = tmpy;
					if(M[y][x] != 'S') {
						M[y][x] = '*';
					}
				}
				break;
			}
		}
	}
}

效果


類的解釋:

      Point.java裡面寫的就是一個點的類裡面就是裝的x,y沒啥說的。MyMap.java寫的類就是記錄前一個點的位置prex 和prey其實這兩個引數用一個Point來記就好了。還有一個最重要的就是那個price,記錄到達當前點所需要的消耗。也就是路徑長度。然後就是test.java了。test當中就是bfs函式最重要了。

大體遍歷的思路:

      將開始節點加入佇列,然後在迴圈中先讀出佇列頭,即出佇列,讀出的頭就是當前節點,圍繞該節點遍歷周圍的所有節點,分為:左上,上,右上,右,右下,下,左下,左共8個方向。然後將周圍的節點依次加入到佇列中,並且設定該節點的權值和前一節點座標。不斷迴圈重複以上操作,逐層遍歷直到找到目的節點,或者佇列為空,若佇列為空都沒有找到目標節點那麼就是該節點不可達。


程式的超詳細實現過程:

       首先為了方便起見設定一個char型別的二維陣列當做地圖,大小是10*10的。然後遍歷找到起始點和結束點的位置座標,Point型別的start和end。還有dx和dy兩個陣列。裡面存的是遍歷的順序,也就是座標的偏移量,兩個陣列每個都是8個元素,因為是8個方向的嘛。然後直接進重點bfs()函式,先建立一個queue佇列,將開始節點加入佇列,設定與地圖等大的MyMap類的一個數組,並初始化。

      開始遍歷,一個while迴圈,迴圈條件是queue大小要大於0,迴圈體中則是讀出佇列頭,出佇列,一個for迴圈用來遍歷該點周圍8個方向的點,當然要有約束條件周圍的點不能超過10*10的範圍同時不能為牆壁,判斷牆壁用的是之前char陣列的地圖,而遍歷記錄price和前一個點的座標的是那個MyMap陣列。queue出來的點為當前點,遍歷的是周圍點,周圍的點因為是8個方向,當擴大的時候必然會存在重複遍歷的問題,不能單純的用一個布林來標記是否重複遍歷,因為如果遍歷到就進行標記的話,得到的路徑不一定是最短的。因此我用的是一個price來記錄到達該點的路徑長度,判斷是否遍歷過了也很簡單,如果price為0的話那麼說就是第一次遍歷,若不為0那就說明不是第一次遍歷。兩種情況分開討論。(1)第一次遍歷到該點:因為是第一次遍歷到,所以要先將其加入到佇列中,然後將設定消耗price,就是獲取當前點(佇列裡出來的點)的price加上兩點間路徑長度,兩點間路徑長度:斜對角是14,上下左右相鄰是10。為什麼這麼設,其實就是勾股定理的出來乘以10,也可以不這麼設定只要相鄰的距離相加大於斜對角距離就可以,滿足三角形兩邊之和大於第三邊就可以。然後設定前一點座標為當前點。(2)第二次遍歷到該點:因為不是第一次了,所以這裡就不用再將其加入佇列中,這裡要做的就是判斷應不應該與這個點連線,怎麼說呢,因為他已經有price記錄了,說明他已經有主子了,那麼此時就要判斷他指向的主子稱不稱職。那他的price與自己的price加上距離進行比較,如果他的price較小說明主子很稱職,也就是路徑較短,如果自己的price加距離要小於他自己的price說明他當前的主子不稱職,就改變他的前一點的標記,改成當前點的座標,並改變price值。如此一來可以保證到達每個點都是最小權值,按照點記錄的前一點座標進行回溯即可得到到達點的最短路徑。


發現節點重複後判斷消耗,發現有更好的路徑則改記錄座標和消耗值


然後找到路徑直接回溯,更改地圖,將走過的節點改為‘*’符號,最後整體列印地圖。即為最短路徑。這個演算法尋路效率與A*演算法相比還是比較低。不過還是挺好理解的。