Java十大算法(2):普利姆算法(Prim)、克魯斯卡爾算法(Kruskal)、迪傑斯特拉算法(Dijkstra)、弗洛伊德算法(Floyd)、馬踏棋盤算法

6、普利姆算法(Prim)

最小生成樹:
修路問題本質就是就是最小生成樹問題, 先介紹一下 最小生成樹 (Minimum Cost Spanning Tree),簡稱MST。

  1. 給定一個帶權的無向連通圖,如何選取一棵生成樹,使樹上所有邊上權的總和爲最小,這叫最小生成樹
  2. N個頂點,一定有N-1條邊
  3. 包含全部頂點
  4. N-1條邊都在圖中
  5. 舉例說明(如圖)
    在這裏插入圖片描述
  6. 求最小生成樹的算法主要是普里姆算法和克魯斯卡爾算法

普里姆算法介紹:

  1. 普利姆(Prim)算法求最小生成樹,也就是在包含n個頂點的連通圖中,找出只有(n-1)條邊包含所有n個頂點的連通子圖,也就是所謂的極小連通子圖
  2. 普利姆的算法如下:
    (1)設G=(V,E)是連通網,T=(U,D)是最小生成樹,V,U是頂點集合,E,D是邊的集合。
    (2)若從頂點u開始構造最小生成樹,則從集合V中取出頂點u放入集合U中,標記頂點v的visited[u]=1。
    (3)若集合U中頂點ui與集合V-U中的頂點vj之間存在邊,則尋找這些邊中權值最小的邊,但不能構成迴路,將頂點vj加入集合U中,將邊(ui,vj)加入集合D中,標記visited[vj]=1。
    (4)重複步驟②,直到U與V相等,即所有頂點都被標記爲訪問過,此時D中有n-1條邊。

普里姆算法最佳實踐(修路問題):
在這裏插入圖片描述
有勝利鄉有7個村莊(A, B, C, D, E, F, G) ,現在需要修路把7個村莊連通,各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里,問:如何修路保證各個村莊都能連通,並且總的修建公路總里程最短?

圖解:
在這裏插入圖片描述
代碼:

public class PrimAlgorithm {

	public static void main(String[] args) {

		char[] data=new char[] {'A','B','C','D','E','F','G'};
		int vertexs = data.length;
		//用二維數組來表示鄰接矩陣
		int[][] weight = new int[][] {//用10000這個較大的數表示不連通
			{10000,5,7,10000,10000,10000,2},
            {5,10000,10000,9,10000,10000,3},
            {7,10000,10000,10000,8,10000,10000},
            {10000,9,10000,10000,10000,4,10000},
            {10000,10000,8,10000,10000,5,4},
            {10000,10000,10000,4,5,10000,6},
            {2,3,10000,10000,4,6,10000},
		};
		
		//創建圖對象
		MGraph graph = new MGraph(vertexs);
		//創建最小生成樹對象
		MinTree minTree = new MinTree();
		minTree.createGraph(graph, vertexs, data, weight);
		//輸出最小生成樹
		minTree.showGraph(graph);
		
		//測試算法
		minTree.prim(graph, 1);
	}
}

// 創建最小生成樹
class MinTree {
	// 創建圖的鄰接矩陣
	/**
	 * 
	 * @param graph
	 *            圖對象
	 * @param vertexs
	 *            圖對應的頂點個數
	 * @param data
	 *            圖對應的各個頂點的值
	 * @param weight
	 *            圖的鄰接矩陣
	 */
	public void createGraph(MGraph graph, int vertexs, char[] data, int[][] weight) {
		for (int i = 0; i < vertexs; i++) {
			graph.data[i] = data[i];
			for (int j = 0; j < vertexs; j++) {
				graph.weight[i][j] = weight[i][j];
			}
		}
	}

	// 顯示圖的鄰接矩陣
	public void showGraph(MGraph graph) {
		for(int i=0;i<graph.vertexs;i++) {
			for(int j=0;j<graph.vertexs;j++) {
				System.out.printf("%6d",graph.weight[i][j]);
			}
			System.out.println();
		}
	}
	
