The only difference I see is that Prim's algorithm stores a minimum cost edge whereas Dijkstra's algorithm stores the total cost from a source vertex to the current vertex.
Dijkstra 为您提供了一条从源节点到目标节点的路径,从而使成本最小化。然而 Prim 的算法给你一个最小生成树,所有的节点都是连接的,总成本是最小的。
Prim 的算法为图构造了一个 最小生成树,这是一个连接图中所有节点的树,并且在连接所有节点的所有树中总开销最小。然而,MST 中任意两个节点之间的路径长度可能不是原始图中这两个节点之间的最短路径。例如,如果您希望将图中的节点物理连接起来,以最低的总成本向它们提供电力,MST 就很有用。两个节点之间的路径长度可能不是最优的,这并不重要,因为您所关心的只是它们是连接的这一事实。
基本算法之间的关键区别在于其不同的边缘选择准则。一般来说,它们都使用一个优先级队列来选择下一个节点,但是它们有不同的标准来选择当前处理节点的相邻节点: Prim 的算法要求下一个相邻节点也必须保存在队列中,而 Dijkstra 的算法则没有:
def dijkstra(g, s):
q <- make_priority_queue(VERTEX.distance)
for each vertex v in g.vertex:
v.distance <- infinite
v.predecessor ~> nil
q.add(v)
s.distance <- 0
while not q.is_empty:
u <- q.extract_min()
for each adjacent vertex v of u:
...
def prim(g, s):
q <- make_priority_queue(VERTEX.distance)
for each vertex v in g.vertex:
v.distance <- infinite
v.predecessor ~> nil
q.add(v)
s.distance <- 0
while not q.is_empty:
u <- q.extract_min()
for each adjacent vertex v of u:
if v in q and weight(u, v) < v.distance:// <-------selection--------
...
MST-PRIM (G, w, r) {
for each key ∈ G.V
u.key = ∞
u.parent = NIL
r.key = 0
Q = G.V
while (Q ≠ ø)
u = Extract-Min(Q)
for each v ∈ G.Adj[u]
if (v ∈ Q)
alt = w(u,v) <== relax function, Pay attention here
if alt < v.key
v.parent = u
v.key = alt
}
迪克斯特拉:
Dijkstra (G, w, r) {
for each key ∈ G.V
u.key = ∞
u.parent = NIL
r.key = 0
Q = G.V
while (Q ≠ ø)
u = Extract-Min(Q)
for each v ∈ G.Adj[u]
if (v ∈ Q)
alt = w(u,v) + u.key <== relax function, Pay attention here
if alt < v.key
v.parent = u
v.key = alt
}
箭头指出了唯一的区别,那就是松弛函数。
Prim 搜索最小生成树,只关心覆盖所有顶点的最小总边。放松功能是 alt = w(u,v)
Dijkstra,它搜索最小路径长度,所以它关心边缘积累。放松功能是 alt = w(u,v) + u.key
Inputs:
G: Graph
s: Starting vertex (any for Prim, source for Dijkstra)
f: a function that takes vertices u and v, returns a number
Generic(G, s, f)
Q = Enqueue all V with key = infinity, parent = null
s.key = 0
While Q is not empty
u = dequeue Q
For each v in adj(u)
if v is in Q and v.key > f(u,v)
v.key = f(u,v)
v.parent = u
对于普里姆,传递 f = w(u, v)和对于 Dijkstra 通过 f = u.key + w(u, v)。
另一个有趣的事情是,上述 Generic 也可以实现广度优先搜索(bFS) ,尽管这样做有些过火,因为并不真正需要昂贵的优先级队列。要将上述通用算法转换为 BFS,传递 f = u.key + 1,它与将所有权值强制转换为1相同(即 BFS 给出从 A 点到 B 点遍历所需的最小边数)。
直觉
这里有一种思考上述通用算法的好方法: 我们从两个存储桶 A 和 B 开始,首先,将所有顶点放在 B 中,这样存储桶 A 就是空的。然后我们将一个顶点从 B 移动到 A。现在看看从 A 的顶点到 B 的顶点的所有边。我们从这些交叉边中选择一条边,并将相应的顶点从 B 移动到 A。重复这个过程,直到 B 为空。
实现这个想法的一种强制方法是维护 A 中的顶点的优先级队列,这个队列可以交叉到 B 中。显然,如果图不稀疏,这将是一件麻烦的事情。所以问题是我们能不能维持顶点的优先级队列?这实际上我们可以作为我们的决定,最终是从 B 中选择哪个顶点。
历史背景
有趣的是,这两种算法背后的通用技术在概念上可以追溯到1930年,即使当时还没有电子计算机。
故事开始于奥塔卡博尔 vka 谁需要一个算法的家庭朋友试图找出如何连接城市在摩拉维亚(现在的捷克共和国的一部分)以最低成本的电线。1926年,他在一本与数学有关的杂志上发表了他的算法,当时计算机科学还不存在。这引起了 Vojt ch Jarník 的注意,他想到了对 Bor vka 算法的改进,并在1930年发表了它。事实上,他发现了同样的算法,我们现在知道,作为普里姆的算法谁重新发现了它在1957年。
你初始化 Prim 时使用了一个源顶点,即 是的,也就是 Prim.new(s); 是的可以是任何一个顶点,不管 是的是什么,最终的结果都是相同的,也就是最小生成树的边缘(mST)。为了获得 MST 边缘,我们调用方法 edges()。
You initialize Dijkstra with a source vertex, s, i.e., Dijkstra.new(s) that you want to get shortest path/distance to all other vertices. The end results, which are the shortest path/distance from s to all other vertices; are different depending on the s. To get the shortest paths/distances from s to any vertex, v, we call the methods distanceTo(v) and pathTo(v) respectively.
2 2
(s) o ----- o ----- o (t)
| |
-----------------
3
Let's say each of the top edges has the cost of 2, and the bottom edge has cost of 3, then Dijktra will tell us to the take the bottom path, since we don't care about the middle node. On the other hand, Prim will return us a MST with the top 2 edges, discarding the bottom edge.
这种差异也反映在实现中的细微差异上: 在 Dijkstra 的算法中,需要有一个簿记步骤(对于每个节点) ,以便在吸收一个新节点后更新来自 是的的最短路径,而在 Prim 的算法中,没有这种需要。
For example:- Consider a situation where you wan't to create a huge network for which u will be requiring a large number of wires so these counting of wire can be done using 最小生成树(普里姆算法或克鲁斯卡尔算法)(也就是说,它会给你最少的电线数量,以最低的成本创建巨大的有线网络连接)。