BFS求無權圖的最短路徑
用book數組的值表示路徑長度即可,省略
Floyd算法(允許負邊)
- Floyd算法可以一次性求出所有節點之間的最短距離,且代碼簡單,但是時間復雜度達到了n^3,因此只適用於n<200的情況;
- 原理:任意兩點i,j之間的距離分為兩種情況:過k點和不過k點。從k=1開始操作遍歷到n即可,不過很顯然每次計算基本上只有對k的鄰邊是有效的
- 代碼實現(基於鄰接矩陣):
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
int graph[MAXN][MAXN];
void floyd(int n)
{
int s=1;//求s到n的距離
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
if(graph[i][k] != INF)//如果相等則說明無需遍歷
for(int j=1; j<=n; j++)
if(graph[i][j] > graph[i][k] + graph[k][j])
graph[i][j] = graph[i][k] + graph[k][j];
printf("%d\n",graph[s][n]);//輸出結果
return ;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
memset(graph,0x3f,sizeof(graph));
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
graph[a][b] = graph[b][a] = c;
}
floyd(n);
}
return 0;
}
- Floyd算法適用於鄰接矩陣:由於算法是動態規划的思想,必須有一個二維數組表示點與點之間的距離,所以使用其他圖的表示方法會浪費空間;
- 判斷負圈:通過代碼不難發現,點 i 到自己的距離並非為0,而是graph[i][i] = graph[i][k] + graph[k][i],即繞外面一圈再回來。一旦存在graph[i][i]<0,就說明有負圈存在;
Bellman-Ford算法(允許負邊)
- Bellman-Ford算法解決的是單源最短路徑問題,即起點s到圖中每個點的最短距離;
- 原理:每一輪更新中,對於每一個點,詢問它的鄰居是否可以到達s點:如果可以,當前的點就可以通過鄰居到達s點。不難想象,每一輪更新中至少有一個點到s的最短距離可以被確定下來,所以一共需要更新n輪,時間復雜度為O(n*m);
- 圖的表示方式:如果使用鄰接矩陣,遍歷邊的過程依然為O(n*n),並沒有得到優化,因此使用數組存邊或者鄰接表來存圖;
- 代碼實現:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 5e3 + 10;
struct edge{int u, w, v; } e[MAXN*2];
int pre[MAXN],d[MAXN];//pre存放前置節點,d[i]存放s到點i的距離
void Print_path(int s, int t)//打印s到t的最短路徑
{
if(s == t) printf("%d",s);
else
{
Print_path(s,pre[t]);
printf(" %d",t);
}
}
void bellman(int n, int cnt)
{
int s = 1;//s為起點
memset(d,0x3f,sizeof(d));//初始化為最大值
d[s] = 0;//s到自己的距離為0
for(int k=1; k<=n; k++)
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
printf("%d\n",d[n]);//打印s到n的最短距離
Print_path(s,n);
printf("\n");
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int cnt = 0;
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[cnt].u = a; e[cnt].v = b; e[cnt].w = c; cnt++;
e[cnt].v = a; e[cnt].u = b; e[cnt].w = c; cnt++;
}
bellman(n,cnt);
}
return 0;
}
- 判斷負圈:很明顯當負圈存在時,程序會一直存在距離更新,因此要判斷循環次數是否超過了n,如果超過說明有負圈;
- 優化后的代碼:
bool bellman(int n, int cnt)
{
int s = 1;//s為起點
memset(d,0x3f,sizeof(d));//初始化為最大值
d[s] = 0;//s到自己的距離為0
bool updata = true;//updata表示上一輪有沒有更新,如果有則繼續更新,否則停止更新
int k = 0;//k表示循環次數
while(updata)
{
updata = false;//當前一輪還未進行過更新
k++;
if(k>n) return false;//循環次數超過n則返回有負圈
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
updata = true;//發生了更新操作
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
}
printf("%d\n",d[n]);//打印s到n的最短距離
Print_path(s,n);
printf("\n");
return true;
}
SPFA算法(允許負邊)
- SPFA是對Bellman-Ford算法的優化。在Bellman-Floyd算法每一輪的更新中,如何確定當前節點v需不需要更新?很明顯,當且僅當v的鄰居節點的最短路徑發生變動的時候,節點v才需要更新。SPFA算法使用BFS的思想,把需要更新的節點放進隊列中,當隊列為空時,算法結束。
- 判斷負圈:節點每進入一次隊列即為當前節點更新了一次,由Bellman-Ford算法中的結論可知,當存在一個節點更新次數超過n次時,說明一定有負圈。
- 代碼實現(:
//基於鄰接表
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 10;
struct edge
{
int to,w;
};
vector<edge>e[MAXN];
int pre[MAXN];//記錄路徑
bool inq[MAXN];//是否在隊列內,優化用
int Neg[MAXN];//記錄循環次數判斷負圈
int dis[MAXN];//記錄最短距離
void print_path(int s, int t)//遞歸輸出最短路徑
{
if(s == t) {printf("%d",s); return ;}
print_path(s, pre[t]);
printf(" %d",t);
}
void print_path2(int s, int t)//非遞歸的路徑輸出,適用於極端情況
{
stack<int>ans;
while(s!=t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
bool spfa(int s, int n)
{
memset(dis,0x3f,sizeof(dis));
memset(inq,false,sizeof(inq));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
inq[s] = true;
dis[s] = 0;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
inq[u] = false;
for(int i=0; i<e[u].size(); i++)
{
int v = e[u][i].to;
int w = e[u][i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
inq[v] = true;
Q.push(v);
Neg[v]++;
if(Neg[v] > n) return false;//返回值為false代表有負圈
}
}
}
}
printf("%d\n",dis[n]);
print_path2(s,n);
return true;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
for(int i=0; i<m; i++)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
edge t = {b,c};
e[a].push_back(t);
}
spfa(1,n);
}
return 0;
}
//基於鏈式前向星的SPFA算法
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NUM = 1e6 + 10;
struct Edge
{
int to,next,w;
}edge[NUM];
int cnt;
int head[NUM];
int dis[NUM];
bool inq[NUM];
int Neg[NUM];
int pre[NUM];
void print_path(int s,int t)//打印起點s到t的最短路徑
{
stack<int>ans;
while(t!=s)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
void init()//前向星的初始化
{
for(int i=0; i<NUM; i++)
{
edge[i].next = -1;
head[i] = -1;
}
cnt = 0;
}
void addedge(int u, int v, int w)//前向星的加邊操作
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
bool spfa(int s, int n)
{
memset(inq,false,sizeof(inq));
memset(dis,0x3f,sizeof(dis));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
dis[s] = 0;
inq[s] = true;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front(); Q.pop();
inq[u] = false;
for(int i=head[u]; i!=-1; i=edge[i].next)
{
int v = edge[i].to; int w = edge[i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
Q.push(v);
inq[v] = true;
Neg[v]++;
if(Neg[v] > n) return false;
}
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
return true;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
init();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
addedge(a, b, c);
}
spfa(1, n);
}
return 0;
}
Dijkstra算法(無法求負邊)
- Dijkstra算法應用了貪心的思想,即從起點開始抄近路走。類似於多米諾骨牌,即從起點開始推倒骨牌,當節點第一次被到達時最短路徑被確定。
- 代碼借助STL中的優先隊列完成,每次取出到S距離最短的節點來模擬多米諾骨牌模型。
//基於鄰接表
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NUM = 1e5;
struct edge
{
int from, to, w;
edge(int a, int b, int c)
{
from = a;
to = b;
w = c;
}
};
vector<edge>e[NUM];
struct s_node
{
int id, n_dis;
s_node(int b, int c)
{
id = b;
n_dis = c;
}
bool operator < (const s_node & a) const
{
return n_dis > a.n_dis;
}
};
int dis[NUM];
bool done[NUM];
int pre[NUM];//記錄前驅結點
void print_path(int s, int t)
{
stack<int>ans;
while(s != t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
void dijkstra(int s,int n)
{
memset(dis, 0x3f, sizeof(dis));
memset(done, false, sizeof(done));
dis[s] = 0;
priority_queue<s_node>Q;
Q.push(s_node(s, dis[s]));
while(!Q.empty())
{
s_node u = Q.top();
Q.pop();
if(done[u.id])
continue;
done[u.id] = true;
for(int i=0; i<e[u.id].size(); i++)
{
edge y = e[u.id][i];
if(done[y.to])
continue;
if(dis[y.to] > y.w + u.n_dis)
{
dis[y.to] = y.w + u.n_dis;
Q.push(s_node(y.to, dis[y.to]));
pre[y.to] = u.id;
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
for(int i=1; i<=n; i++)
e[i].clear();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[a].push_back(edge(a,b,c));
e[b].push_back(edge(b,a,c));
}
dijkstra(1, n);
}
return 0;
}