最短路径问题是一个很常见的问题,与其相关的算法也很多,本文总结了三种不同的算法来解决这个问题,并进行了一些对比分析。本文不像教科书那样详细介绍每种算法的具体细节,可以阅读这篇文章:http://dsqiu.iteye.com/blog/1689163,里面讲的和详细。
一.Dijkstra算法
相信说到单源最短路径问题,大家都会想到著名的Dijkstra算法。Dijkstra算法本质上是一种贪心算法,可以把所有节点分为两个集合S(已找到最短路径的集合)和T(剩余节点的集合),每步都从T中选择一个权值最小的节点加入到S中,同时对与该节点相连的并且在T中的节点进行松弛操作。正因为这种贪心的思想,导致了Dijkstra算法不适用于带有负权值的环的图,但适用于带有权值为负的边的图。
此处稍微解释一下,我们知道贪心算法的思想是每步选择最优,从而使得最终得到最优结果。但当图中存在权值为负的环时,局部最优不一定会导致最终最优。例如在下图中:
源节点为0,按照Dijkstra算法的规则,第一步选择节点1加入集合S,则节点0到节点1的最短路径权值为4,但如图可知最短路径为0-2-1,权值为3。故Dijkstra算法不适用于带有负权值的环的图。
代码:
#include
#include
#include
#include
using namespace std;
struct State
{
int index, weight;
State(int i, int w) :index(i), weight(w){}
bool operator<(const State& S) const
{
return weight > S.weight;
}
};
int main()
{
ifstream in("data.txt");
int n, m, s, u, v, w; //n表示图中节点个数,m表示边的条数,s表示起始节点的索引号
vector>> Adj; //图的邻接表
vector Cost; //保存图中每个节点到起始节点的最短路径权值
priority_queue Q;
in >> n >> m >> s;
Adj.assign(n, vector>());
Cost.assign(n, -1);
for (int i = 0; i < m; i++) //用邻接表存储图的信息
{
in >> u >> v >> w;
Adj[u].push_back(pair(v, w));
}
Q.push(State(s, 0));
while (!Q.empty())
{
State s = Q.top();
Q.pop();
if (Cost[s.index] != -1) continue;
Cost[s.index] = s.weight;
for (int i = 0; i < Adj[s.index].size(); i++)
{
pair p = Adj[s.index][i];
if (Cost[p.first] != -1) continue;
Q.push(State(p.first, s.weight + p.second));
}
}
cout << "Result:\n";
for (int j = 0; j < n; j++)
{
if (j == s) continue;
cout << "From " << s << " to " << j << ": ";
if (Cost[j] == -1) cout << "No Path\n";
else cout << Cost[j] << endl;
}
system("pause");
return 0;
}
#include
#include
#include
#include
using namespace std;
int main()
{
ifstream in("data.txt");
int n, m, s, u, v, w; //n表示图中节点个数,m表示边的条数,s表示起始节点的索引号
vector>> Adj; //图的邻接表
vector Cost; //保存图中每个节点到起始节点的最短路径权值
queue> Q; //pair中第一个值为节点索引号,第二个值记录当前状态下本节点到源节点的路径长度
in >> n >> m >> s;
Adj.assign(n, vector>());
Cost.assign(n, INT_MAX);
for (int i = 0; i < m; i++) //用邻接表存储图的信息
{
in >> u >> v >> w;
Adj[u].push_back(pair(v, w));
}
Q.push(pair(s, 0));
Cost[s] = 0;
while (!Q.empty())
{
pair p = Q.front();
Q.pop();
int i;
for (i = 0; i < Adj[p.first].size(); i++) //遍历与节点p相连的节点,同时做松弛操作
{
pair q=Adj[p.first][i];
if (Cost[p.first] + q.second < Cost[q.first])
{
Cost[q.first] = Cost[p.first] + q.second;
Q.push(pair(q.first, p.second + 1));
if (p.second + 1 == n) break; //此处防止当图中存在负权值的环时,在环上无限循环
}
}
if (i < Adj[p.first].size()) break;
}
cout << "Result:\n";
if (!Q.empty()) { cout << "图中存在权值为负的环!\n"; return 0; }
for (int j = 0; j < n; j++)
{
if (j == s) continue;
cout << "From " << s << " to " << j << ": ";
if (Cost[j] == INT_MAX) cout << "No Path\n";
else cout << Cost[j] << endl;
}
system("pause");
return 0;
}
#include
#include
#include
using namespace std;
struct Edge
{
int s, e, weight;
//Edge(int u, int v, int w) :s(u), e(v), weight(w){}
};
int main()
{
ifstream in("data.txt");
int n, m, s, u, v, w; //n表示图中节点个数,m表示边的条数,s表示起始节点的索引号
vector Graph; //保存图的边信息
in >> n >> m >> s;
Graph.assign(m, Edge());
vector dp(n, INT_MAX); //保存每个节点到源节点的路径,经过n-1轮的优化,最终dp中保存的是最短路径
dp[s] = 0;
for (int i = 0; i < m; i++) in >> Graph[i].s >> Graph[i].e >> Graph[i].weight;
bool flag;
for (int k = 1; k < n; k++)
{
flag = false;
for (int i = 0; i < m; i++)
{
Edge E = Graph[i];
if (dp[E.s] + E.weight < dp[E.e]) //松弛操作
{
dp[E.e] = dp[E.s] + E.weight;
flag = true;
}
}
if (!flag) break; //此处做了一个小的优化:如果在k的某轮循环中,没有对dp进行任何的优化,则可以直接终止循环
}
int i;
for (i = 0; i < m; i++)
{
Edge E = Graph[i];
if (dp[E.e]>dp[E.s] + E.weight) break;
}
cout << "Result:\n";
if (i < m) { cout << "图中存在权值为负的环!\n"; getchar(); return 0; }
for (int j = 0; j < n; j++)
{
if (j == s) continue;
cout << "From " << s << " to " << j << ": ";
if (dp[j] == INT_MAX) cout << "No Path!\n";
else cout << dp[j] << endl;
}
system("pause");
return 0;
}