最短路
Bellman-Ford
最短路中一定不含环(因为含有负环的最短路不存在,零环和正环可以除去),所以从起点到终点的最短路经过的边数不超过\(n-1\),所以一定可以通过\(n-1\)轮松弛得到最短路,每一轮松弛用所有边进行更新
如果第\(n\)次松弛依然有起点到某个顶点的最短路被更新,说明从起点可以到达一个负环,最短路不存在
可以增加一个优化,如果在某轮松弛中起点到任何顶点的最短路都没有被更新,那么不需要进行下一轮松弛,直接退出
时间复杂度\(O(nm)\),其中\(n\)为点数,\(m\)为边数
const int inf=0x3f,maxn=110,maxm=10010;
int n,m,dis[maxn];
struct Edge{
int u,v,w;
}edge[maxm];
void Bellman_Ford(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
int check;
for(int i=1;i<=n;i++){
check=0;
for(int j=1;j<=m;j++){
int u=edge[j].u,v=edge[j].v,w=edge[j].w;
if(dis[v]>dis[u]+w){
dis[v]=min(dis[v],dis[u]+w);
check=1;
}
}
if(!check) break;
}
}
SPFA
SPFA是经过队列优化的Bellman-Ford算法,在Bellman-Ford的某轮松弛中,如果起点到某个顶点的距离没有被更新,那么在下一轮中,就不需要从这个顶点出发去松弛其他顶点了
所以通过一个先进先出的队列来保存松弛过的顶点,每次取出队首顶点\(u\),并且枚举从\(u\)出发的所有边\((u, v)\),如果\(dis[u]+w(u,v)<dis[v]\),则更新\(dis[v]\),然后判断顶点\(v\)是否已经在队列中,如果不在就将顶点\(v\)插入队尾。这样不断从队列中取出队首顶点来进行松弛操作,直至队列为空
任意顶点被更新的次数一定小于\(n\),如果某个顶点第\(n\)次被更新,则说明从起点到终点的路径中存在负环
SPFA算法在最坏的情况下,时间复杂度和Bellman-Ford算法一样,为\(O(nm)\)。但是一般不会达到这个上界,一般的期望时间复杂度为\(O(km)\),其中\(k\)为常数
const int inf=0x3f3f3f3f;
int dis[maxn];
bool inq[maxn];
queue<int> q;
void spfa(int s){
for(int i=1;i<=n;i++){
dis[i]=(i==s)?0:inf;
inq[i]=(i==s);
}
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
inq[u]=false;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
int w=weight[i];
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!inq[v]){
q.push(v);
inq[v]=true;
}
}
}
}
}
Dijkstra
Dijkstra算法每次从最短距离已经确定的顶点开始松弛,最短距离已经确定的点就是当前\(dis[i]\)最小的点
如果如中存在负边权,则无法确定某个顶点是否已经取到最短距离,所以Dijkstra算法无法处理含有负边权的图
每次通过遍历所有顶点找到\(dis[i]\)最小的顶点,时间复杂度\(O(n^2+m)\)
const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn],book[maxn];
void Dijkstra(int s){
for(int i=1;i<=n;i++) dis[i]=(i==s)?0:inf;
memset(book,0,sizeof(book));
book[s]=1;
for(int i=head[s];~i;i=nxt[i]){
int v=to[i],w=weight[i];
dis[v]=min(dis[v],dis[s]+w);
}
for(int k=1;k<n;k++){
int m=inf,id;
for(int i=1;i<=n;i++){
if(!book[i]){
if(m>dis[i]){
m=dis[i];
id=i;
}
}
}
book[id]=1;
for(int i=head[id];~i;i=nxt[i]){
int v=to[i],w=weight[i];
dis[v]=min(dis[v],dis[id]+w);
}
}
}
Dijkstra堆优化
在查找\(dis[i]\)最小的顶点时,可以使用优先队列优化
时间复杂度\(O(m\log n)\)
const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn],book[maxn];
void Dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
memset(book,0,sizeof(book));
priority_queue<PII> pq;
pq.push({0,s});
while(!pq.empty()){
PII p=pq.top();
pq.pop();
int u=p.second;
if(book[u]) continue;
book[u]=1;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
int w=weight[i];
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pq.push({-dis[v],v});
}
}
}
}
不使用book数组,直接判断当前从优先队列中取出的点是否已经无用
const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn];
void Dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
priority_queue<PII> pq;
pq.push({0,s});
while(!pq.empty()){
PII p=pq.top();
pq.pop();
int d=p.first;
int u=p.second;
if(dis[u]<d) continue;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
int w=weight[i];
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pq.push({-dis[v],v});
}
}
}
}
Floyd
计算任意两点间的最短路,基于动态规划,设\(dis[k][i][j]\)表示只允许经过\([1,k]\)中转的情况下,\(i\)和\(j\)之间的最短路。所以有两种情况:
1.如果最短路经过\(k\),则\(dis[k][i][j]=dis[k-1][i][k]+dis[k-1][k][j]\)
2.如果最短路不经过\(k\),则\(dis[k][i][j]=dis[k-1][i][j]\)
于是有状态转移方程:\(dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][k]+dis[k-1][k][j])\)
时间复杂度\(O(n^3)\),滚动数组优化之后,空间复杂度\(O(n^2)\)
const int maxn=110;
int dis[maxn][maxn];
void Floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j || i==k || k==j) continue;
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
}