	//編寫Prim算法得到最小生成樹
	/**
	 * 
	 * @param graph 圖
	 * @param v 表示從圖的第幾個頂點開始生成'A'->0, 'B'->1 ...
	 */
	public void prim(MGraph graph, int v) {
		//visited數組表示頂點是否被訪問過
		int[] visited = new int[graph.vertexs];//默認就爲0
		//把當前節點標記爲已經訪問
		visited[v]=1;
		//h1和h2記錄兩個頂點的下標
		int h1=-1;
		int h2=-1;
		int minWeight=10000;//將其設置爲一個比較大的數,在後面的遍歷過程中會被替換
		for(int k=1;k<graph.vertexs;k++) {//因爲有vertexs個頂點,則經過prim算法過後一定有vertexs-1條邊
			
			//確定每一次生成的子圖,和哪個結點距離最近
			for(int i=0;i<graph.vertexs;i++) {//i表示被訪問過的點
				for(int j=0;j<graph.vertexs;j++) {//j表示與i結點相連且未被訪問過的點
					if(visited[i]==1&&visited[j]==0&&graph.weight[i][j]<minWeight) {
						//替換minWeight,由此來尋找滿足條件兩邊中權值最小的
						minWeight=graph.weight[i][j];
						h1=i;
						h2=j;
					}
				}
			}
			//此時已經找到了一條最小的邊,則將這個邊輸出
			System.out.println("在邊 <"+graph.data[h1]+"-"+graph.data[h2]+">修路	權值爲:"+minWeight);
			//將當前這個新點標記爲已訪問過
			visited[h2]=1;
			//重置minWeight
			minWeight=10000;
		}
	}
}

class MGraph {
	int vertexs;// 表示節點數
	char[] data;// 存放節點數據
	int[][] weight;// 存放邊

	public MGraph(int vertexs) {
		this.vertexs = vertexs;
		data = new char[vertexs];
		weight = new int[vertexs][vertexs];
	}
}

結果:

 10000     5     7 10000 10000 10000     2
     5 10000 10000     9 10000 10000     3
     7 10000 10000 10000     8 10000 10000
 10000     9 10000 10000 10000     4 10000
 10000 10000     8 10000 10000     5     4
 10000 10000 10000     4     5 10000     6
     2     3 10000 10000     4     6 10000
在邊 <B-G>修路	權值爲:3
在邊 <G-A>修路	權值爲:2
在邊 <G-E>修路	權值爲:4
在邊 <E-F>修路	權值爲:5
在邊 <F-D>修路	權值爲:4
在邊 <A-C>修路	權值爲:7

7、克魯斯卡爾算法(Kruskal)

克魯斯卡爾算法介紹:

  1. 克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。
  2. 基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成迴路。
  3. 具體做法:首先構造一個只含n個頂點的森林,然後依權值從小到大從連通網中選擇邊加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止。

克魯斯卡爾最佳實踐-公交站問題:
在這裏插入圖片描述
有北京有新增7個站點(A, B, C, D, E, F, G) ,現在需要修路把7個站點連通,各個站點的距離用邊線表示(權) ,比如 A – B 距離 12公里,問:如何修路保證各個站點都能連通,並且總的修建公路總里程最短?

圖解:
在這裏插入圖片描述
代碼:

import java.util.Arrays;

public class KruskalCase {

	private int edgeNum;//記錄有多少條邊
	private char[] vertexs;//記錄頂點
	private int[][] matrix;//鄰接矩陣
	//使用INF表示兩個頂點不能連通
	private static final int INF = Integer.MAX_VALUE;
	
	public static void main(String[] args) {
		
		char[] vertexs = {'A','B','C','D','E','F','G'};
		//鄰接矩陣
		int[][] matrix= {
					/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
			/*A*/ {   0,  12, INF, INF, INF,  16,  14},
			/*B*/ {  12,   0,  10, INF, INF,   7, INF},
			/*C*/ { INF,  10,   0,   3,   5,   6, INF},
			/*D*/ { INF, INF,   3,   0,   4, INF, INF},
			/*E*/ { INF, INF,   5,   4,   0,   2,   8},
			/*F*/ {  16,   7,   6, INF,   2,   0,   9},
			/*G*/ {  14, INF, INF, INF,   8,   9,   0}
		};
		//創建KruskalCase對象實例
		KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
		//輸出圖
		kruskalCase.print();
		
		kruskalCase.kruskal();
	}
	
	//構造器
	public KruskalCase(char[] vertexs ,int[][] matrix) {
		//初始化頂點個數,和邊的個數
		int vlen = vertexs.length;
		
		//初始化頂點
		this.vertexs=vertexs.clone();
		
		//初始化邊
		this.matrix=matrix.clone();
		
		//統計邊的個數
		for(int i=0;i<vlen;i++) {
			for(int j=i+1;j<vlen;j++){
				if(this.matrix[i][j]!=INF) {
					edgeNum++;
				}
			}
		}
	}
	
