单源最短路径算法 Bellman ford 算法,spfa算法,Dijkstra算法
主要参考算法导论
目录
基本性质
使用min_w(s,v)表示源节点s到v的最短路径长度;
w(u,v)表示节点u到v的权重;
u.d表示源节点s到节点u的当前路径长度;
松弛操作
relax(u,v,w)
{
if(u.d + w < v.d)
{
v.d = u.d + w;
}
}
三角不等式
min_w(s,v) <= min_w(s,u) + w(u,v);
路径松弛性质
如果p(v0, v1, v2, ...... , vk)为v0到vk 的最短路径,并且对边的松弛次序为(v0,v1),(v1,v2),(v2,v3),......(vk-1,vk),则松弛过后u.d = min_w(s,v);
Bellman Ford算法
Bellman 算法解决的是一般情况下的单源最短路径问题,边的最短路可以为负值
适用于:存在环路,路径权重为负值的情况,但图中不可包含权重为负值的环路。
时间复杂度
O(VE);外层循环为O(V),内层循环使用聚合分析得O(E);总的复杂度为:O(VE);
代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
#define INF 0x7fffffff
struct Edge{
int u,v,t;
};
int N,T;
Edge a[2002];
int ans[1001];
int pre[1001];
void bellman_ford(int s)
{
for(int i = 1; i <= N; ++i)
{
ans[i] = INF;
}
pre[s] = 0;
ans[s] = 0;
int e1 = N - 1;//循环次数为顶点数减一;因为一条最短路径最多包含 N -1 条边;
int e2 = T;//边数;
for(int i = 0 ; i < e1; ++i)
{
for(int j = 0; j < e2; ++j)
{
//以下是对边的松弛操作,为了防止数据溢出,进行了多次判断。
//实际原理很简洁:
/*
int tmp = ans[a[j].u] + a[j].t;
if(tmp < ans[a[j].v])
{
ans[a[j].v] = tmp;//更新对短路路径值;
pre[a[j].v] = a[j].u;//更新前驱节点;
}
*/
if(ans[a[j].u] == INF)continue;
int tmp = ans[a[j].u] + a[j].t;
if(ans[a[j].v] == INF)
{
ans[a[j].v] = tmp;
pre[a[j].v] = a[j].u;
}
else if(tmp < ans[a[j].v])
{
ans[a[j].v] = tmp;
pre[a[j].v] = a[j].u;
}
}
}
}
int main()
{
cin >> T >> N;
for(int i = 0; i < T; ++i)
{
cin >> a[i].u >> a[i].v >> a[i].t;
}
bellman_ford(1);
//输出所有的最短路径;
for(int i = 1; i <= N; ++i)
{
int id = i;
stack<int> st;
while(pre[id] != 0)
{
st.push(id);
id = pre[id];
}
st.push(id);
cout << st.top();
st.pop();
while(!st.empty())
{
int tmp = st.top();
st.pop();
cout << "-->" <<tmp;
}
cout << endl;
}
return 0;
}
/*
10 5
1 2 6
1 5 7
2 3 5
2 5 8
3 2 -2
4 3 7
4 1 2
5 1 7
5 3 -3
5 4 9
*/
spfa(Shortest Path Faster Algorithm) 算法
使用队列对Bellman ford进行优化:
Bellman ford是对每个边进行松弛,实际上,每一次只有再上一次松弛中最短的路径的值(d)发生改变的点可达的点才有松弛的可能,
所以,使用一个队列保存,d值发生改变点,然后依次对队列中的点,相关边进行松弛。
算法的正确性:
假定存在一条最短路径: 如果p(v0, v1, v2, ....vk,vk+1,.. , vM)为v0到vM 的最短路径,
使用路径松弛定理进行证明,容易知道若某一次while循环松弛了边(vk-1,vk),由于vk入队,则后续循环中当vk出队,则(vk,vk+1)边必会得到松弛,所以算法是正确的。
时间复杂度:
O(k*E),k的取值2-3(参考百度百科)
代码
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
#define INF 0x7fffffff
struct Ed{
int to,cost;
Ed(){
}
Ed(int a,int b):to(a),cost(b)
{
}
};
int N,T;
vector<Ed> a[1001];
int ans[1001];
int pre[1001];
int vis[1001];
void spfa(int s)
{
for(int i = 1; i <= N; ++i)
{
ans[i] = INF;
}
pre[s] = 0;
ans[s] = 0;
vis[s] = 1;
queue<int> q;
q.push(s);
while(!q.empty())
{
int now = q.front();
q.pop();
vis[now] = 0;
int len = a[now].size();
for(int i = 0; i < len; ++i)
{
int nt = a[now][i].to;
int wt = a[now][i].cost;
int tmp = ans[now] + wt;
if(tmp < ans[nt])
{
ans[nt] = tmp;
pre[nt] = now;
if(vis[nt] == 0)
{
q.push(nt);
vis[i] = 1;
}
}
}
}
}
int main()
{
cin >> T >> N;
int uu,vv,tt;
for(int i = 0; i < T; ++i)
{
cin >> uu >> vv >> tt;
a[uu].push_back(Ed(vv,tt));
}
spfa(1);
//输出所有的最短路径;
for(int i = 1; i <= N; ++i)
{
int id = i;
stack<int> st;
while(pre[id] != 0)
{
st.push(id);
id = pre[id];
}
st.push(id);
cout << st.top();
st.pop();
while(!st.empty())
{
int tmp = st.top();
st.pop();
cout << "-->" <<tmp;
}
cout << endl;
}
return 0;
}
/*
10 5
1 2 6
1 5 7
2 3 5
2 5 8
3 2 -2
4 3 7
4 1 2
5 1 7
5 3 -3
5 4 9
*/
Dijkstra 算法
Dijkstra 算法解决的是带权图的有向图的最短路径问题,要求w(u,v) >= 0;
伪代码:
集合S为顶点最短路径值已知的点;
集合V为所有顶点的集合;
Dijkstra(int s){
1.初始化;将所有点的最短路径估计值设为INF;
2.将源节点的最短路径值设为0.
当S != V 时,执行如下操作:
1.在 V - S中找到最短路径估计值最小的顶点u,加入集合S.
2.对 u 的所有相邻节点的进行松弛操作Relax.
}
算法导论伪代码:
集合S为顶点最短路径值已知的点;
集合V为所有顶点的集合;
最小优先队列Q用于保存集合 V - S;
Dijkstra(int s){
1.初始化;将所有点的最短路径估计值设为INF;
2.将源节点的最短路径值设为0.
while Q != empty
u = Extract_min(Q); 在 Q中找到最短路径估计值最小的顶点u
S = S U {u};
for 节点 u 的每个可达节点 v
Relax(u,v,w);
}
时间复杂度
O((V+E)lg(V));{主要参考算法导论}
代码实现
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define N 201
#define INF 0x7fffffff
struct Ed{
int to,cost;//分别表示到达的下一个节点的序号和边的权重;
Ed(int a,int b):to(a),cost(b)
{
}
Ed()
{
}
};
struct Nod{
int id,d;
Nod(int id_,int d_):id(id_),d(d_)
{
}
Nod(){
}
};
struct cmp{//用于优先队列中节点优先级的比较;
bool operator()(Nod&a,Nod&b){
return a.d > b.d;
}
};
vector<Ed> a[N];
int ans[N];
void dijkstra(int s)
{
for(int i = 1; i <= N; ++i)
{
ans[i] = INF;
}
priority_queue<Nod,vector<Nod>,cmp> q;
ans[s] = 0;
q.push(Nod(s,0));
while(!q.empty())
{
Nod now = q.top();
q.pop();
int id = now.id;
int d = now.d;
if(ans[id] < d)continue;//发现节点在进入优先队列后又被进行了松弛;直接跳过;
int len = a[id].size();
int nt,w;
for(int i = 0; i < len; ++i)
{
nt = a[id][i].to;
w = a[id][i].cost;
int tmp = d + w;
if(tmp < ans[nt])
{
ans[nt] = tmp;
q.push(Nod(nt,tmp));
}
}
}
}