* <第7章> 狄克斯特拉算法 </第7章>
* 7.1 使用狄克斯特拉算法,包含4个步骤
* (1)找出最便宜的节点,即可在最短时间内前往的节点
* (2)对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销
* (3)重复这个过程,直到对图中的每个节点都这样做了
* (4)计算最终路径
* 7.2 术语
* (1)狄克斯特拉算法用于每条边都有关联数字的图,这些数字称为权重(weight)
* (2)带权重的图称为加权图(weighted graph),不带权重的图称为非加权图(unweighted graph)
* (3)狄克斯特拉算法只适用于有向无环图(directed acyclic graph,DAG)
* 7.3 负权边
* (1)狄克斯特拉算法这样假设:对于处理过的节点,没有前往该节点的更短路径。这种假设仅在没有负权边时才成立
* (2)不能将狄克斯特拉算法用于包含负权边的图
* (3)在包含负权边的图中,要找出最短路径,可使用另一种算法——贝尔曼-福德算法(Bellman-Ford algorithm)
* 7.4 实现 示例:{@link Dixtra}
* (1)三个散列表:图、开销表、节点表
* (2)一个存储已处理过节点的数组或列表
* (3)随着算法的执行,开销表和节点表将会不断更新
* 7.5 小结
* (1)广度优先搜索用于在非加权图中查找最短路径
* (2)狄克斯特拉算法用于在加权图中查找最短路径
* (3)仅当权重为正时狄克斯特拉算法才管用
* (4)如果图中包含负权边,请使用贝尔曼-福德算法
/**
* 狄克斯特拉算法
*/
public class Dixtra {
// 存储节点、邻居节点和前往邻居节点的开销
private static final Map<String, Map<String, Integer>> graph = new HashMap<>();
// 存储已知的每个节点开销
private static final Map<String, Integer> costs = new HashMap<>();
// 存储每个节点的父节点
private static final Map<String, String> parents = new HashMap<>();
// 存储处理过的节点
private static final List<String> processed = new ArrayList<>();
/**
* 在未处理节点中找出开销最小的节点
*
* @return
*/
private static String findLowestCostNode() {
// 未被处理过的节点
Map<String, Integer> notProcessedCosts = new HashMap<>();
for (Map.Entry<String, Integer> entry : costs.entrySet()) {
if (!processed.contains(entry.getKey())) {
notProcessedCosts.put(entry.getKey(), entry.getValue());
}
}
// 所有节点都处理完了
if (notProcessedCosts.size() == 0) {
return null;
}
// 从未被处理过的节点中找到开销最小的节点
List<Map.Entry<String, Integer>> list = new ArrayList<>(notProcessedCosts.entrySet());
list.sort(Comparator.comparingInt(Map.Entry::getValue));
return list.get(0).getKey();
}
private static void search() {
String node = findLowestCostNode();
while (node != null) {
// 取出节点的开销
int cost = costs.get(node);
// 取出节点的邻居
Map<String, Integer> neighbors = graph.get(node);
if (neighbors != null) {
// 遍历节点的所有邻居
for (String s : neighbors.keySet()) {
// 到达这个邻居节点需要的总开销
int newCost = cost + neighbors.get(s);
// 如果经当前节点前往该邻居更近
if (newCost < costs.get(s)) {
// 更新该邻居的开销
costs.put(s, newCost);
// 同时将该邻居的父节点设置为当前节点
parents.put(s, node);
}
}
}
// 标记处理过的节点
processed.add(node);
// 找出接下来要处理的节点,并循环
node = findLowestCostNode();
}
}
public static void main(String[] args) {
initGraph();
initCosts();
initParents();
search();
System.out.println("开销表:" + costs);
System.out.println("节点表:" + parents);
}
private static void initParents() {
parents.put("a", "start");
parents.put("b", "start");
parents.put("c", "a");
parents.put("d", null);
parents.put("end", null);
}
private static void initCosts() {
costs.put("a", 5);
costs.put("b", 2);
costs.put("c", Integer.MAX_VALUE);
costs.put("d", Integer.MAX_VALUE);
costs.put("end", Integer.MAX_VALUE);
}
private static void initGraph() {
Map<String, Integer> map = new HashMap<>();
map.put("a", 5);
map.put("b", 2);
graph.put("start", map);
map = new HashMap<>();
map.put("c", 4);
map.put("d", 2);
graph.put("a", map);
map = new HashMap<>();
map.put("a", 8);
map.put("d", 7);
graph.put("b", map);
map = new HashMap<>();
map.put("end", 3);
map.put("d", 6);
graph.put("c", map);
map = new HashMap<>();
map.put("end", 1);
graph.put("d", map);
// 终点没有邻居节点
graph.put("end", null);
}
}