最短路算法

最短路算法:

image-20211117084920793

单源最短路:所有边权都是正数

1、Dijkstra算法:

对于稠密图(边相对较多):朴素Dijkstra 时间复杂度:\(O(n^2+m)\)

思路分析:该算法是基于贪心思想的,每次找到最小的没有被用来更新其他点的dist[t],再用它来更新它的出边,并在st中打上标记即可

849. Dijkstra求最短路 I - AcWing题库

代码示例:

//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
//#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;

#define For(i,a,b) for (int i=(a);i<=(b);++i)
#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
#define mls multiset
#define lb lower_bound
#define ub upper_bound
#define pb push_back
#define pob pop_back
#define itt iterator
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define lowbit(x) x & (-x)
#define clr(x) memset(x, 0, sizeof(x));

typedef vector<int> vii;
typedef vector<long long> vll;
typedef long long ll;
typedef unsigned long long ull;
		
const int MAXN = 0x7fffffff;
const int MOD = 1000000007;
const ll MOD1 = 212370440130137957ll;
const int N = 510;

int g[N][N];
int dist[N];
int n, m;
bool st[N];

int dijkstra()
{
	dist[1] = 0;
	for(int i = 1; i <= n; i ++)
	{
		int t = -1;
		for(int j = 1; j <= n; j ++)
		{
			if(!st[j] && (t == -1 || dist[j] < dist[t])) t = j;
		}
		st[t] = true;
		for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], dist[t] + g[t][j]);
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}

int main ()
{	
	//IOS;
	memset(dist, 0x3f, sizeof dist);
	memset(g, 0x3f, sizeof g);
	cin >> n >> m;
	For(i, 1, m)
	{
		int a, b, c;
		cin >> a >> b >> c;
		g[a][b] = min(g[a][b], c);
	}

	cout << dijkstra() << endl;

	return 0;
}	
/*

*/	

对于稀疏图(边相对较少):堆优化Dijkstra 时间复杂度:\(O(mlogn)\)

思路分析:相对于朴素Dijkstra,通过堆,我们每次可以用\(O(1)\)的时间去查找dist[t],然后再用其去更新t的出边,但更新这一步维护堆的过程每次时间复杂度为\(O(logn)\),一共m次,因此时间复杂度\(O(mlogn)\)

850. Dijkstra求最短路 II - AcWing题库

代码示例:

//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
//#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;

#define For(i,a,b) for (int i=(a);i<=(b);++i)
#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
#define mls multiset
#define lb lower_bound
#define ub upper_bound
#define pb push_back
#define pob pop_back
#define itt iterator
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define lowbit(x) x & (-x)
#define clr(x) memset(x, 0, sizeof(x));
#define fi first
#define se second

typedef vector<int> vii;
typedef vector<long long> vll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
		
		
const int MAXN = 0x7fffffff;
const int MOD = 1000000007;
const ll MOD1 = 212370440130137957ll;
const int N = 150050;

typedef pair<int , int> PII;
int n, m;
int h[N], e[N], w[N], nxt[N], idx;
bool st[N]; //

int dist[N];

void add(int a, int b, int c)
{
	e[idx] = b, w[idx] = c, nxt[idx] = h[a], h[a] = idx ++;
}

int dijkstra()
{
	memset(dist, 0x3f, sizeof dist);
	priority_queue <PII, vector<PII>, greater<PII> > heap;
	dist[1] = 0;
	heap.push({0, 1});
	while(!heap.empty())
	{
		auto t = heap.top();
		heap.pop();
		int ver = t.se, distance = t.fi;
		if(st[ver]) continue;
		st[ver] = true;
		for(int i = h[ver]; i != -1; i = nxt[i])
		{
			int j = e[i]; 
			if(dist[j] > distance + w[i])
			{ 
				dist[j] = distance + w[i];
				heap.push({dist[j], j});
			} 
		}
	}
	if(dist[n] == 0x3f3f3f3f) return -1;
	return dist[n];
}


int main ()
{	
	//IOS;
	cin >> n >> m;
	memset(h, -1, sizeof h);

	For(i, 1, m)
	{
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}

	cout << dijkstra() << endl;

	return 0;
}	
/*

*/

单源最短路:存在负权边

3、Bellman-Ford算法:可以处理最多经过k条边的问题

