zoukankan      html  css  js  c++  java
  • 单源最短路径算法 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
    更多练习

  • 相关阅读:
    【流水账】2021-06-19 Day-09
    【流水账】2021-06-18 Day-08
    【流水账】2021-06-16 Day-06
    【流水账】2021-06-15 Day-05
    .Net调用Java的实现方法
    优先队列的实例题
    栈的相关程序题
    重载函数
    卡特兰数
    关于全排列的递归
  • 原文地址:https://www.cnblogs.com/lif323/p/9340153.html
Copyright © 2011-2022 走看看