	public void kruskal() {
		int index=0;//表示最後結果數組的索引
		int[] ends = new int[vertexs.length];//用於保存“已有最小生成樹”中的每個頂點在最小生成樹中的終點
		//創建結果數組,用於保存最後的最小生成樹
		EData[] res  = new EData[vertexs.length-1];
		
		//獲取圖中所有的邊的集合(12條邊)
		EData[] edges = getEdges();
		
		//按邊的權值大小排序(從小到大)
		sortEdges(edges);
		
		//遍歷edges數組,添加邊入最小生成樹。判斷準備加入的邊是否形成了迴路,如果沒形成則加入最小生成樹中,否則繼續遍歷
		for(int i=0;i<edgeNum;i++) {
			//獲取第i條邊的第一個頂點
			int p1 = getPosition(edges[i].start);
			//獲取第i條邊的第二個頂點
			int p2 = getPosition(edges[i].end);
			
			//獲取p1頂點在已有最小生成樹中的終點
			int m = getEnd(ends, p1);
			//獲取p2頂點在已有最小生成樹中的終點
			int n = getEnd(ends, p2);
			//判斷是否構成迴路
			if(m!=n) {//沒有構成迴路
				ends[m]=n;
				res[index++]=edges[i];
			}
		}
		//打印最小生成樹
		System.out.println("最小生成樹:");
		for(int i=0;i<res.length;i++) {
			System.out.println(res[i]);
		}
		System.out.println(Arrays.toString(ends));
	}
	
	//打印鄰接矩陣
	public void print() {
		System.out.println("鄰接矩陣爲:");
		for(int i=0;i<vertexs.length;i++) {
			for(int j=0;j<vertexs.length;j++) {
				System.out.printf("%12d",matrix[i][j]);
			}
			System.out.println();
		}
	}
	
	//對邊進行排序處理(冒泡)
	private void sortEdges(EData[] edges) {
		for(int i=0;i<edges.length-1;i++) {
			for(int j=0;j<edges.length-1-i;j++) {
				if(edges[j].weight>edges[j+1].weight) {
					EData temp = edges[j];
					edges[j]=edges[j+1];
					edges[j+1]=temp;
				}
			}
		}
	}
	
	//返回傳入頂點對應的下標
	private int getPosition(char ch) {
		for(int i=0;i<vertexs.length;i++) {
			if(vertexs[i]==ch) {
				return i;
			}
		}
		return -1;
	}
	
	//獲取圖中的所有邊,放入EData數組中,通過鄰接矩陣來獲取
	private EData[] getEdges() {
		int index=0;
		EData[] edges = new EData[edgeNum];
		for(int i=0;i<vertexs.length;i++) {
			for(int j=i+1;j<vertexs.length;j++) {
				if(matrix[i][j]!=INF) {
					edges[index++]=new EData(vertexs[i],vertexs[j],matrix[i][j]);
				}
			}
		}
		return edges;
	}
	
	//獲取下標爲i的頂點的終點的下標
	//ends數組用來記錄各個頂點的終點是哪個,它是在遍歷過程中逐步生成的
	private int getEnd(int[] ends,int i) {
		while(ends[i]!=0) {
			i=ends[i];
		}
		return i;
	}
}

//創建一個類,其對象實例表示一條邊
class EData{
	char start;//邊的一個點
	char end;//邊的另一個點
	int weight;//邊的權值
	
	//構造器	
	public EData(char start, char end, int weight) {
		super();
		this.start = start;
		this.end = end;
		this.weight = weight;
	}
	
	//輸出邊的信息
	@Override
	public String toString() {
		return "EData [<" + start + ", " + end + "> =" + weight + "]";
	}
}

結果:

鄰接矩陣爲:
           0          12  2147483647  2147483647  2147483647          16          14
          12           0          10  2147483647  2147483647           7  2147483647
  2147483647          10           0           3           5           6  2147483647
  2147483647  2147483647           3           0           4  2147483647  2147483647
  2147483647  2147483647           5           4           0           2           8
          16           7           6  2147483647           2           0           9
          14  2147483647  2147483647  2147483647           8           9           0
最小生成樹:
EData [<E, F> =2]
EData [<C, D> =3]
EData [<D, E> =4]
EData [<B, F> =7]
EData [<E, G> =8]
EData [<A, B> =12]

8、迪傑斯特拉算法(Dijkstra)

迪傑斯特拉(Dijkstra)算法介紹:
迪傑斯特拉(Dijkstra)算法是典型最短路徑算法,用於計算一個結點到其他結點的最短路徑。 它的主要特點是以起始點爲中心向外層層擴展(廣度優先搜索思想),直到擴展到終點爲止。

