最短路
基础思路:Dijkstra是贪心用最短的边去更新其他相邻边,而spfa是上次更新的终点去更新其他边,bellman-ford则是非常暴力的每次更新所有边
1、Dijkstra(非负单源、稠密或大规模稀疏,类bfs+贪心,每次取距离当前点距离最小的新点,用新点来更新‘与新点相连的所有点’到起点的距离)
`
typedef pair<int, int>PII;
int n, m, s;
vector
int dist[100010]; int visi[100010];//dist存储该点到起点的距离,visi存储
void dijkstra() {
memset(dist, 0x3f3f3f3f, sizeof(dist));//非直连起点的是无限大,方便后面更新成其他点过来的距离
dist[s] = 0;
priority_queue<PII, vector
hp.push({ 0,s });//起点到起点距离为0,起点的序数是s
while (!hp.empty()) {
PII t = hp.top(); hp.pop();
if (visi[t.second]) { continue; }//***注意!!!这里是核心区别,Dijkstra不能处理已经处理过的点
visi[t.second] = 1;
for (int j = 0; j < tu[t.second].size(); j++) {
int next = tu[t.second][j].first;
if (dist[next] > dist[t.second] + tu[t.second][j].second) {
dist[next] = dist[t.second] + tu[t.second][j].second;//更新与点t相连的所有点,如果经过t到起点距离更短,就更新,否则不经过t更短,就不更新
hp.push({ dist[next],next });
}
}
}
}
int main() {
cin >> n >> m >> s;//s是题目所给的起点,算法将求起点到所有其他点的最短路
for (int i = 0; i < m; i++) {
int u, v, w; cin >> u >> v >> w;
tu[u].push_back({ v,w });//注意重边、无向需要修改此处,重边注意求min
}
dijkstra();
for (int i = 1; i <= n; i++) {
cout << dist[i] << " ";
}
return 0;
}`
堆优化的dijkstra时间复杂度O((V+E)logV),比O(VE)快一个数量级以上。
2、SPFA(容易被卡)
`const int N=1e5+10;
define fi first
define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N];//各点到源点的距离
bool st[N];int n,m;
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int spfa(){
queue
memset(dist,0x3f,sizeof dist);
dist[1]=0;q.push({0,1});
st[1]=true;
while(q.size()){
PII p=q.front();q.pop();
int t=p.se;
st[t]=false;//从队列中取出来之后该节点st被标记为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]){//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
st[j]=true;
q.push({dist[j],j});
}
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);//注意无向图要存两次
}
int res=spfa();
if(res==-1) puts("impossible");
else printf("%d",res);
return 0;
}`
SPFA判断负环的情况:
`bool spfa(int s) {
queue
vector
vector
fill(dist, dist + n + 1, INF);
dist[s] = 0;
q.push(s);inq[s] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1; // 继承路径长度
if (cnt[v] >= n) return true; // 负环检测
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
}
return false; // 无负环
}`
3、bellman-ford(复杂度O(nm),遍历n次每次遍历所有边并更新它们)
`#include
include
using namespace std;
const int N=510,M=10010;
struct Edge{
int a;
int b;
int w;
}e[M];//把每个边保存下来即可
int dist[N];
int back[N];//备份数组,例如第1次更新了a和b,如果b要用a来更新的话,就要用上一次的a(第0次的a),来保证b是第1次,而第1次更新过的a要在第2次更新的时候才能给b用来更新
int n,m,k;//k代表最短路径最多包涵k条边
int bellman_ford(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++){//k次循环,正常情况下应该是n-1次(for (int i = 1; i <= n - 1; i++) )
memcpy(back,dist,sizeof dist);
for(int j=0;j<m;j++){//遍历所有边
int a=e[j].a,b=e[j].b,w=e[j].w;
dist[b]=min(dist[b],back[a]+w);//使用backup:避免给a更新后立马更新b,这样b一次性最短路径就多了两条边出来
}
}
if(dist[n]>0x3f3f3f3f/2) return -1;
else return dist[n];
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
e[i]={a,b,w};//无向图要增加一次
}
int res=bellman_ford();
if(res==-1) puts("impossible");
else cout<<res;
return 0;
}`
此题是特殊题,求的是最多只能有k条边的情况下从起点到指定点的最短路长度,此时不可用SPFA了。
求负环:在n-1次之后再来一次,当dist[a] < INF / 2时(只允许从可到达的点出发来松弛下一个点),如果有边还能更新(有更小),那么有负环
4、floyd(多源最短路,可以求任意x和y之间的最短路,复杂度为O(n^3))
`
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
int d[N][N];//存储图
void floyd() {
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]);
}
int main() {
cin >> n >> m >> k;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while(m--) {
cin >> x >> y >> z;//输入x,y之间有一条长z的边
d[x][y] = min(d[x][y], z);//注意保存最小的边
}
floyd();
while(k--) {
cin >> x >> y;
if(d[x][y] > INF/2) puts("impossible");
//由于有负权边存在所以约大过INF/2也很合理
else cout << d[x][y] << endl;
}
return 0;
}`
floyd适用范围:有、无向图,重边自环,带负权边,不可出现负环(会无限循环)

浙公网安备 33010602011771号