【數據結構與算法】圖之最短路徑

8.6.1  最短路徑的基本概念

            在一個圖中,若從一個結點到另一個結點存在着路徑,定義路徑長度爲一條路徑上所經過的邊的數目。圖中從一個結點到另一個結點可能存在着多條路徑,我們把路徑長度最短的那條路徑叫做最短路徑,其路徑長度叫做最短路徑長度或最短距離.

在一個帶權圖中,若從一個結點到另一個結點存在着一條路徑,則稱該路徑上所經過邊的權值之和爲該路徑上的帶權路徑長度。帶權圖中從一個結點到另一個結點可能存在着多條路徑,我們把帶權路徑長度值最小的那條路徑也叫做最短路徑,其帶權路徑長度也叫做最短路徑長度或最短距離。

8.6.2   從一個點到其餘各結點的最點路徑
    1 狄克斯特拉算法思想 
             狄克斯特拉算法是:設置兩個結點的集合S和T,集合S中存放已找到最短路徑的結點,集合T中存放當前還未找到最短路徑的結點。初始狀態時,集合S中只包含源點,設爲v0,然後從集合T中選擇到源點v0路徑長度最短的結點u加入到集合S中,集合S中每加入一個新的結點u都要修改源點v0到集合T中剩餘結點的當前最短路徑長度值,集合T中各結點的新的當前最短路徑長度值,爲原來的當前最短路徑長度值與從源點過結點u到達該結點的路徑長度中的較小者。此過程不斷重複,直到集合T中的結點全部加入到集合S 中爲止。 下圖爲意示例:


8.6.3  每對結點之間的最短路徑
     弗洛伊德算法是:設矩陣cost用來存放帶權有向圖G的權值,即矩陣元素cost[i][j]中存放着下標爲i的結點到下標爲j的結點之間的權值,可以通過遞推構造一個矩陣序列A0,A1,A2,……,AN來求每對結點之間的最短路徑。初始時有,A0[i][j]=cost[i][j]。當已經求出Ak,要遞推求解Ak+1時,可分兩種情況來考慮:一種情況是該路徑不經過下標爲k+1的結點,此時該路徑長度與從結點vi到結點vj的路徑上所經過的結點下標不大於k的最短路徑長度相同;另一種情況是該路徑經過下標爲k+1的結點,此時該路徑可分爲兩段,一段是從結點vi到結點vk+1的最短路徑,另一段是從結點vk+1到結點vj的最短路徑,此時的最短路徑長度等於這兩段最短路徑長度之和。這兩種情況中的路徑長度較小者,就是要求的從結點vi到結點vj的路徑上所經過的結點下標不大於k+1的最短路徑長度。

弗洛伊德算法的算法思想可用如下遞推公式描述:
   A0[i][j]=cost[i][j]
   Ak+1[i][j]=min{Ak[i][j], Ak[i][k+1]+Ak[k+1][j]}   (0≤k≤n-1)
     也就是說,初始時,A0[i][j]=cost[i][j],然後進行遞推,每遞推一次,從結點vi到結點vj的最短路徑上就多考慮了一個經過的中間結點,這樣,經過n次遞推後得到的An[i][j]就是考慮了經過圖中所有結點情況下的從結點vi到結點vj的最短路徑長度。

package cn.ls.path;

import cn.ls.graph.AdjMWGraph;

/**
 * 狄克斯特拉算法實現
 * 最短路徑.
 */
public class Dijkstra {

	static final int maxWeight = 9999;
	/**
	 * 
	 * @param g   矩陣圖類
	 * @param v0      源點序號 
	 * @param distance   用來存放從源點v0到其餘各結點的最短距離數值。
	 * @param path		   用來存放從源點v0到其餘各結點的最短距離上到達目標結點的前一結點下標。	
	 * @throws Exception
	 */
	public static void dijkstra(AdjMWGraph g, int v0, int[] distance, int path[]) throws Exception {
		int n = g.getNumOfVertices();
		int[] s = new int[n];  //用來存放n個結點的標記.
		int minDis, u = 0;

		for (int i = 0; i < n; i++) {
			distance[i] = g.getWeight(v0, i);
			s[i] = 0;  //初始標記爲0
			if (i != v0 && distance[i] < maxWeight)
				path[i] = v0;
			else
				path[i] = -1;
		}
		s[v0] = 1;  //標記結點v0已從集合T加入到集合S中.

		//在還未找到最短路徑的節點集中選取具有最短路徑的結點U
		for (int i = 1; i < n; i++) {
			minDis = maxWeight;
			for (int j = 0; j < n; j++){
				if (s[j] == 0 && distance[j] < minDis) {
					u = j;
					minDis = distance[j];
				}
			}
			//已不存在路徑時算法結束, 對非連通圖是必須的
			if (minDis == maxWeight)
				return;

			s[u] = 1;  //標記結點u已從集合T加入到集合S中.
			//修正從v0到其他結點的最短距離和最短路徑.
			for (int j = 0; j < n; j++){
				//例如由A-->B 原來距離爲無窮大,現在則將距離變成20。此時Path[1] = 2.也就是到達B的前一結點的下標值是2(C).
				if (s[j] == 0 && g.getWeight(u, j) < maxWeight && distance[u] + g.getWeight(u, j) < distance[j]) {
					distance[j] = distance[u] + g.getWeight(u, j);
					path[j] = u;
				}
			}
		}
	}
}