迪傑斯特拉(Dijkstra)算法過程:
設置出發頂點爲v,頂點集合V{v1,v2,vi…},v到V中各頂點的距離構成距離集合Dis,Dis{d1,d2,di…},Dis集合記錄着v到圖中各頂點的距離(到自身可以看作0,v到vi距離對應爲di)

  1. 從Dis中選擇值最小的di並移出Dis集合,同時移出V集合中對應的頂點vi,此時的v到vi即爲最短路徑。
  2. 更新Dis集合,更新規則爲:比較v到V集合中頂點的距離值,與v通過vi到V集合中頂點的距離值,保留值較小的一個(同時也應該更新頂點的前驅節點爲vi,表明是通過vi到達的)。
  3. 重複執行兩步驟,直到最短路徑頂點爲目標頂點即可結束。

迪傑斯特拉(Dijkstra)算法最佳應用-最短路徑:
在這裏插入圖片描述
戰爭時期,勝利鄉有7個村莊(A, B, C, D, E, F, G) ,現在有六個郵差,從G點出發,需要分別把郵件分別送到 A, B, C , D, E, F 六個村莊,各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里,問:如何計算出G村莊到 其它各個村莊的最短距離?

圖解:
在這裏插入圖片描述
代碼:

import java.util.Arrays;

public class DijkstraAlgorithm {

	public static void main(String[] args) {
		char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
		// 鄰接矩陣
		int[][] matrix = new int[vertex.length][vertex.length];
		final int N = 65535;// 表示不可連接
		matrix[0] = new int[] { N, 5, 7, N, N, N, 2 };
		matrix[1] = new int[] { 5, N, N, 9, N, N, 3 };
		matrix[2] = new int[] { 7, N, N, N, 8, N, N };
		matrix[3] = new int[] { N, 9, N, N, N, 4, N };
		matrix[4] = new int[] { N, N, 8, N, N, 5, 4 };
		matrix[5] = new int[] { N, N, N, 4, 5, N, 6 };
		matrix[6] = new int[] { 2, 3, N, N, 4, 6, N };
		// 創建Graph對象
		Graph graph = new Graph(vertex, matrix);
		graph.showGraph();
		graph.dijkstra(6);
		graph.showDijkstra();
	}
}

class Graph {
	private char[] vertex;// 存放各個頂點
	private int[][] matrix;// 鄰接矩陣
	private VisitedVertex visitedVertex;// 已經訪問的定點的集合

	// 構造器
	public Graph(char[] vertexs, int[][] matrix) {
		this.vertex = vertexs;
		this.matrix = matrix;
	}

	// 顯示最後結果
	public void showDijkstra() {
		visitedVertex.show();
	}

	// 顯示圖
	public void showGraph() {
		for (int[] link : matrix) {
			System.out.println(Arrays.toString(link));
		}
	}

	// 地傑斯特拉算法實現,index表示出發頂點的下標
	public void dijkstra(int index) {
		visitedVertex = new VisitedVertex(vertex.length, index);
		update(index);// 更新距離和前驅頂點

		for (int i = 1; i < vertex.length; i++) {
			index = visitedVertex.updateArr();// 選擇並訪問新的訪問頂點
			update(index);
		}
	}

	// 同時更新index下標頂點到周圍相連頂點的距離,和周圍相連頂點的前驅頂點
	public void update(int index) {
		int len = 0;
		// 遍歷鄰接矩陣的index行
		for (int i = 0; i < matrix[index].length; i++) {
			// len表示出發頂點到index頂點的距離 + index頂點到i頂點的距離
			len = visitedVertex.getDis(index) + matrix[index][i];
			// 如果i頂點未被訪問過,並且len小於出發頂點到i頂點的距離,就要更新
			if (!visitedVertex.in(i) && len < visitedVertex.getDis(i)) {
				visitedVertex.updatePre(i, index);// 更新i頂點的前驅頂點爲index頂點
				visitedVertex.updateDis(i, len);// 更新出發頂點到i頂點的距離爲len
			}
		}
	}
}

// 已訪問頂點集合
class VisitedVertex {
	public int[] already_arr;// 記錄各個頂點是否爲訪問過
	public int[] pre_visited;// 記錄本下標頂點所對應的前一個頂點的下標
	public int[] dis;// 記錄出發頂點到與其相連的所有頂點的距離,將距離最短的放入dis中

