資料結構與演算法(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)\)
程式碼示例:
//#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)\),因此很容易被出題人卡
程式碼示例:
//#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的最短路,每個點都有無法被遍歷到的可能性。
程式碼示例:
//#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)\)
程式碼示例:
//#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;
}
/*
*/