基于Dijkstra算法的最短路问题求解
迪杰斯特拉算法是由荷兰计算机科学家在1956年发现的算法,此算法使用类似广度优先搜索的方法解决了带权图的单源最短路径问题。它是一个贪心算法。
一、Dijkstra算法原理、步骤
- 算法输入:赋权(非负权)有向图 G = ( V , A ) G=(V,A) G=(V,A),指定一个起点 v 0 v_0 v0(即从顶点 v 0 v_0 v0开始计算)
- 初始化:当前点
v
i
v_i
vi=起点
v
0
v_0
v0,数组
P
P
P和
T
T
T,数组
P
P
P中只有起点
v
0
v_0
v0,即
P
=
{
v
0
}
P=\{ v_0\}
P={v0};数组T中是除起点
v
0
v_0
v0之外的顶点,并且数组T中记录各顶点到起点
v
0
v_0
v0的距离。如果顶点与起点
v
0
v_0
v0不相邻,距离为无穷大。
P的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而T则是记录还未求出最短路径的顶点(以及该顶点到起点V_0的距离)
- 若 T = { } T=\{\} T={},算法停止,输出集合 P P P;否则转入step4。
- 获取当前点v_i的邻接点,更新邻接点的距离,从数组T中找出路径最短的顶点 v k v_k vk,并将其加入到数组P中;同时,从数组T中移除顶点 v k v_k vk。
- 令当前点 v i v_i vi= v k v_k vk,执行step3。
二、基于Dijkstra算法的最短路问题求解(Python)
参考https://blog.youkuaiyun.com/xiaoxi_hahaha/article/details/110257368中的题目:
完整代码如下:
import numpy as np
inf = np.inf
def dijkstra(graph, source):
permanent = {source: 0} # 永久标号
temporary = {} # 临时标号
nodes = graph.keys() # 顶点集合
# 初始化
for node in nodes:
if node != source:
temporary[node] = inf
current_node = source
while temporary: # 若临时标号集合不为空,算法继续
neighbor_weight_list = graph[current_node]
for n, w in neighbor_weight_list:
if permanent[current_node] + w < temporary[n]:
temporary[n] = permanent[current_node] + w
min_key = None
min_val = 1e6
for k, v in temporary.items():
if v < min_val:
min_val = v
min_key = k
temporary.pop(min_key)
permanent[min_key] = min_val
current_node = min_key
print(permanent)
if __name__ == "__main__":
# 有向图的邻接表,点:【(临界点,权值),(邻接点,权值)】
graph = {
0: [(1, 5), (2, 2)],
1: [(3, 1), (4, 6)],
2: [(3, 6), (5, 8)],
3: [(4, 1), (5, 2)],
4: [(6, 7)],
5: [(6, 3)],
6: []
}
dijkstra(graph, 0)
三、基于Dijkstra算法的最短路问题求解(Java)

