三种算法
floyd
复杂度 :O(n^3)
应用场景:多源最短路,非负边
邻接矩阵存图
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m;
const int MAX_N = 105;
int d[MAX_N][MAX_N];
int main() {
while(scanf("%d%d",&n,&m),n||m){
memset(d,0x3f,sizeof(d));
for(int i = 1;i <= n;i++) d[i][i] = 0;
for(int i = 1;i <= m;i++){
int u,v,x;
scanf("%d%d%d",&u,&v,&x);
d[u][v] = min(d[u][v],x);
d[v][u] = min(d[v][u],x);
}
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]);
}
}
}
printf("%d\n",d[1][n]);
}
return 0;
}
dijkstra:
复杂度O(n^2),优先队列优化后O((n+m)logn)
应用场景:无负边
链式前向星存图
Til the Cows Come Home
#include <cstdio>
#include <queue>
#include <cstring>
#define min(x,y) x<y?x:y
#define P pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m;
int tot = 0;
const int MAX_N = 1e3+5;
const int MAX_M = 2e3+10;
int d[MAX_N];
int edge[MAX_M<<1];
int ver[MAX_M<<1];
int Next[MAX_M<<1];
int head[MAX_M<<1];
void add(int x,int y,int z){
ver[tot] = y;
edge[tot] = z;
Next[tot] = head[x];
head[x] = tot++;
}
void init(){
for(int i=1;i <= MAX_M;i++){
head[i] = Next[i] = -1;
}
memset(d,0x3f,sizeof(d));
}
int main() {
scanf("%d%d",&m,&n);
init();
for(int i = 1;i <= m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
priority_queue< P > q;//优先队列以pair第一维大到小排序,由最短路(最小值),所以放入(-d[i])
d[n] = 0;
q.push(P(0,n));
while(q.size()){
P p = q.top(); q.pop();
int u = p.second;
if(-p.first > d[u])continue;
for(int i = head[u];~i;i = Next[i]){
int v = ver[i],dis = edge[i];
if(d[v] > d[u] + dis){
d[v] = d[u] + dis;
q.push(P(-d[v],v));
}
}
}
printf("%d",d[1]);
return 0;
}
其中(-p.first > d[u])有另一种方式
用vis数组
if(vis[u])continue;
else {
vis[u] = 1;
...
...
}
2021.7.13更新
今天学了Bellman-ford算法, 原因在于这道题
Currency exchange
题意是:有n种货币,给你一些某种货币, 你要不断的换钱,使自己的钱增加。而换币会有汇率和服务费。容易发现, 如果存在一个环, 通过不断地换币回到同一种货币, 若钱的数量增多, 则存在bug可以无限刷钱,输出Yes, 不然输出No
知识点:Bellman-ford算法和最长路
先讲Bellman-ford(没有队列优化)
代码如下
void Bellman(){
memset(d, 0x3f, sizeof d);
d[1] = 0;
for(int i = 1; i <= n; i++){
int flag = 0;
for(int j = 1; j <= m; j++){
int u = upoints[j], v = vpoints[j], w = cost[j];
if(d[u] + w < d[v])
d[v] = d[u] + w, flag= 1;
}
if(!flag)break;
}
}
性质1:不存在负环的情况下, 有效更新最多进行n - 1次,计算的d[]就一定是最短路
解释:参考dijkstra算法, 边权为正数时,每次至少有一个点更新到最小值。
不同的是, bellman在有负边的情况下仍能正常工作, 只要不存在负环, 原因是是, 最短路一定是简单路径, 每次循环时, 能扩充出一个正确的点。
推论1:以最短路为例, 若第n次仍有边满足d[u] + w < d[v], 则存在负环, 最短路不可求
可以用队列优化:
void Spfa(){
memset(d, 0x3f, sizeof d);
d[1] = 0;
que.push(1);
vis[1] = 1;
while(q,size()){
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; i; i = Next[i]){
int v = ver[i], w = cost[i];
if(d[u] + w < d[v]){
d[v] = d[u] + w;
if(!vis[v])vis[v] = 1, q.push(v);
}
}
}
}
队列优化能用的原因仍可以用简单路径解释, 最短路上的第二个点离起点距离为1, 将所有距离为1的点更新完后, 再更新距离为2的点
知识点2:最长路
这个其实不难, 将不等号方向变相即可轻松实现。此时dijkstra不能判断正边, bellman可以判断有无正环