算法分析:外层循环k次,内层遍历所有的边并进行松弛操作,即令所有的边的满足三角不等式dist[b] <= dist[a] + w[i],而外层循环的意义就是,相当于一层一层的去松弛求最短路,外层遍历几次即最短路径过几条边,需要注意的问题是每次松弛必须用上一层的dist,以防止串联

同时可以利用如果外层循环超过n次,仍能进行松弛更新,即存在负环

时间复杂度:\(O(nm)\)

853. 有边数限制的最短路 - AcWing题库

代码示例:

//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
//#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;

#define For(i,a,b) for (int i=(a);i<=(b);++i)
#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
#define mls multiset
#define lb lower_bound
#define ub upper_bound
#define pb push_back
#define pob pop_back
#define itt iterator
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define lowbit(x) x & (-x)
#define clr(x) memset(x, 0, sizeof(x));
#define fi first
#define se second

typedef vector<int> vii;
typedef vector<long long> vll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
		
const int MAXN = 0x7fffffff;
const int MOD = 1000000007;
const ll MOD1 = 212370440130137957ll;

const int M = 1e5 + 5;
const int N = 505;
int n, k, m;
int dist[N];
int backup[N]; 

struct node 
{
	int a, b, w;
}edge[M];

int bmf()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	for(int i = 1; i <= k; i ++)
	{
		memcpy(backup, dist, sizeof dist);//备份上一层的dist
		for(int j = 1; j <= m; j ++)
		{
			int a = edge[j].a, b = edge[j].b, w = edge[j].w;
			if(dist[b] > backup[a] + w) dist[b] = backup[a] + w;
		}
	}
	return dist[n];
}

int main ()
{	
	//IOS;
	cin >> n >> m >> k;
	For(i, 1, m)
	{
		int a, b, w;
		cin >> a >> b >> w;
		edge[i] = {a, b, w};
	}

	int t = bmf();
	if(t >= 0x3f3f3f3f / 2) cout << "impossible" << endl;
	else cout << t << endl;

	return 0;
}	
/*

*/	

4、SPFA算法:队列优化的Bellman-Ford算法

算法分析:我们知道Bellman-Ford算法在内层循环的时候每次会遍历所有的边,但实际上对于t,我们只需要遍历t的所有临边即可,因此我们可以利用队列,同时通过st状态数组防止队列中点的重复,每次将队头的临边更新即可。

时间复杂度:平均\(O(m)\),最坏情况\(O(nm)\),因此很容易被出题人卡

851. spfa求最短路 - AcWing题库

代码示例:

//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
//#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;

#define For(i,a,b) for (int i=(a);i<=(b);++i)
#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
#define mls multiset
#define lb lower_bound
#define ub upper_bound
#define pb push_back
#define pob pop_back
#define itt iterator
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define lowbit(x) x & (-x)
#define clr(x) memset(x, 0, sizeof(x));
#define fi first
#define se second

typedef vector<int> vii;
typedef vector<long long> vll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
		
const int MAXN = 0x7fffffff;
const int MOD = 1000000007;
const ll MOD1 = 212370440130137957ll;
const int N = 1e5 + 5;

int e[N], h[N], ne[N], idx, w[N];
bool st[N];
int dist[N];
int n, m;

void add(int a, int b, int c)
{
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}

int spfa()
{
	queue <int> q;
	memset(dist, 0x3f3f3f3f, sizeof dist);
	dist[1] = 0;
	q.push(1);
	st[1] = true;
	while(!q.empty())
	{
		int t = q.front();
		q.pop();
		st[t] = false;
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			if(dist[j] > dist[t] + w[i])
			{
				dist[j] = dist[t] + w[i];
				if(!st[j])
				{
                q.push(j); //此时推进到j,dist[j]一定是最短路
					st[j] = true;
				}
			}
		}
	}
	return dist[n];
}

int main ()
{	
	//IOS;
	memset(h, -1, sizeof h);
	cin >> n >> m;
	For(i, 1, m)
	{
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}

	int t = spfa();
	if(t == 0x3f3f3f3f) cout << "impossible" << endl;
	else cout << t << endl;

	return 0;
}	
/*

*/

SPFA判断负权回路

算法分析:我们可以开一个数组cnt来记录每个点最短路的经过的边数,一张有n个点图中,最短路不可能超过n-1,倘若cnt[i] >= n 就说明一定存在负权回路

PS:需要注意的是,最开始初始化队列的时候需要把每个点都放进队列,因为此时不是求1-n的最短路,每个点都有无法被遍历到的可能性。