package cn.ls.graph;
/**
 * 
 *鄰接矩陣圖類
 */
public class AdjMWGraph {
	static final int maxWeight = 10000;

	private SeqList vertices; // 存儲結點的順序表
	private int[][] edge; // 存儲邊的二維數組
	private int numOfEdges; // 邊的個數

	public AdjMWGraph(int maxV) { // 構造函數,maxV爲結點個數
		vertices = new SeqList(maxV);
		edge = new int[maxV][maxV];
		for (int i = 0; i < maxV; i++) {
			for (int j = 0; j < maxV; j++) {
				if (i == j)
					edge[i][j] = 0;
				else
					edge[i][j] = maxWeight;
			}
		}
		numOfEdges = 0;
	}

	public int getNumOfVertices() { // 返回結點個數
		return vertices.size;
	}

	public int getNumOfEdges() { // 返回邊的個數
		return numOfEdges;
	}

	public Object getValue(int v) throws Exception {
		// 返回結點v的數據元素
		return vertices.getData(v);
	}
	/**
	 * 返回邊<v1,v2>的權值
	 * @param v1 邊的行下標
	 * @param v2 邊的列下標 
	 * @return
	 * @throws Exception
	 */
	public int getWeight(int v1, int v2) throws Exception {
		if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
			throw new Exception("參數v1或v2越界出錯!");
		return edge[v1][v2];
	}

	public void insertVertex(Object vertex) throws Exception {
		// 插入結點
		vertices.insert(vertices.size, vertex);
	}
	/**
	 * 插入邊<v1,v2>,權值爲weight	 
	 * @param v1   邊的行下標
	 * @param v2   邊的列下標 	
	 * @param weight 邊的權值 
	 * @throws Exception
	 */
	public void insertEdge(int v1, int v2, int weight) throws Exception {
		if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
			throw new Exception("參數v1或v2越界出錯!");
		edge[v1][v2] = weight; //置邊的權值
		numOfEdges++; //邊的個數加1
	}

	public void deleteEdge(int v1, int v2) throws Exception {
		//刪除邊<v1,v2>
		if (v1 < 0 || v1 > vertices.size || v2 < 0 || v2 > vertices.size)
			throw new Exception("參數v1或v2越界出錯!");
		if (edge[v1][v2] == maxWeight || v1 == v2)
			throw new Exception("該邊不存在!");

		edge[v1][v2] = maxWeight; // 置邊的權值爲無窮大
		numOfEdges--; // 邊的個數減1
	}

	public int getFirstNeighbor(int v) throws Exception {
		// 取結點v的第一個鄰接結點。若存在返回該結點的下標序號,否則返回-1
		if (v < 0 || v >= vertices.size)
			throw new Exception("參數v越界出錯!");
		for (int col = 0; col < vertices.size; col++)
			if (edge[v][col] > 0 && edge[v][col] < maxWeight)
				return col;
		return -1;
	}

