Loading

单源最短路径算法 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));
			}
		}
	}

}

例题练习

Til the Cows Come Home
Frogger
Heavy Transportation
更多练习

posted @ 2018-07-22 22:34  lif323  阅读(300)  评论(0编辑  收藏  举报