852. spfa判断负环 - AcWing题库

代码示例:

	//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
	//#pragma GCC optimize(2)

	#include <bits/stdc++.h>
	using namespace std;

	#define For(i,a,b) for (int i=(a);i<=(b);++i)
	#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
	#define mls multiset
	#define lb lower_bound
	#define ub upper_bound
	#define pb push_back
	#define pob pop_back
	#define itt iterator
	#define endl '\n'
	#define IOS ios::sync_with_stdio(0); cin.tie(0);
	#define lowbit(x) x & (-x)
	#define clr(x) memset(x, 0, sizeof(x));
	#define fi first
	#define se second

	typedef vector<int> vii;
	typedef vector<long long> vll;
	typedef long long ll;
	typedef unsigned long long ull;
	typedef pair<int, int> pii;
	typedef pair<ll, ll> pll;
			
	const int MAXN = 0x7fffffff;
	const int MOD = 1000000007;
	const ll MOD1 = 212370440130137957ll;
	const int N = 2005;
	const int M = 10005;

	int e[M], h[N], w[M], idx, ne[M];
	bool st[N];
	int cnt[N];
	int n, m;
	int dist[N];

	void add(int a, int b, int c)
	{
		e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
	}

	bool spfa()
	{
		memset(dist, 0x3f, sizeof dist);
		queue <int> q;
		for(int i = 1; i <= n; i ++)
		{
			q.push(i);
			st[i] = true;
		}
		while(!q.empty())
		{
			int t = q.front();
			q.pop();
			st[t] = false;
			for(int i = h[t]; i != -1; i = ne[i])
			{
				int j = e[i];
				if(dist[j] > dist[t] + w[i])
				{
					cnt[j] = cnt[t] + 1;	
					if(cnt[j] >= n) return true;
					dist[j] = dist[t] + w[i];
					if(!st[j])
					{
						q.push(j);
						st[j] = true;
					}
				}
			}
		}
		return false;
	} 

	int main ()
	{	
		//IOS;
		cin >> n >> m;
		memset(h, -1, sizeof h);
		For(i, 1, m)
		{
			int a, b, c;
			cin >> a >> b >> c;
			add(a, b, c);
		}
		if(spfa()) cout << "Yes" << endl;
		else cout << "No" << endl;
		return 0;
	}	
	/*

	*/

多源汇最短路

1、Floyd算法:(不可求负权边)

算法分析:Floyd是基于动态规划思想的算法,转移方程为\(d[k][i][j] = min(d[k][i][j], d[k-1][i][k] + d[k-1][k][j])\)

省去第一维后,即\(d[i][j] = min(d[i][j], d[i][k] + d[k][j])\)

时间复杂度:\(O(n^3)\)

854. Floyd求最短路 - AcWing题库

代码示例:

//#pragma comment(linker,   "/STACK:10240000000000,10240000000000")
//#pragma GCC optimize(2)

#include <bits/stdc++.h>
using namespace std;

#define For(i,a,b) for (int i=(a);i<=(b);++i)
#define Fod(i,b,a) for (int i=(b);i>=(a);--i)
#define mls multiset
#define lb lower_bound
#define ub upper_bound
#define pb push_back
#define pob pop_back
#define itt iterator
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define lowbit(x) x & (-x)
#define clr(x) memset(x, 0, sizeof(x));
#define fi first
#define se second

typedef vector<int> vii;
typedef vector<long long> vll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
		
const int MAXN = 1e9;
const int MOD = 1000000007;
const ll MOD1 = 212370440130137957ll;
const int N = 205; 

int n, m, t;
int d[N][N];

void floyd()
{
	For(k, 1, n)
		For(i, 1, n)		
			For(j, 1, n)			
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}	

int main ()
{	
	IOS;
	cin >> n >> m >> t;
	For(i, 1, n)
		For(j, 1, n)
			if(i == j) d[i][j] = 0;
			else d[i][j] = MAXN;

	For(i, 1, m)
	{
		int a, b, c;
		cin >> a >> b >> c;
		d[a][b] = min(d[a][b], c);
	}

	floyd();
	
	For(i, 1, t)
	{
		int a, b;
		cin >> a >> b;
		if(d[a][b] > MAXN / 2) cout << "impossible" << endl;
		else cout << d[a][b] << endl;
	}

	return 0;
}	
/*

*/
posted @ 2021-11-18 15:14  Yra  阅读(92)  评论(0)    收藏  举报