	public int getNextNeighbor(int v1, int v2) throws Exception {
		// 取結點v1的鄰接結點v2後的鄰接結點
		// 若存在返回該結點的下標序號,否則返回-1
		if (v1 < 0 || v1 >= vertices.size || v2 < 0 || v2 >= vertices.size)
			throw new Exception("參數v1或v2越界出錯!");
		for (int col = v2 + 1; col < vertices.size; col++)
			if (edge[v1][col] > 0 && edge[v1][col] < maxWeight)
				return col;
		return -1;
	}
	/**
	 * 深度優先遍歷
	 * @param v
	 * @param visited
	 * @param vs
	 * @throws Exception
	 */
	private void depthFirstSearch(int v, boolean[] visited, Visit vs) throws Exception {
		// 連通圖以v爲初始結點序號、訪問操作爲vs的深度優先遍歷
		// 數組visited標記了相應結點是否已訪問過,0表示未訪問,1表示已訪問
		vs.print(getValue(v)); // 訪問該結點
		visited[v] = true; // 置已訪問標記

		int w = getFirstNeighbor(v); // 取第一個鄰接結點
		while (w != -1) { // 當鄰接結點存在時循環
			if (!visited[w]) // 如果沒有訪問過
				depthFirstSearch(w, visited, vs); // 以w爲初始結點遞歸遍歷
			w = getNextNeighbor(v, w); // 取下一個鄰接結點
		}
	}
	/**
	 * 廣度優先遍歷
	 * @param v
	 * @param visited
	 * @param vs
	 * @throws Exception
	 */
	private void broadFirstSearch(int v, boolean[] visited, Visit vs) throws Exception {
		// 連通圖以v爲初始結點序號、訪問操作爲vs的廣度優先遍歷
		// 數組visited標記了相應結點是否已訪問過,0表示未訪問,1表示已訪問
		int u, w;
		SeqQueue queue = new SeqQueue(); // 創建順序隊列queue

		vs.print(getValue(v)); // 訪問結點v
		visited[v] = true; // 置已訪問標記

		queue.append(new Integer(v)); // 結點v入隊列
		while (!queue.isEmpty()) { // 隊列非空時循環
			u = ((Integer) queue.delete()).intValue(); // 出隊列
			w = getFirstNeighbor(u); // 取結點u的第一個鄰接結點
			while (w != -1) { // 當鄰接結點存在時循環
				if (!visited[w]) { // 若該結點沒有訪問過
					vs.print(getValue(w)); // 訪問結點w
					visited[w] = true; // 置已訪問標記
					queue.append(new Integer(w)); // 結點w入隊列
				}

				// 取結點u的鄰接結點w的下一個鄰接結點
				w = getNextNeighbor(u, w);
			}
		}
	}
	/**
	 * 非連通圖的深度優先遍歷
	 * @param vs
	 * @throws Exception
	 */
	public void depthFirstSearch(Visit vs) throws Exception {
		boolean[] visited = new boolean[getNumOfVertices()];
		for (int i = 0; i < getNumOfVertices(); i++)
			visited[i] = false; // 置所有結點均未訪問過
		for (int i = 0; i < getNumOfVertices(); i++)
			// 對每個結點循環
			if (!visited[i]) // 如果該結點未訪問
				depthFirstSearch(i, visited, vs);// 以結點i爲初始結點深度優先遍歷
	}
	/**
	 * 非連通圖的廣度優先遍歷
	 * @param vs
	 * @throws Exception
	 */
	public void broadFirstSearch(Visit vs) throws Exception {
		boolean[] visited = new boolean[getNumOfVertices()];
		for (int i = 0; i < getNumOfVertices(); i++)
			visited[i] = false; // 置所有結點均未訪問過
		for (int i = 0; i < getNumOfVertices(); i++)
			// 對每個結點循環
			if (!visited[i]) // 如果該結點未訪問過
				broadFirstSearch(i, visited, vs);// 以結點i爲初始結點廣度優先遍歷
	}
}

package cn.ls.path;

import cn.ls.graph.AdjMWGraph;
import cn.ls.graph.RowColWeight;
/**
 * 
 *測試類
 */
public class Exam8_4 {
	static final int maxVertices = 100;

	public static void createGraph(AdjMWGraph g, Object[] v, int n, RowColWeight[] rc, int e) throws Exception {
		for (int i = 0; i < n; i++)
			g.insertVertex(v[i]);
		for (int k = 0; k < e; k++)
			g.insertEdge(rc[k].row, rc[k].col, rc[k].weight);
	}

	public static void main(String[] args) {
		AdjMWGraph g = new AdjMWGraph(maxVertices);
		Character[] a = { new Character('A'), new Character('B'), new Character('C'), 
				new Character('D'), new Character('E'), new Character('F') };
		RowColWeight[] rcw = { new RowColWeight(0, 2, 5), new RowColWeight(0, 3, 30), 
				new RowColWeight(1, 0, 2), new RowColWeight(1, 4, 8), new RowColWeight(2, 1, 15), 
				new RowColWeight(2, 5, 7), new RowColWeight(4, 3, 4), new RowColWeight(5, 3, 10), 
				new RowColWeight(5, 4, 18) };
		int n = 6, e = 9;//6個結點9條邊

		try {
			createGraph(g, a, n, rcw, e);

			int[] distance = new int[n];
			int[] path = new int[n];

			Dijkstra.dijkstra(g, 0, distance, path);

			System.out.println("從頂點A到其他各頂點的最短距離爲:");
			for (int i = 1; i < n; i++)
				System.out.println("到頂點" + g.getValue(i) + "的最短距離爲:" + distance[i]);

			System.out.println("從頂點A到其他各頂點的前一頂點分別爲:");
			for (int i = 0; i < n; i++)
				if (path[i] != -1)
					System.out.println("到頂點" + g.getValue(i) + "的前一頂點爲:" + g.getValue(path[i]));
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

測試結果:

從頂點A到其他各頂點的最短距離爲:
到頂點B的最短距離爲:20
到頂點C的最短距離爲:5
到頂點D的最短距離爲:22
到頂點E的最短距離爲:28
到頂點F的最短距離爲:12
從頂點A到其他各頂點的前一頂點分別爲:
到頂點B的前一頂點爲:C
到頂點C的前一頂點爲:A
到頂點D的前一頂點爲:F
到頂點E的前一頂點爲:B
到頂點F的前一頂點爲:C

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