图形中最短和最长路径算法的快速概述和比较。
关于图中无辜的看起来最短和最长的路径问题,有很多要记住的要点。 在计算机程序员的技术工作面试中,有关此主题的问题非常常见。 但是,通常很难保持内存新鲜并记住有关这些问题及其算法的所有详细信息。
例如:
- 您知道在图中找到最短的简单路径是NP难的吗? (如果没有,请参见下面的最长路径部分)
- 您知道在某些图中最短路径可以在线性时间内找到吗?
在这里,我对一个地方的每个著名算法提供了一个简短的总结和要点,可以在每次面试之前快速进行回顾。
开始之前:
首先,我们假设图是G(V,E)具有,其中
- V = {1,2,…,n},| V | = n
- | E | =米
对于最短路径问题,我们假设我们在最短的非简单路径之后,即顶点可以重复。 另外,我们假设边缘权重可以是整数值,即正,负或零。
最短距离问题只需要节点之间的最短距离,而最短路径问题需要节点之间的实际最短路径。 我们在这里讨论最短距离问题。 通常可以在算法中稍加改进的情况下找到最短路径。
Floyd-Warshall算法
Floyd-Warshall是最简单的算法:
快速直观 :我们仅使用集合{1,2,…,k} 中的节点作为它们之间的中间点 ,计算从节点i到j的最短路径。 d(i,j,k)表示仅使用k个节点的i,j之间的最短距离。 我们可以这样写:
d(i,j,k)= min(d(i,j,k-1),d(i,k,k-1)+ d(k,j,k-1))
下图显示了这种直觉:
通过比较使用k-1个节点从i到j的最短路径以及使用k-1个节点从i到k以及从k到j的总和,可以计算使用k个节点从i,j的最短路径
下面的视频清楚地说明了Floyd-Warshall算法:
下面是C ++的实现:
// Floyd-Warshall Algorithm for finding the shortest distance
vector < vector < long long >> floydWarshal( vector < vector < int >> &graph) {
vector < vector < long long >> d(graph.size(),
vector < long long >(graph.size()));
//Initialize d
for ( int i = 0 ; i < graph.size(); ++i) {
for ( int j = 0 ; j < graph.size(); ++j) {
d[i][j] = graph[i][j];
}
}
for ( int k = 0 ; k < graph.size(); k++)
for ( int i = 0 ; i < graph.size(); ++i)
for ( int j = 0 ; j < graph.size(); ++j)
d[i][j] = std ::min(d[i][j], d[i][k] + d[k][j]);
return d;
}
您需要了解的Floyd-Warshal算法的知识:
- 它找到所有节点对之间的最短距离。
- 是O(n³)
- 它是一种动态编程算法
- 图形可以具有负边
- 它可以有负周期
Dijkstra算法
Dijkstra算法找到单个源和所有其他节点之间的最短路径。
直觉:保留已访问节点的列表。 在每个步骤:
- 查找距离最短的未访问节点u
- 放松你的邻居的距离
- ü添加到访问列表和重复
以下是Dijkstra在C ++中的实现:
// Dijkstra shortest distance
vector < long > dijkstra( vector < vector < int >> &graph, int source) {
vector < long > d(graph.size());
vector < int > s;
for ( int i = 0 ; i < graph.size(); i++) {
d[i] = graph[source][i];
}
s.push_back(source);
while (s.size() < graph.size()) {
//Find minimum
int u = findMinInDButNotInS(d, s);
s.push_back(u);
//Relax
for ( int j = 0 ; j < graph.size(); j++) {
d[j] = std ::min(d[j], d[u] + graph[u][j]);
}
}
return d;
}
您需要了解的Dijkstra算法:
- 运行时间为: O( n² )简单形式。 最小堆: O((m + n)log(n))。 使用斐波那契堆: O(m + n log n)
- 这是一个贪婪的算法
- 它无法处理负边沿或负循环
以下是Dijkstra在C ++中使用优先级队列的算法:
// Dijkstra shortest distance
vector < long > dijkstraPriorityQueue( vector < vector < int >> &graph,
int source) {
// Queue of pairs of distance to node number
priority_queue<pair< int , int >, vector <pair< int , int >>,
greater<pair< int , int >>>
q;
vector < long > d(graph.size(), INT_MAX);
d[source] = 0 ;
q.push(make_pair( 0 , source));
while (!q.empty()) {
// Find minimum
int u = q.top().second;
q.pop();
// Relax distances
// Rather than decreasing the values in the queue,
// we just add the updated distance to the queue again.
for ( int j = 0 ; j < graph.size(); j++) {
if (d[j] > d[u] + graph[u][j]) {
d[j] = d[u] + graph[u][j];
q.push(make_pair(d[j], j));
}
}
}
return d;
}
Bellman-Ford算法
该算法找到从源到所有其他节点的最短距离。
直觉:我们有两个循环:
直觉:我们有两个循环:
- 内循环:我们在所有边缘上进行迭代。 在每次迭代中,我们使用从0到j的边来放宽距离。
- 外循环:我们重复内循环n-1次
Bellman-Ford算法。 图片是从 这里 拍摄的 。
在外循环的第i次迭代之后,将计算最多i个边的最短路径。 在任何简单路径中最多可以有n-1条边,因此我们重复n-1次。
下面是C ++中Bellman-Ford算法的实现。
// BellmanFord Algorithm
vector < long > bellmanFord( vector < vector < int >> &graph, int source) {
vector < long > d(graph.size(), INT_MAX);
d[source] = 0 ;
for ( int i = 0 ; i < graph.size() - 1 ; i++) {
for ( int u = 0 ; u < graph.size(); u++)
for ( int v = 0 ; v < graph.size(); v++)
d[v] = std ::min(d[v], d[u] + graph[u][v]);
}
return d;
}
Bellman-Ford算法。 请注意,我们使用邻接矩阵来迭代边缘
您需要了解的Bellman-Ford算法
- 运行时间: O(mn)。
- 如果我们使用邻接矩阵(如上述代码)来迭代边缘,则运行时间为O(n³)
- 它可以处理负边缘
- 它可以报告负周期
DAG中的最短距离
DAG(有向无环图)中的最短距离可以在线性时间内计算出来。
我们使用拓扑排序来查找单个源到所有其他节点的距离。
直觉:以拓扑顺序迭代图的节点u 。 对于与u相邻的每个节点v , 使用以下命令放松d [v]:
d [v] = min(d [v],d [u] + w(u,v))
您需要了解的内容:
- 运行时间: O(m + n)
- 它可以处理负边缘
- 它无法处理负周期(DAG中没有周期)
以下是使用拓扑排序在DAG中最短路径的实现:
// Shortest path using topological sort
vector < long > shortestPathTopo( vector < vector < int >> &graph, int source) {
vector < long > d(graph.size(), INT_MAX);
d[source] = 0 ;
vector < int > sorted = topologicalSortDFS(graph);
for ( auto n : sorted) {
for ( int j = 0 ; j < graph.size(); j++) {
// Relax outgoing edges of n
if (graph[n][j] != INT_MAX) {
d[j] = min(d[j], d[n] + graph[n][j]);
}
}
}
return d;
}
// Topological Sort Using DFS
vector < int > topologicalSortDFS( vector < vector < int >> &graph) {
vector < int > result;
// map of node to 0: not visited, 1: being visited, 2: visited
unordered_map < int , int > visited;
set < int > unvisited;
for ( int i = 0 ; i < graph.size(); i++) {
unvisited.insert(i);
}
// While there is unvisited nodes
while (!unvisited.empty()) {
DFS(graph, visited, unvisited, result, *(unvisited.begin()));
}
std ::reverse(result.begin(), result.end());
return result;
}
//-----------------------------------------------------
// Recursively visits nodes
// If all children of a node is visited, adds it to sorted
// Marks nodes 0: not visited, 1: being visited, 2: visited
void DFS ( vector < vector < int >> &graph, unordered_map < int , int > &visited,
set < int > &unvisited, vector < int > &sorted, int n) {
if (visited[n] == 1 ) {
cout << "Detected cycle!" << endl ;
return ;
}
if (visited[n] == 2 ) {
return ;
}
visited[n] = 1 ;
for ( int j = 0 ; j < graph.size(); j++) {
if (j != n && graph[n][j] != INT_MAX) {
DFS(graph, visited, unvisited, sorted, j);
}
}
unvisited.erase(n);
visited[n] = 2 ;
sorted.push_back(n);
}
广度优先搜索算法
广度优先搜索BFS可以在非加权图中或在加权图中找到最短路径,如果所有边的权重相同。 不失一般性,假定所有权重均为1 。
直觉: BFS使图变平,即,在每次迭代i时,它都会访问距源距离i的节点。 因此,如果从源到节点的最短路径是i ,我们肯定会在迭代i中找到它。
您需要了解的BFS:
- 运行时间:O(m + n)
- 所有权重应该相等
- 它不能处理负重
- 它无法处理负循环
最长距离问题
寻找最短路径的姐妹问题是找到最长的路径。 但是首先要注意的是,当我们谈论最长的路径时,会产生巨大的困惑:
最长路径问题通常意味着找到最长的简单路径。
然而,最短路径问题(如上所述)着重于找到最短(简单或非简单)路径。
因此,在文献中,即使人们谈论寻找最长的路径,他们通常也意味着寻找最长的简单路径。
转化为 -G
最长的简单路径问题可以通过将G转换为-G(即 ,将原始G中每个边的权重的符号取反)来解决,然后计算最短的简单路径 。
注意,如果-G没有负周期,则找到最短的简单路径与找到最短的路径相同,使用上述算法可以在多项式时间内求解。
关于最长的简单路径问题,您需要了解的内容
- 通常,找到最长的简单路径是NP-Hard。 通过减少哈密顿循环问题可以很容易地表明这一点。
- 因此,在G中存在正循环时找到最长的简单路径是NP-难的。
- 如果G中没有正周期,则通过在-G上运行上述最短路径算法之一,可以在多项式时间内解决最长的简单路径问题。
这是关于在我们不经常听到的图中找到最短的简单路径的有趣观点:
在图中找到最短的简单路径很困难。
这可以通过使用-G变换来证明找到最长的简单路径的问题来证明。
为了更好地理解它,假设G中存在一个负周期。 在这种情况下,我们最著名的算法都找不到简单的最短路径,因为它不会退出。 但是,图中仍然有一条最短的简单路径,其中没有顶点重复。 找到最短的简单路径很困难!
DAG中的最长路径
如果G是DAG,则由于没有循环,因此可以使用线性时间中的拓扑排序来解决找到最长的简单路径的问题。 该解决方案类似于在DAG中找到最短距离的解决方案,只是在放宽距离时取最大值。
如果您在求职面试中听到有关最短和最长路径算法的有趣问题,请告诉我。 祝您面试顺利!
From: https://hackernoon.com/shortest-and-longest-path-algorithms-job-interview-cheatsheet-2adc8e18869