圖論-最短路問題
阿新 • • 發佈:2018-12-26
最短路問題總結
floyed演算法
可以求任意兩點的最短路,適合負邊權,也可以用於檢測任意兩點是否連通。演算法效率O(N^3)
核心程式碼:
//d[i][j]表示節點i到j的最短路
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j] = min(d[i][j] , d[i][k]+d[k][j]);
其中k一定是在最外面,理解為什麼在最外面是最難,這裡利用了動態規劃的思想。 理解時可以擴大維度 f[k][i][j]表示i->j經過k的最短路,f[k-1][i][j]表示經過k-1,的最短路 f[k][i][j] = min{f[k-1][i][k],f[k-1][i][k] + f[k-1][k][j]} f[k][i][j] 由公式是有f[k-1][i][j] 轉化而來,所以k是階段。
dijkstra演算法(迪傑斯特拉)
貪心思想, 效率是O(N^2),無法處理負邊權,資料用鄰接矩陣,用優先佇列優化用鄰接表。
用dist[i]表示i到起點的距離,vist[i]表示i是否被訪問過。
策略是,訪問過的節點構成一個集合,每次更新一個點,這個點是剩下的點中到起點最短的點,
將他加入集合,然後由新加的點出發更新剩下的點。因為這裡的更新剩下的點所以無法處理負邊權
void dijkstra(int start) {
for(int i=1;i<=n;i++){
int x;
int mi=INF; // 在所有未標號的結點中,選擇一個d值最小的點x。
//這裡有點貪心的意味,每次找最小值進行走下一步。所以可以用堆來優化查詢最小值
for(int j=1;j<=n;j++)
if(vist[j] == 0 && dist[j] < mi )
//如果j沒訪問,並且圓點到這個點的距離小於最小值
{x = j ; mi = dist[x];}
vist[x] = 1;//這個點標記訪問
for(int j=1;j<=n;j++)//從x出發到其他點,更新其他點
if (dist[x] + g[x][j] < dist[j])
//源點經過x到j 的距離小於其他路到j 的距離
{
dist[j]=dist[x] + g[x][j];//更新
path[j]=x;//j經過x
}
}
}
bellman-ford演算法
鬆弛原理,效率O(NV),可以處理負邊權,也可以判斷是否用負權迴路。採取邊目錄儲存
bellman-ford的理論:
對於不存在負權迴路的圖,意味著,最短路最多(不算起點)只經過n-1個節點。
那麼就可以通過n-1次鬆弛操作就可以得到最短路
//採取邊目錄的儲存方式,E[i]表示第i條邊
void b_ford(int start){
dist[start]=0;
for(int i=1;i<n;i++){
for(int j=1;j<=m;j++){//m是邊數
if(dist[E[j].x] + w[j] < dist[E[j].y])
//dist[E[j].x]表示邊j的第一個點到起點的長度
{
dist[E[j].y]= dist[E[j].x] + w[j];
}
if(dist[E[j].y] + w[j] < dist[E[j].x])
//dist[E[j].y]表示邊j的第2 個點到起點的長度
dist[E[j].x]= dist[E[j].y] + w[j];
//這道題是無向圖,所以每個邊要判斷兩次,對於有 向圖則是一次
//write(n);
}
}
//這裡可以在對每個邊判斷是否還能不能鬆弛,如果還能鬆弛,說明有負權迴路
}
SPFA!
SPFA是使用佇列實現的Bellman-Ford演算法。鄰接表儲存()
每次從源點對連線他所有邊(u,v)進行鬆弛,如果能夠鬆弛,說明v可以修改,則將v壓入鬆弛佇列
操作步驟:
1. 初始佇列和標記陣列
2. 源點入隊。
3. 對隊首點出發的所有邊進行鬆弛操作(即更新最小值)。
4. 將不在佇列中的尾結點入隊。
5. 隊首點更新完其所有的邊後出隊。
queue<int> q;
bool inqueue[N]; // 是否在佇列中
int cnt[N]; // 檢查負環時使用:結點進隊次數。如果超過n說明有負環。
bool SPFA(int start) { // 有負環則返回false
// 初始佇列和標記陣列
for (int i=0; i<n; i++) d[i]=INF;
d[start]=0;
memset(cnt,0,sizeof(cnt));
q.push(start); // 源點入隊
cnt[start]++;
while (!q.empty()) {
int x=q.front(); q.pop();
inqueue[x]=false;
// 對隊首點出發的所有邊進行鬆弛操作(即更新最小值)
for (edge *e=adj[x];e!=NULL;e=e->next){
int &v=e->v, &w=e->w;
if (d[v]>d[x]+w) {
d[v] = d[x]+w;
// 將不在佇列中的尾結點入隊
if (!inqueue[v]) {
inqueue[v]=true;
q.push(v);
if (++cnt[v]>n) return false; // 有負環
}
}
}
}
return true;
}