贪心算法实例(三)求解单源最短路径问题(Dijkstra算法)
问题
给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其他各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。
例如,Patrick教授希望找到一条从菲尼克斯(Phoenix)到印第安纳波利斯(Indianapolis)的最短路径。给定一幅美国的道路交通图,上面标有所有相邻城市之间的距离,Patrick教授怎样才能找出这样一条最短的路径呢?一种可能的办法当然是,先将从菲尼克斯到印第安纳波利斯的所有路径都找出来,将每条路径上的距离累加起来,然后选择其中最短的路径。但是,即使在不允许环路的情况下,也可以看得出来,Patrick教授需要检查无数种可能的路径,而其中的大多数路径根本不值得检查 —— 一条从菲尼克斯经过西雅图再到印第安纳波利斯的路径显然不符合要求,因为西雅图已经偏离了目标方向好几百英里。
思路
Dijkstra算法
Dijkstra算法是贪心算法,解决的是带权重的有向图上的单源最短路径问题:设置顶点集合S,并不断地作贪心选择来扩充这个集合 —— 一个顶点属于集合S当且仅当从源顶点到该顶点的最短路径长度已知。
- S中仅含有源顶点 —— 设u是G的某一个顶点,把从源顶点到u,且中间只经过S中顶点的路称为从源顶点到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。
- Dijkstra算法每次从[V-S]中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改(松弛操作)。
- 一旦S包含了所有V中顶点,dist就记录了从源顶点到所有其他顶点之间的最短路径长度。
伪代码
DIJKSTRA. (G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S = Φ 3 Q = G.V 4 while Q ≠ Φ 5 u = EXTRACT-MIN(Q) 6 S = S∪{u} 7 for each vertex v ∈ G.Adj[u] 8 RELAX(u, v, w)
实现
# Python 3: Dijkstra class Vertex(object): def __init__(self, name: str, value: int = -1): self.name: str = name self.value: int = value self.dist = float('Inf') # shortest distance from source vertex. self.prev = None # previous vertex with shortest distance class Edge(object): def __init__(self, start: Vertex, end: Vertex, weight: int): self.start: Vertex = start self.end: Vertex = end self.weight: int = weight class Graph: def __init__(self, V, E): """ :param V: a collection of vertex :param E: a collection of edges """ self.V = V self.E = E def dijkstra(G: Graph, s: int): G = initialize_single_source(G, s) S = [] # a sorted collection of vertex with shortest paths from the source vertex to each vertex Q = G.V while Q: u = extract_min(Q) S.append(u) Q.remove(u) for e in G.E: e.end = relax(e.start, e.end, e.weight) return S def extract_min(Q): """ extract the vertex with the shortest path to current vertex. :param Q: a collection of vertex :return: """ tv = None tdist = float('Inf') for v in Q: if v.dist < tdist: tdist = v.dist tv = v return tv def initialize_single_source(G, s: int): """ :param G: :param s: source vertex """ for v in G.V: v.dist = float('Inf') v.prev = None G.V[s].dist = 0 return G def relax(u, v, w): """ :param u: previous (possibly) vertex u :param v: current vertex v :param w: weight of edge from vertex u to vertex v. :return: """ if v.dist > u.dist + w: v.dist = u.dist + w v.prev = u return v """ s = Vertex('s', -1) t = Vertex('t', -1) y = Vertex('y', -1) x = Vertex('x', -1) z = Vertex('z', -1) V = [s, t, y, x, z] E = [ Edge(s, t, 10), Edge(s, y, 5), Edge(t, y, 2), Edge(t, x, 1), Edge(y, t, 3), Edge(y, x, 9), Edge(y, z, 2), Edge(x, z, 4), Edge(z, s, 7), Edge(z, x, 6), ] G = Graph(V, E) S = dijkstra(G, 0) for s in S: print(f'{s.name}: {s.dist}') """