	// 構造器
	/**
	 * 
	 * @param length
	 *            頂點的個數
	 * @param index
	 *            出發頂點對應的下標
	 */
	public VisitedVertex(int length, int index) {
		this.already_arr = new int[length];//標記某個頂點是否已經被訪問
		this.pre_visited = new int[length];//記錄每個頂點的上一個頂點是哪一個(用來確定最短路徑是哪一條)
		this.dis = new int[length];//起始點到每一個點的最短路徑長度

		// 初始化dis數組,全填爲最大值(除了出發頂點)
		Arrays.fill(dis, 65535);
		this.already_arr[index] = 1;// 設置出發頂點已經被訪問過
		this.dis[index] = 0;// 設置出發頂點的訪問距離爲0
	}

	// 判斷index頂點是否被訪問過
	public boolean in(int index) {
		return already_arr[index] == 1;
	}

	// 更新出發頂點到index頂點的距離,更新爲len
	public void updateDis(int index, int len) {
		dis[index] = len;
	}

	// 更新pre頂點的前驅頂點,更新爲index
	public void updatePre(int pre, int index) {
		pre_visited[pre] = index;
	}

	// 返回出發頂點到index頂點的距離
	public int getDis(int index) {
		return dis[index];
	}

	// 繼續選擇並返回新的訪問頂點,比如G完之後就是A作爲一個新的訪問頂點(並非出發頂點)
	public int updateArr() {
		int min = 65535, index = 0;
		for (int i = 0; i < already_arr.length; i++) {
			if (already_arr[i] == 0 && dis[i] < min) {
				min = dis[i];
				index = i;
			}
		}
		// 更新index被訪問過
		already_arr[index] = 1;
		return index;
	}

	// 顯示最後的結果
	public void show() {

		System.out.println("==========================");
		// 輸出already_arr
		for (int i : already_arr) {
			System.out.print(i + " ");
		}
		System.out.println();
		// 輸出pre_visited
		for (int i : pre_visited) {
			System.out.print(i + " ");
		}
		System.out.println();
		// 輸出dis
		for (int i : dis) {
			System.out.print(i + " ");
		}
		System.out.println();

		// 爲了好看最後的最短距離,我們處理
		char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
		int count = 0;
		for (int i : dis) {
			if (i != 65535) {
				System.out.print(vertex[count] + "(" + i + ") ");
			} else {
				System.out.println("N ");
			}
			count++;
		}
		System.out.println();
	}
}

結果:

[65535, 5, 7, 65535, 65535, 65535, 2]
[5, 65535, 65535, 9, 65535, 65535, 3]
[7, 65535, 65535, 65535, 8, 65535, 65535]
[65535, 9, 65535, 65535, 65535, 4, 65535]
[65535, 65535, 8, 65535, 65535, 5, 4]
[65535, 65535, 65535, 4, 5, 65535, 6]
[2, 3, 65535, 65535, 4, 6, 65535]
==========================
1 1 1 1 1 1 1 
6 6 0 5 6 6 0 
2 3 9 10 4 6 0 
[65535, 5, 7, 65535, 65535, 65535, 2]
[5, 65535, 65535, 9, 65535, 65535, 3]
[7, 65535, 65535, 65535, 8, 65535, 65535]
[65535, 9, 65535, 65535, 65535, 4, 65535]
[65535, 65535, 8, 65535, 65535, 5, 4]
[65535, 65535, 65535, 4, 5, 65535, 6]
[2, 3, 65535, 65535, 4, 6, 65535]
==========================
1 1 1 1 1 1 1 
6 6 0 5 6 6 0 
2 3 9 10 4 6 0 
A(2) B(3) C(9) D(10) E(4) F(6) G(0) 

其中 A(2) B(3) C(9) D(10) E(4) F(6) G(0) 括號中的數字表示G點與括號前的點最短的距離。

9、弗洛伊德算法(Floyd)

弗洛伊德(Floyd)算法介紹:

  1. 和Dijkstra算法一樣,弗洛伊德(Floyd)算法也是一種用於尋找給定的加權圖中頂點間最短路徑的算法。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。
  2. 弗洛伊德算法(Floyd)計算圖中各個頂點之間的最短路徑。
  3. 迪傑斯特拉算法用於計算圖中某一個頂點到其他頂點的最短路徑。
  4. 弗洛伊德算法 VS 迪傑斯特拉算法:迪傑斯特拉算法通過選定的被訪問頂點,求出從出發訪問頂點到其他頂點的最短路徑;弗洛伊德算法中每一個頂點都是出發訪問點,所以需要將每一個頂點看做被訪問頂點,求出從每一個頂點到其他頂點的最短路徑。

