在这篇文章中,我们解释了使用拓扑排序寻找有向无环图中最短路径的算法。
内容表。
- 问题陈述
- 方法
- 拓扑排序(DFS)的伪代码
- 使用拓扑排序寻找最短路径的伪代码
- 时间复杂度
- 拓扑排序法与其他最短距离查找算法的比较
- 拓扑排序的应用
- 问题
我们现在将深入研究。
问题陈述
我们有一个加权的有向无环图和一个源顶点,我们需要计算从源顶点到图中其他每个顶点的最短路径(假定与图中每条边相关的权重代表两个顶点之间的路径长度
方法
-
由于我们给定的图是一个有向无环图,我们可以考虑在图上应用拓扑排序,以便计算出从源顶点的最短路径。
-
什么是拓扑排序?
- 拓扑排序只对有向无环图(DAG)有效。
- 有向无环图顶点的拓扑排序是对图中的顶点v1,v2,--Vn进行排序,即 "如果有一条从vi到vj顶点的边,那么在图的拓扑排序中,vi应该总是排在vj之前。"
- 因此,基本上在图的拓扑排序中,具有较少依赖性的顶点被印在具有相对较大依赖性的顶点之前。
- 我们可以同时使用DFS/BFS来实现拓扑排序。
我们如何使用拓扑排序法来寻找最短路径?
- 由于源顶点和特定顶点之间的最短路径应涉及最小的中间边,因此首先找到拓扑排序来计算最短路径是有意义的,因为拓扑排序将顶点按照它们的indegree递增顺序排列。
- 我们的想法是根据图形的拓扑排序遍历图形的顶点,对于拓扑排序中的每一个当前顶点,我们遍历当前顶点的邻接列表,实现以下操作。
for(auto i : adj[u])
{
if dist[i]>dist[u]+weight(u,i)
dist[i] = dist[u]+weight(u,i);
}
-
这里 "u "是拓扑排序中的当前顶点
-
adj[u]代表顶点 "u "的邻接列表。
-
"i "是 "u "的邻接列表中的当前顶点。
-
dist[]是一个向量,其索引号代表图中的一个顶点,对应的数值存储在一个索引中,代表索引号所代表的顶点与源顶点之间的最短路径的长度。除了源顶点的索引号,每个索引的dist[i]的初始值都是INT_MAX。
-
因此,基本上我们要检查我们是否能从源顶点通过顶点 "u "到达顶点 "i",如果我们得到更短的路径,我们就更新dist[i]的值。
拓扑排序(DFS)的伪代码
拓扑排序可以通过图的深度优先遍历来简单实现。我们将使用一个堆栈来存储通过图的深度优先遍历所遍历的顶点,这样,具有最大阶数的顶点就在堆栈的底部,而具有最小阶数的顶点就在堆栈的顶部。
void topological(vector<int> adj[], int v)
{
vector<bool> visited(v,false);
stack<int> stk;
for(int i=0;i<v;i++)
{
if(visited[i]==false)
{
DFSrec(adj,i,visited,stk);
}
}
}
void DFSrec(vector<int> adj, int u, vector<bool> &visited, stack<int>& stk)
{
visited[u] = true;
for( auto i : adj[u])
{
if(visited[i] == false)
{
DFSrec(adj,i,visited,stk)
}
}
stk.push(u);
}
最后,在实现这个基于图的深度优先遍历的拓扑排序功能后,我们将得到一个堆栈,其顶部元素代表具有最小阶数的顶点,堆栈的底部元素代表具有最大阶数的顶点。
使用拓扑分类法寻找最短路径的伪代码
- 我们将遍历包含拓扑排序的堆栈,该堆栈是我们使用深度优先遍历法形成的图形。
- 我们将形成一个向量dist[],就像前面解释的那样,我们将对每个索引进行惯性化,除了代表源顶点的索引为INT_MAX。
- 在每个迭代中,我们将遍历图中顶点的邻接列表,该列表由堆栈的当前顶部元素代表,在当前迭代结束时,我们将跳出堆栈的当前顶部。
void shortestpath(vector<int> adj, stack<int>& stk, int source)
{
int v = adj.size();
vector<int> dist(v,INT_MAX);
dist[source] = 0;
while(stk.empty() != true)
{
int u = stk.top();
stk.pop();
for(auto i: adj[u])
{
if(dist[i] > dist[u]+weight(u,i))
dist[i] = dist[u] + weight(u,i);
}
}
最后,dist[]向量将存储索引号所代表的顶点与源顶点的距离,如果任何顶点的dist[i]为无穷大,这意味着源顶点和索引号 "i "所代表的顶点之间不可能有路径。
时间复杂度
-
使用DFS进行拓扑排序的时间复杂度为O(v+e),其中 "v "是顶点的数量,"e "是图中边的数量。
- 原因是什么?使用DFS的拓扑排序是一个正常的
DFS程序,只是对将顶点推入堆栈做了很小的修改,这需要O(1)的时间,因此基本上我们可以说时间复杂性与正常的DFS函数相同。
- 原因是什么?使用DFS的拓扑排序是一个正常的
-
最短路径函数的时间复杂度是O(v+e)
,其中v是图中veritces的数量,e是图中edge的数量。 -
原因是什么?使用while循环遍历堆栈,for循环遍历顶点的邻接列表,我们只是简单地遍历图形,因此将花费O(v+e)。
-
总的时间复杂度是O(v+e+v+e),基本上等于O(v+e)。
拓扑排序法与其他最短距离查找算法的比较
- Bellman Ford算法可用于一般的加权图,Belman Ford算法的时间复杂度为O(v*e)。
- Dijkstra算法的时间复杂度为O(e*logv),比Bellman Ford算法好,但Dijkstra算法的问题是,它不能用于有负加权边的图。
- 对于非加权图,我们可以使用BFS算法,时间复杂度为O(v+e)。
- 对于有向无环图,我们可以使用时间复杂度为O(v+e)的Topological Sorting算法。
拓扑分类法的应用
拓扑排序法可用于工作调度。
在这里,图中的每个顶点都可以被视为工作,如果有一条从顶点v1到v2的边,那么这意味着工作2与工作1有依赖关系,因此工作1应该在工作2之前完成,因此我们需要进行拓扑排序,以获得每个工作完成的最合适的顺序。
问题
为什么拓扑排序只适用于有向无环图?
答案已经在本文的前一节中回答了。如果你无法回答,请再看一遍。
通过OpenGenus的这篇文章,你一定对使用拓扑排序寻找最短路径有了深刻的认识。好好享受吧。