狄克斯特拉算法(Dijkstra’s algorithm)
为了什么而存在
这个算法主要用在图这种数据结果中,而且是比较特殊的 加权图 。这里引用《算法图解》中的一个图片来进行讲解。
比如我们要从双子峰到金门大桥,我们想在最短的时间内到达。这里就不是段数最少的问题了,因为条道路都有耗时的时间,可以看成每条路的权重,你要找的就是最后权重累加最少的路径。
上面属于加权图,计算这种加权图的最短路径的方法就是用 狄克斯特拉算法 。
如果不是加权图,那么用的就是广度优先搜索来解决最短路径问题。以下的图片就是非加权图:
在搞清楚这个算法之前,我们先要知道**“图”** 这种数据结构。
图这种数据结构,可以说有着非常大的派系了,比如下面这中图属于无向图:
下面这种带着方向的,我们叫它有向图:
如果在每条边加上权重,我们就叫它 加权有向图 ,这个属于,
如果无向的,我们一般叫做带权图:
如何实现
(1) 找出最便宜的节点,即可在最短时间内前往的节点。
(2) 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。
(3) 重复这个过程,直到对图中的每个节点都这样做了。
(1)首先,我们需要确定初始点在哪里
(2)根据初始点,我们先把和初始点接近的点的开销确定了
(3)然后取出开销最小的那个点,然后开始得到开销最小的点的本身的开销和它到达它的邻居的开销。
(4)然后更新他的邻居的开销。
(5)接着再寻找出还未处理点中再寻找最小的开销的那个点继续执行(2)的步骤。
Show The code
public int getShortestPath( Map<String, HashMap<String, Integer>> graph, String start, String end) {
HashSet<String> processedNodeSet = new HashSet<>();
HashMap<String, Integer> costsMap = new HashMap<>();
HashMap<String, String> parentsMap = new HashMap<>();
for (String node : graph.keySet()) {
costsMap.put(node, Integer.MAX_VALUE);
}
HashMap<String, Integer> startNeighbors = graph.get(start);
for (String item : startNeighbors.keySet()) {
costsMap.put(item, startNeighbors.get(item));
}
String node = findLowestCostNode(costsMap, processedNodeSet);
while (node != null) {
Integer cost = costsMap.get(node);
HashMap<String, Integer> neighbors = graph.get(node);
for (String key : neighbors.keySet()) {
int newCost = cost + neighbors.get(key);
if (newCost < costsMap.get(key)) {
costsMap.put(key, newCost);
parentsMap.put(key, node);
}
}
processedNodeSet.add(node);
node = findLowestCostNode(costsMap, processedNodeSet);
}
return costsMap.get(end);
}
public String findLowestCostNode(Map<String, Integer> costs, HashSet<String> processedNodeSet) {
String ans = null;
int lowCost = Integer.MAX_VALUE;
for (String key : costs.keySet()) {
if (costs.get(key) < lowCost && !processedNodeSet.contains(key)) {
ans = key;
lowCost = costs.get(key);
}
}
return ans;
}
时间复杂度:
时间复杂度:
几个关键疑问
为什么该算法不能用在负的带权的边的背景之下?
因为我们都是从当前的条件中选择开销最小的点来处理,如果有负的开销的点,那么就意味着,我们不能够每次都选取当前的最小节点来进行更新了,因为当某个边右通向负的带权的点的时候,就会产生新的最小开销的点,而那个点极有可能是我们已经处理过的。所以就导致就算更新了这个点,我们也依然没法使用。
该算法是否有优化的空间?
我们从算法中可以知道,我们每次其实都在不断的重复寻找未处理的最小开销的点的这个步骤,寻找最小点,这个思想就像我们之前的学到的最小堆的使用思想一致,每次只要花 就能在每次取出最小点。以后有空可以实现一下子。。