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

上一篇文章,我们了解了广度优先搜索算法(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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章