弗洛伊德(Floyd)算法分析:

  1. 設置頂點vi到頂點vk的最短路徑已知爲Lik,頂點vk到vj的最短路徑已知爲Lkj,頂點vi到vj的路徑爲Lij,則vi到vj的最短路徑爲:min((Lik+Lkj),Lij),vk的取值爲圖中所有頂點,則可獲得vi到vj的最短路徑。
  2. 至於vi到vk的最短路徑Lik或者vk到vj的最短路徑Lkj,是以同樣的方式獲得。

弗洛伊德(Floyd)算法最佳應用-最短路徑:
在這裏插入圖片描述
勝利鄉有7個村莊(A, B, C, D, E, F, G),各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里,問:如何計算出各村莊到 其它各村莊的最短距離?

圖解:
在這裏插入圖片描述
代碼實現:

package floyd;

import java.util.Arrays;

public class FloydAlgorithm {

	public static void main(String[] args) {
		char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
		int[][] matrix = new int[vertex.length][vertex.length];
		final int N = 65535;
		matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };
		matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };
		matrix[2] = new int[] { 7, N, 0, N, 8, N, N };
		matrix[3] = new int[] { N, 9, N, 0, N, 4, N };
		matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };
		matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };
		matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };

		Graph graph = new Graph(vertex.length, matrix, vertex);
		graph.floyd();
		graph.show();
	}
}

class Graph {
	private char[] vertex;// 存放頂點
	private int[][] dis;// 保存從各個頂點出發到其它頂點的距離
	private int[][] pre;// 保存前驅頂點

	// 構造器
	/**
	 * 
	 * @param length
	 *            大小
	 * @param matrix
	 *            鄰接矩陣
	 * @param vertex
	 *            頂點數組
	 */
	public Graph(int length, int[][] matrix, char[] vertex) {
		this.vertex = vertex;
		this.dis = matrix;
		this.pre = new int[length][length];
		// 對pre數組初始化,存放的是頂點下標
		for (int i = 0; i < length; i++) {
			Arrays.fill(pre[i], i);
		}
	}

	// 顯示dis數組和pre數組
	public void show() {
		// 爲了顯示便於閱讀,我們優化一下輸出
		char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
		for (int k = 0; k < dis.length; k++) {
			// 先將pre數組輸出的一行
			for (int i = 0; i < dis.length; i++) {
				System.out.print(vertex[pre[k][i]] + " ");
			}
			System.out.println();
		}
		for (int k = 0; k < dis.length; k++) {
			// 輸出dis數組的一行數據
			for (int i = 0; i < dis.length; i++) {
				System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路徑是" + dis[k][i] + ") ");
			}
			System.out.println();
		}
	}
	
	//弗洛伊德算法
	public void floyd() {
		int len=0;//保存變量距離
		//遍歷中間頂點,k爲其下標
		for(int k=0;k<dis.length;k++) {
			//從i頂點出發
			for(int i=0;i<dis.length;i++) {
				//到達j頂點
				for(int j=0;j<dis.length;j++) {
					len = dis[i][k]+dis[k][j];//從i頂點出發,經過中間頂點k,到達j頂點的距離
					if(len<dis[i][j]) {//如果len小於i到j的直連距離,則更新len;否則就不更新
						dis[i][j]=len;
						pre[i][j]=pre[k][j];
					}
				}
			}
		}
	}
}

結果:

A A A F G G A 
B B A B G G B 
C A C F C E A 
G D E D F D F 
G G E F E E E 
G G E F F F F 
G G A F G G G 
(A到A的最短路徑是0) (A到B的最短路徑是5) (A到C的最短路徑是7) (A到D的最短路徑是12) (A到E的最短路徑是6) (A到F的最短路徑是8) (A到G的最短路徑是2) 
(B到A的最短路徑是5) (B到B的最短路徑是0) (B到C的最短路徑是12) (B到D的最短路徑是9) (B到E的最短路徑是7) (B到F的最短路徑是9) (B到G的最短路徑是3) 
(C到A的最短路徑是7) (C到B的最短路徑是12) (C到C的最短路徑是0) (C到D的最短路徑是17) (C到E的最短路徑是8) (C到F的最短路徑是13) (C到G的最短路徑是9) 
(D到A的最短路徑是12) (D到B的最短路徑是9) (D到C的最短路徑是17) (D到D的最短路徑是0) (D到E的最短路徑是9) (D到F的最短路徑是4) (D到G的最短路徑是10) 
(E到A的最短路徑是6) (E到B的最短路徑是7) (E到C的最短路徑是8) (E到D的最短路徑是9) (E到E的最短路徑是0) (E到F的最短路徑是5) (E到G的最短路徑是4) 
(F到A的最短路徑是8) (F到B的最短路徑是9) (F到C的最短路徑是13) (F到D的最短路徑是4) (F到E的最短路徑是5) (F到F的最短路徑是0) (F到G的最短路徑是6) 
(G到A的最短路徑是2) (G到B的最短路徑是3) (G到C的最短路徑是9) (G到D的最短路徑是10) (G到E的最短路徑是4) (G到F的最短路徑是6) (G到G的最短路徑是0) 

