1. 程式人生 > 其它 >資料結構與演算法(Java版)_07_環形雙鏈表的增、刪、改、查

資料結構與演算法(Java版)_07_環形雙鏈表的增、刪、改、查

最短路演算法:

單源最短路:所有邊權都是正數

1、Dijkstra演算法:

對於稠密圖(邊相對較多):樸素Dijkstra 時間複雜度:\(O(n^2+m)\)

思路分析:該演算法是基於貪心思想的,每次從已經更新過的st陣列中找到最小的沒有更新的dist[t],再用它來更新它的出邊即可

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;
}	
/*

*/