算法詳解之狄克斯特拉算法

上一篇文章,我們瞭解了廣度優先搜索算法(BFS),BFS主要用來解決圖的可達路徑驗證和最小路徑問題,即從一個頂點A到另一個頂點B,是否有可達路徑,如果有那麼求出其到達的最少步驟。那麼這裏的最短路徑就如果加上時間或者其他元素來表示的話,真的就是最短的嗎?

這裏,我們不妨將達到各個頂點的邊加上一個花費時間來修飾,這裏用來修飾邊的元素,我們稱之爲權重,加上權重的邊即爲加權邊,如下圖所示:

狄克斯特拉
還是上一篇文章的圖,但是我們做了一些改動,這裏我們使用有向圖並且給各個邊加上了權重,這裏的權重代表的是每條邊需要花費的時間,這樣一來,我們上一篇文章中求出的 “最短路徑” 還合適嗎?

一目瞭然,加上權重之後,我們之前的最短路徑已經不是真的 “最短”,那麼我們需要引入一個專門的算法來處理這種情況——狄克斯特拉算法

算法簡介

狄克斯特拉算法 是用來處理加權圖的最短路徑問題的一種圖遍歷算法,其具體思路如下:

  1. 找出 “最便宜” 的節點,即可在最小權重下到達的節點;
  2. 更新該節點的鄰居的開銷;
  3. 重複這個過程,直到對圖中所有的節點都這樣做了。
  4. 計算最終的路徑

我們來看使用這種思路,解決上圖的最短路徑問題,分步拆解如下:

第一步:

從起點 Start出發,找出它能到達的最便宜的節點,從圖中看出,節點Start相鄰的節點有A 和 B,到達A節點需要5分鐘,到達B節點需要 2分鐘,至於其他節點對於Start節點來說目前並不清楚,我們假設爲無窮大,我們用一個節點開銷表來表示,如下所示:

節點 耗時
A 5
B 2
C \infty
D \infty
E \infty
F \infty
End \infty

第二步:

由上表我們知道Start節點到B節點的開銷最低爲2,那麼更新B節點的開銷爲2,我們用一個節點-開銷表來表示其中的關係,如下表所示:

父節點 節點 開銷
Start A 5
Start B 2

接着我們從B節點出發找最便宜開銷節點,如下表所示:

節點 開銷
D 2
E 7

那麼我們修改B的鄰居節點的開銷爲:

父節點 節點 開銷
B D 5
B E 9

第三步:

接着重複上述步驟,直到得到如下表格:

父節點 節點 開銷
Start B 2
B D 5
D C 6
C End 8

代碼實現

Python實現

def find_lowest_cost_node(costs,processed):
	lowest_cost = float('inf')  # 默認節點開銷爲無窮大
	lowest_cost_node = None   # 默認最小開銷節點爲None
	for node in costs:
		cost = costs[node]  # 當前節點開銷
		if cost < lowest_cost and node not in processed:
			lowest_cost = cost
			lowest_cost_node = node
	return lowest_cost_node

def dijkstra(start,end,graph):
	costs = graph[start]  # 起點到鄰居節點的開銷映射
	parents = {}  # 保存最小開銷節點與父節點的關係映射
	processed = []  # 已處理節點列表,防止節點重複處理
	# 查找起點可以到達的最小開銷節點
	node = find_lowest_cost_node(costs,processed)
	while node is not None:
		cost = costs[node]  # 
		neighbors = graph[node]  # 當前最小開銷節點的鄰居節點
		for next_node in neighbors.keys():
			# 從最小開銷節點到達其鄰居節點的合併開銷
			new_cost = cost + neighbors[next_code]
			if next_node not in costs or costs[next_code] > new_cost:
				costs[next_code] = new_cost
				parents[next_code] = node
		processed.append(node)
		node = find_lowest_cost_node(costs,processed)
	path = [end]  # 最短路徑節點列表
	parent = parents[end]
	while parent:
		if parent == start:
			path.append(parent)
			break
		path.append(parent)
		if parent in parents.keys():
			parent = parents[parent]
		else:
			break
	path.append(start)  # 加入起點
	print('最短路徑爲 %s' % (' -> '.join(path[::-1])))
	print('最小開銷爲: %d' % costs[end])


if __name__ == '__main__':
	graph = dict()
    graph['Start'] = dict()
    graph['Start']['A'] = 5
    graph['Start']['B'] = 2

    graph['A'] = dict()
    graph['A']['C'] = 3

    graph['B'] = dict()
    graph['B']['D'] = 3
    graph['B']['E'] = 7

    graph['C'] = dict()
    graph['C']['End'] = 1

    graph['D'] = dict()
    graph['D']['C'] = 1

    graph['E'] = dict()
    graph['E']['F'] = 2

    graph['F'] = dict()
    graph['F']['End'] = 1

    graph['End'] = {}
    dijkstra('Start', 'End', graph)

輸出結果:

最短路徑爲: Start -> B -> D -> C -> End
最小開銷爲:  7

Java實現

//查找開銷最小的節點
private String findLowestCostNode(Map<String,Integer> costs,Set<String> processed) {
	int lowestCost = Integer.MAX_VALUE;
	String lowestCostNode = null;
	for (String node : costs.keySet()) {
         int cost = costs.get(node);
         if (cost < lowest_cost && !processed.contains(node)) {
              lowest_cost = cost;
              lowest_cost_node = node;
         }
     }
    return lowest_cost_node;
}

/**
 * 使用狄克斯特拉算法查找加權邊最優路徑
 * @param start  起點
 * @param end   終點
 * @param graph  圖結構散列表
 */
public  void search(String start,String end,Map<String,Map<String,Integer>> graph) {
        //起點的鄰居節點的開銷映射
        Map<String, Integer> costs = graph.get(start);
        //各個最小開銷節點的父節點
        Map<String,String> parents = new HashMap<>();
        //已處理節點的集合
        Set<String> processed = new HashSet<>();

        //查找起點可達的最小開銷節點
        String node = findLowestCostNode(costs,processed);
        while (node != null && graph.get(node) != null) {
            int cost = costs.get(node);
            Map<String,Integer> neighbors = graph.get(node);
            for (String n : neighbors.keySet()) {
                int new_cost = cost + neighbors.get(n);
                if (!costs.containsKey(n) || costs.get(n) > new_cost) {
                    costs.put(n,new_cost);
                    parents.put(n,node);
                }
            }
            processed.add(node);
            node = findLowestCostNode(costs,processed);
        }
        print(start,end,parents,costs.get(end));
}

private void print(String start,String end,Map<String,String> parents,int cost) {
	Stack<String> stack = new Stack<>();
	String parent = parents.get(end);
	while (parent != null) {
		if (start.equalsIgnoreCase(parent)) {
			stack.push(parent);
            break;
		}
		stack.push(parent);
		parent = parents.get(parent);
	}
	StringBuffer path = new StringBuffer();
	while (!stack.empty()) {
		String node = stack.pop();
		if (path.length != 0) {
			path.append(" -> ");
		}
		path.append(node);
	}
	System.out.println("最優路徑: " + start + " -> " + path.toString() + " -> " + end);
    System.out.println("總開銷爲: " + cost);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章