为方便说明,做以下定义:
- 顶点:赋权图中的点,顶点集合 V = { A , B , C , D , E , F } V=\{A,B,C,D,E,F\} V={A,B,C,D,E,F}。
- 标号:这里标号对应于运筹学中P标号。对某个点 v i , ∀ i ∈ V v_i,\forall i \in V vi,∀i∈V的标号为从起点到 v i v_i vi的最短距离。如对 v i = B v_i=B vi=B,则 B . d i s t a n c e = 5 B.distance = 5 B.distance=5。
- 邻居:定义 v i v_i vi的邻居为:和 v i v_i vi直接相连接且未被标号的点。
3.1算法流程
- 算法输入:顶点集合,邻接矩阵
- 初始化每个顶点 { A , B , C , D , E , F } \{A,B,C,D,E,F\} {A,B,C,D,E,F},将所有顶点入队。 v 1 . d i s t a n c e = 0 , v i . d i s t a n c e = ∞ , ∀ v i ∈ V ∖ { v 1 } , v i . m a r k e d = f a l s e v_1.distance =0,v_i.distance=∞, \forall v_i\in V \setminus \{v_1\},v_i.marked=false v1.distance=0,vi.distance=∞,∀vi∈V∖{v1},vi.marked=false。将所有顶点入优先队列再弹出起点;而上面Python求解中,起点入P集合,其他顶点入T集合是一样的。
- 弹出队头顶点 j j j,标记为已访问。
- 获取队头的邻接点。
- 更新邻接点的标号
- 若队列不为空,返回 step2。
3.2实例分析
step1:初始化:所有元素入优先级队列,如图3
step2:
- 弹出队头元素 A = 0 A=0 A=0
- 得到 A A A的邻居, n e i g h b o r s A = { B , C } neighbors_A=\{B,C\} neighborsA={B,C}
- 更新邻居标号,如图4
- 队列
[
B
=
6
,
C
=
3
,
D
=
∞
,
∞
,
F
=
∞
]
[B=6,C=3,D=\infty,\infty,F=\infty]
[B=6,C=3,D=∞,∞,F=∞]不为空。
step3:
-
弹出队列头元素 C = 3 C=3 C=3
-
得到 C C C的邻居, n e i g h b o r s C = { B , D , E } neighbors_C=\{B,D,E\} neighborsC={B,D,E}
-
更新邻居标号,如图5
-
队列 [ B = 5 , D = 6 , E = 7 , F = ∞ ] [B=5,D=6,E=7,F=\infty] [B=5,D=6,E=7,F=∞]不为空。
step4:
- 弹出队列头元素 B B B
- 得到 B B B的邻居, n e i g h b o r s B = { D } neighbors_B=\{D\} neighborsB={D}
- 更新邻居标号, 5 + 5 < 6 5+5<6 5+5<6,D点标号无需更新,如图6
- 队列
[
D
=
6
,
E
=
7
,
F
=
∞
]
[D=6,E=7,F=\infty]
[D=6,E=7,F=∞]不为空。
step5:
-
弹出队列头元素 D D D
-
得到 D D D的邻居, n e i g h b o r s D = { E , F } neighbors_D=\{E,F\} neighborsD={E,F}
-
更新邻居标号,如图7
-
队列 [ E = 7 , F = 9 ] [E=7,F=9] [E=7,F=9]不为空。
step5:
- 弹出队列头元素 E E E
- 得到 D D D的邻居, n e i g h b o r s E = { F } neighbors_E=\{F\} neighborsE={F}
- 更新邻居标号,如图8
- 队列
[
F
=
9
]
[F=9]
[F=9]不为空。
step5:
- 弹出队列头元素 F F F
- 得到 D D D的邻居, n e i g h b o r s F = { N U L L } neighbors_F=\{NULL\} neighborsF={NULL}
- 更新邻居标号
- 队列 [ ] [] []为空,算法结束
注:Dijkstra算法应用优先队列的原因:每次弹出的队列的元素是有优先级的,即标号最小的优先弹出,这里也看出Dijkstra就是一种贪婪算法。
3.3完整代码
- Vertex类:对赋权有向图中的顶点进行封装,每个顶点有3个属性:顶点名称、永久标号、是否被访问。
- Graph类:赋权有向图,有顶点和边两个属性。
- Dijkstra类:算法运行类。
- Main类:程序运行类,程序开始,初始化顶点,和边的信息。有顶点和边构建图。将图传入算法中运行即可。
import java.util.*;
public class Dijkstra {
private final Graph graph;
private final List<Vertex> vertices;
private final int[][] edges;
private Queue<Vertex> temporary;
private Set<Vertex> permanent;
Dijkstra(Graph graph) {
this.graph = graph;
this.vertices = graph.getVertices();
this.edges = graph.getEdges();
}
private void init(int start) {
vertices.get(start).setDistance(0);
temporary = new PriorityQueue<>(Comparator.comparingInt(Vertex::getDistance));
temporary.addAll(vertices);
permanent = new TreeSet<>(Comparator.comparingInt(Vertex::getDistance)) {{
add(vertices.get(start));
}};
System.out.printf("initialize: %s\n", temporary);
}
void search(int start) {
init(start);
System.out.println("dijkstra searching...");
while (!temporary.isEmpty()) {
Vertex vertex = temporary.poll(); // 出队列
vertex.setMarked(true);
List<Vertex> neighbors = graph.getNeighbors(vertex);
updateDistance(vertex, neighbors);
}
System.out.println(permanent);
}
private void updateDistance(Vertex vertex, List<Vertex> neighbors) {
for (Vertex neighbor : neighbors) {
updateDistance(vertex, neighbor);
}
}
private void updateDistance(Vertex vertex, Vertex neighbor) {
int distance = edges[vertices.indexOf(vertex)][vertices.indexOf(neighbor)] + vertex.getDistance();
if (distance < neighbor.getDistance()) {
temporary.remove(neighbor);
neighbor.setDistance(distance);
permanent.add(neighbor);
temporary.add(neighbor);
}
}
void writeAns() {
System.out.println("\n initial data: ");
for (int i = 0; i < edges.length; i++) {
for (int j = 0; j < edges[i].length; j++) {
if (edges[i][j] == Integer.MAX_VALUE) {
System.out.print("M" + " ");
} else {
System.out.print(edges[i][j] + " ");
}
}
System.out.println();
}
}
}
class Graph {
private List<Vertex> vertices;
private int[][] edges;
public Graph(String[] vertexName, int[][] adjacentList) {
this.vertices = new ArrayList<>();
this.edges = adjacentList;
Vertex[] vertices = new Vertex[vertexName.length];
for (int i = 0; i < vertexName.length; i++) {
vertices[i] = new Vertex(vertexName[i]);
}
Collections.addAll(this.vertices, vertices);
}
public Graph() {
}
private Vertex getVertex(int index) {
return vertices.get(index);
}
List<Vertex> getNeighbors(Vertex v) {
List<Vertex> neighbors = new ArrayList<>();
int index = vertices.indexOf(v);
for (int i = 0; i < vertices.size(); i++) {
if (i == index) continue;
if (edges[index][i] < Integer.MAX_VALUE) {
Vertex neighbor = getVertex(i);
if (!neighbor.isMarked())
neighbors.add(neighbor);
}
}
return neighbors;
}
public List<Vertex> getVertices() {
return vertices;
}
public int[][] getEdges() {
return edges;
}
}
class Vertex {
private String name;
private int distance;
private boolean marked;
Vertex(String name) {
this.name = name;
this.distance = Integer.MAX_VALUE;
setMarked(false);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDistance() {
return distance;
}
public void setDistance(int path) {
this.distance = path;
}
public boolean isMarked() {
return marked;
}
public void setMarked(boolean marked) {
this.marked = marked;
}
@Override
public String toString() {
return name + "=" + distance;
}
}
class Main {
private final static int M = Integer.MAX_VALUE;
public static void main(String[] args) {
// 节点名称
String[] vertexName = new String[]{"A", "B", "C", "D", "E", "F"};
// 邻接矩阵
int[][] adjacentList = {
{0, 6, 3, M, M, M},
{6, 0, 2, 5, M, M},
{3, 2, 0, 3, 4, M},
{M, 5, 3, 0, 5, 3},
{M, M, 4, 5, 0, 5},
{M, M, M, 3, 5, 0}
};
Graph graph = new Graph(vertexName, adjacentList);
Dijkstra dijkstra = new Dijkstra(graph);
dijkstra.search(0);
dijkstra.writeAns();
}
}
运行结果如下:
[A=0, C=3, B=5, D=6, E=7, F=9]
参考
- https://blog.youkuaiyun.com/xiaoxi_hahaha/article/details/110257368
- https://zhuanlan.zhihu.com/p/346558578