10、馬踏棋盤算法

馬踏棋盤算法介紹:

  1. 馬踏棋盤算法也被稱爲騎士周遊問題
  2. 將馬隨機放在國際象棋的8×8棋盤Board [0~7] [0~7] 的某個方格中,馬按走棋規則(馬走日字)進行移動。要求每個方格只進入一次,走遍棋盤上全部64個方格。
  3. 遊戲演示:馬踏棋盤小遊戲(6x6棋盤)

在這裏插入圖片描述在這裏插入圖片描述
馬踏棋盤遊戲分析:

  1. 馬踏棋盤問題(騎士周遊問題)實際上是圖的深度優先搜索(DFS)的應用。
  2. 如果使用回溯(就是深度優先搜索)來解決,假如馬兒踏了53個點,如圖:走到了第53個,座標(1,0),發現已經走到盡頭,沒辦法,那就只能回退了,查看其他的路徑,就在棋盤上不停的回溯……
  3. 分析這種方式,可以再使用使用貪心算法(greedyalgorithm)的思想進行優化。即每次優先走再下一步走法較少的點。

圖解:
在這裏插入圖片描述
代碼:

import java.awt.Point;
import java.util.ArrayList;

public class HorseChessBoard {

	private static int X;// 棋盤列數
	private static int Y;// 棋盤行數
	// 創建一個數組標記棋盤的各個位置是否被訪問過
	private static boolean visited[][];
	// 使用一個屬性,標記棋盤的所有位置是否都被訪問過
	private static boolean finished;

	public static void main(String[] args) {
		X = 8;
		Y = 8;
		int row = 1;// 馬初始位置的行,從1開始
		int col = 1;// 馬初始位置的列,從1開始
		// 創建棋盤
		int[][] chessBoard = new int[X][Y];
		visited = new boolean[X][Y];// 初始值均爲false
		long star = System.currentTimeMillis();
		travelChessBoard(chessBoard, row - 1, col - 1, 1);
		long end = System.currentTimeMillis();
		System.out.println("共耗時:" + (end - star) + " ms");
		
		//輸出棋盤最後情況
		for(int[] rows:chessBoard) {
			for(int cols:rows) {
				System.out.print(cols+"\t");
			}
			System.out.println();
		}
	}

	/**
	 * 
	 * @param chessBoard
	 *            棋盤
	 * @param row
	 *            馬當前位置的行,從0開始
	 * @param col
	 *            馬當前位置的列,從0開始
	 * @param step
	 *            第幾步,從1開始
	 */
	public static void travelChessBoard(int[][] chessBoard, int row, int col, int step) {
		chessBoard[row][col] = step;
		visited[row][col] = true;// 標記該位置已經被訪問
		// 獲取當前位置可以走的下一個位置的集合
		ArrayList<Point> points = next(new Point(col, row));
		// 遍歷points
		while (!points.isEmpty()) {
			Point point = points.remove(0);
			// 判斷該點是否已經被訪問過
			if (!visited[point.y][point.x]) {
				travelChessBoard(chessBoard, point.y, point.x, step + 1);
			}
		}
		// 判斷馬是否完成任務(用step和應走的步數進行比較)
		// 如果沒有達到數量,則表示未完成任務,則將整個棋盤置0
		// 如果step<X*Y成立,則有兩種情況
		// 1. 棋盤到目前位置仍然沒有走完
		// 2. 棋盤處於一個回溯過程
		if (step < X * Y && !finished) {
			chessBoard[row][col] = 0;
			visited[row][col] = false;
		} else {
			finished = true;
		}
	}

	// 根據當前位置(Point對象),計算馬還能走那些位置(Point),並放入List集合中(最多有8個位置)
	public static ArrayList<Point> next(Point curPoint) {

		// 創建一個集合
		ArrayList<Point> points = new ArrayList<Point>();

		Point p1 = new Point();
		// 表示馬兒可以走5這個位置
		if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走6這個位置
		if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走7這個位置
		if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走0這個位置
		if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走1這個位置
		if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走2這個位置
		if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走3這個位置
		if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走4這個位置
		if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
			points.add(new Point(p1));
		}
		return points;
	}
}

