如果你有一个图,并且需要找到它的直径(即两个节点之间的最大距离),你怎么能做的很O(log v * (v + e))
复杂。
维基百科说你可以Dijkstra's algorithm
使用binary heap
. 但我不明白这是如何工作的。有人可以解释一下吗?
或者显示一个伪代码?
如果你有一个图,并且需要找到它的直径(即两个节点之间的最大距离),你怎么能做的很O(log v * (v + e))
复杂。
维基百科说你可以Dijkstra's algorithm
使用binary heap
. 但我不明白这是如何工作的。有人可以解释一下吗?
或者显示一个伪代码?
我知道我迟到了一年,但我不相信这些答案中的任何一个都是正确的。OP 在评论中提到边缘未加权;在这种情况下,最好的算法在 O(n ω log n) 时间内运行(其中 ω 是矩阵乘法的指数;Virginia Williams 目前的上限为 2.373)。
该算法利用了未加权图的以下属性。令A为图的邻接矩阵,每个节点都添加了自环。那么 (A k ) ij非零当且仅当 d(i, j) ≤ k。我们可以利用这个事实通过计算 A k的 log n 值来找到图形直径。
以下是该算法的工作原理:设 A 是图的邻接矩阵,每个节点都添加了自循环。设置 M 0 = A。当 M k包含至少一个零时,计算 M k+1 = M k 2。
最终,您会找到一个包含所有非零项的矩阵 M K。我们知道 K ≤ log n 由上面讨论的性质;因此,到目前为止,我们只执行了 O(log n) 次矩阵乘法。我们现在可以继续在 M K = A 2 K和 M K-1 = A 2 K-1之间进行二分搜索。设置 B = M K-1如下。
设置直径 = 2 k-1。对于 i = (K-2 ... 0),执行以下测试:
将 B 乘以 M i并检查结果矩阵是否为零。如果我们找到任何零,则将 B 设置为该矩阵乘积,并将 2 i添加到 DIAMETER。否则,什么也不做。
最后,返回直径。
作为一个次要的实现细节,在执行每个矩阵乘法之后,可能需要将矩阵中的所有非零条目设置为 1,这样这些值就不会变得太大且难以在短时间内写下来。
对于一般 Graph G=(V,E)
,没有O(log V * (V + E))
已知的用于计算直径的时间复杂度算法。当前的最佳解决方案是O(V*V*V)
,例如,通过使用 Floyd Warshall 算法计算所有最短路径。对于稀疏图,即何时E
在 中o(N*N)
,约翰逊算法为您提供O(V*V*log(V)+V*E)
了更好的时间复杂度。
如果您的图表具有某些属性,例如非循环(树),您会变得更好。
所以坏消息是,Dijkstra 在一般情况下是不够的......
正如 eci 所提到的,其中一种解决方案是使用 Floyd-Warshall 算法。如果您需要它的代码,可以在此处找到它的 C++ 版本。
Boost BGL 有一个名为“rcm_queue”的小型扩展双端队列类,可以通过简单的广度优先搜索找到顶点的偏心率,这意味着 O(E) 的复杂性。
http://www.boost.org/doc/libs/1_54_0/boost/graph/detail/sparse_ordering.hpp
由于可以通过遍历所有顶点的偏心率来计算直径,因此可以计算复杂度为 O(V*E) 的图的直径。
特别是对于一个 deg(G) <<< V 非常稀疏的图,我没有找到任何运行时间更好的东西。
我没有研究 Floyd Warshall 算法。我只是在处理一个具有 > 5.000.000 个顶点但任何顶点的最高度数小于 15 的图,我认为,这可能会优于具有 V*V*log(V) 复杂度的算法。
编辑
当然,这仅适用于无向图而不是负加权(甚至仅未加权?我不确定 atm)
图描述——无向无权,n个节点,m条边对于图的直径,我们需要计算所有节点对之间的最短路径。源节点到所有其他节点之间的最短路径可以使用 BFS 算法计算无向和未加权图。1 个节点的时间复杂度为O(n + m)。在这里,由于我们需要对 n 个节点进行 BFS,因此在所有节点对之间找到最短路径的总时间复杂度变为O(n(n + m))。由于有n(n-1)/2对节点,所以我们有n(n-1)/2所有节点之间的最短路径的值。对于直径,我们需要获得这些值的最大值,也就是 O(n*n)。所以最终的时间复杂度变为:
O(n(n + m)) + O(n*n) = O(n(2n + m)) = O(n(n + m))
用于获取所有节点对之间最短路径的伪代码。我们使用一个名为 Shortest_paths 的矩阵来存储任意两对节点之间的最短路径。我们正在使用一个名为 flag 的字典,其值为 true 或 false ,指示顶点是否已被访问。对于 BFS 的每次迭代,我们将这个字典初始化为全为 false。我们使用队列 Q 来执行 BFS。算法 BFS(适用于所有节点)
Initialize a matrix named Shortest_paths[n][n] to all -1.
For each source vertex s
For each vertex v
Flag[v] = false
Q = empty Queue
Flag[s] = true
enQueue(Q,s)
Shortest_paths[s][s] = 0
While Queue is not empty
Do v = Dequeue(Q)
For each w adjacent to v
Do if flag[w] = false
Flag[w] = true
Shortest_paths[s][w] = Shortest_paths[s][v] + 1
enQueue(Q,w)
Diameter = max(Shortest_paths)
Return Diameter
假设图未加权,则以下步骤可以在 O(V * ( V + E )) 中找到解,其中 V 是顶点数,E 是边数。
让我们将 2 个顶点 u, v 之间的距离定义为 u 和 v 之间的最短路径的长度。用 d(u, v) 表示 定义顶点 v 的偏心率为 e(v) = max {d(u,五)| u ∈ V(G)} (V(G) 是图 G 中的顶点集) 定义 Diameter(G) = max{e(v) | v ∈ V(G)}
现在到算法:
1- 对于 V(G) 中的每个顶点 v 运行 BFS(v) 并构建从每个顶点到其他顶点的距离的二维数组。(计算每个顶点到其他顶点的距离在BFS 算法中很容易做到)
2- 计算步骤 1 中创建的二维数组中每个顶点的 e(v) 3- 通过从步骤 2 中找到最大 e(v) 来计算直径 (G)
现在分析这个算法,很容易看出它是由O(V *(V + E))的第一步主导的
另一种在线性时间 O(V + E) 中运行的算法 1- 在任意随机顶点 v ∈ V(G) 上运行 BFS 2- 选择距离最大的顶点 u 3- 在该顶点 u 上再次运行 BFS 4- 直径是从第 3 步生成的最大距离
首先,您需要找到图形的凸包(找到它是 O(nh),其中 h 是 hull 上的节点数)。直径点将位于凸包上,因此问题将减少到在 h 个点中找到最远的点。因此,总订单将是 O(nh+h*h)。
实际上,如果图形非常大,您将需要使用 Dijkstra 算法来找到最短距离。所以这取决于 OP 的图有多少节点。
你不能一直单调地将 A 提高到下一个幂并跟踪哪些 (i,j) 有一些 A^k 的非零条目吗?一旦所有 i,j 在先前计算的 A^k 中都有一个非零值,无论你在什么 k 上都是图形的直径。
这只能发生在未加权的图上。其中 bfs 给出了 o(v+e) 中的最短路径树,你对 v 源重复相同的操作。