結果:

共耗時:69672 ms
1	8	11	16	3	18	13	64	
10	27	2	7	12	15	4	19	
53	24	9	28	17	6	63	14	
26	39	52	23	62	29	20	5	
43	54	25	38	51	22	33	30	
40	57	42	61	32	35	48	21	
55	44	59	50	37	46	31	34	
58	41	56	45	60	49	36	47	

可以明顯看到需要較長時間,才能運行出結果。
下面我們用貪心算法的思想進行優化,優先走再下一步走法較少的點:
在這裏插入圖片描述
代碼:

import java.awt.Point;
import java.util.ArrayList;
import java.util.Comparator;

public class HorseChessBoard {

	private static int X;// 棋盤列數
	private static int Y;// 棋盤行數
	// 創建一個數組標記棋盤的各個位置是否被訪問過
	private static boolean visited[][];
	// 使用一個屬性,標記棋盤的所有位置是否都被訪問過
	private static boolean finished;

	public static void main(String[] args) {
		X = 8;
		Y = 8;
		int row = 1;// 馬初始位置的行,從1開始
		int col = 1;// 馬初始位置的列,從1開始
		// 創建棋盤
		int[][] chessBoard = new int[X][Y];
		visited = new boolean[X][Y];// 初始值均爲false
		long star = System.currentTimeMillis();
		travelChessBoard(chessBoard, row - 1, col - 1, 1);
		long end = System.currentTimeMillis();
		System.out.println("共耗時:" + (end - star) + " ms");
		
		//輸出棋盤最後情況
		for(int[] rows:chessBoard) {
			for(int cols:rows) {
				System.out.print(cols+"\t");
			}
			System.out.println();
		}
	}

	/**
	 * 
	 * @param chessBoard
	 *            棋盤
	 * @param row
	 *            馬當前位置的行,從0開始
	 * @param col
	 *            馬當前位置的列,從0開始
	 * @param step
	 *            第幾步,從1開始
	 */
	public static void travelChessBoard(int[][] chessBoard, int row, int col, int step) {
		chessBoard[row][col] = step;
		visited[row][col] = true;// 標記該位置已經被訪問
		// 獲取當前位置可以走的下一個位置的集合
		ArrayList<Point> points = next(new Point(col, row));
		//對points中元素進行排序
		sort(points);
		// 遍歷points
		while (!points.isEmpty()) {
			Point point = points.remove(0);
			// 判斷該點是否已經被訪問過
			if (!visited[point.y][point.x]) {
				travelChessBoard(chessBoard, point.y, point.x, step + 1);
			}
		}
		// 判斷馬是否完成任務(用step和應走的步數進行比較)
		// 如果沒有達到數量,則表示未完成任務,則將整個棋盤置0
		// 如果step<X*Y成立,則有兩種情況
		// 1. 棋盤到目前位置仍然沒有走完
		// 2. 棋盤處於一個回溯過程
		if (step < X * Y && !finished) {
			chessBoard[row][col] = 0;
			visited[row][col] = false;
		} else {
			finished = true;
		}
	}

	// 根據當前位置(Point對象),計算馬還能走那些位置(Point),並放入List集合中(最多有8個位置)
	public static ArrayList<Point> next(Point curPoint) {

		// 創建一個集合
		ArrayList<Point> points = new ArrayList<Point>();

		Point p1 = new Point();
		// 表示馬兒可以走5這個位置
		if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走6這個位置
		if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走7這個位置
		if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走0這個位置
		if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走1這個位置
		if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走2這個位置
		if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走3這個位置
		if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
			points.add(new Point(p1));
		}
		// 判斷馬兒可以走4這個位置
		if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
			points.add(new Point(p1));
		}
		return points;
	}
	
	//根據當前這一步的所有下一步的選擇位置,進行非遞減排序(貪心算法思想優化)
	public static void sort(ArrayList<Point> points) {
		points.sort(new Comparator<Point>() {

			@Override
			public int compare(Point o1, Point o2) {
				int count1=next(o1).size();
				int count2=next(o2).size();
				return count1-count2;
			}
			
		});
	}
}

結果:

共耗時:24 ms
1	16	37	32	3	18	47	22	
38	31	2	17	48	21	4	19	
15	36	49	54	33	64	23	46	
30	39	60	35	50	53	20	5	
61	14	55	52	63	34	45	24	
40	29	62	59	56	51	6	9	
13	58	27	42	11	8	25	44	
28	41	12	57	26	43	10	7	

可以明顯看出這樣優化大大提高了速度(由於改變了行走的策略,會導致走法與未優化前的不同)

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