首页 > 编程语言 >最短路算法

最短路算法

时间:2024-08-13 21:16:04浏览次数:11  
标签:int 短路 负环 算法 复杂度 dis

存在最短路的前提

不存在负环。

链接

还是 OIWiki 好啊~~

Floyd 算法

两两间最短路,可判负环。时间复杂度 \(O(n^3)\)。

像 DP 的思路一样。

设 \(f_{k,x,y}\) 表示允许经过 \(1\sim k\) 的点,\(x\to y\) 的最短距离。

\(O(n^3)\) 的 DP 即可。

按照 \(k,x,y\) 的顺序循环,每次与 \((x,k),(k,y)\) 取 \(\min\) 即可。

f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);

因为第一维对结果无影响,我们可以发现数组的第一维是可以省略的,于是可以直接改成 f[x][y] = min(f[x][y], f[x][k]+f[k][y])

搬 OIWiki 的代码:

for (k = 1; k <= n; k++) {
  for (x = 1; x <= n; x++) {
    for (y = 1; y <= n; y++) {
      f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);
    }
  }
}

综上时间复杂度就是 \(O(n^3)\),空间复杂度就是 \(O(n^2)\)。

Floyd 求图的传递闭包

参考博客

用 bitset 优化可以实现 \(O(\frac{n^3}{w})\) 的复杂度。

Floyd 判负环

\(\exists x,f_{x,x}<0 \Rightarrow \text{存在负环}\)

Bellman-Ford 算法

单源最短路,可判负环。时间复杂度 \(O(nm)\)。

共循环 \(O(n)\) 次,每次枚举每一条边进行松弛操作(即对边 \((u,v)\) 对 \(dis_v\) 进行取 \(\min\) 操作)。若在某一次没有对任意一条边进行松弛操作,可以直接结束循环。

证明只用循环 \(n-1\) 次即可求出最短路。

因为第 \(i\) 次操作可以确定最短路经过 \(i\) 条边的结点的 \(dis\),因此最坏情况下只需要重复 \(n-1\) 次即可。

证毕。

然鹅我们一般循环 \(n\) 次,为的是顺便判一下有没有负环(最短路是否存在)。

判负环

若第 \(n\) 次仍然可以进行松弛操作,则存在负环。

Code (显然是从 OIWiki 搬的)

struct Edge {
  int u, v, w;
};

vector<Edge> edge;

int dis[MAXN], u, v, w;
const int INF = 0x3f3f3f3f;

bool bellmanford(int n, int s) {
  memset(dis, 0x3f, sizeof(dis));
  dis[s] = 0;
  bool flag = false;  // 判断一轮循环过程中是否发生松弛操作
  for (int i = 1; i <= n; i++) {
    flag = false;
    for (int j = 0; j < edge.size(); j++) {
      u = edge[j].u, v = edge[j].v, w = edge[j].w;
      if (dis[u] == INF) continue;
      // 无穷大与常数加减仍然为无穷大
      // 因此最短路长度为 INF 的点引出的边不可能发生松弛操作
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        flag = true;
      }
    }
    // 没有可以松弛的边时就停止算法
    if (!flag) {
      break;
    }
  }
  // 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
  return flag;
}

SPFA 算法

最坏时间复杂度仍为 \(O(nm)\)。

很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。

那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。

SPFA 也可以用于判断 \(s\) 点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少(\(\geq\)) \(n\) 条边时,说明 \(s\) 点可以抵达一个负环。

struct edge {
  int v, w;
};

vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;

bool spfa(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0, vis[s] = 1;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop(), vis[u] = 0;
    for (auto ed : e[u]) {
      int v = ed.v, w = ed.w;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
        if (cnt[v] >= n) return false;
        // 在不经过负环的情况下,最短路至多经过 n - 1 条边
        // 因此如果经过了多于 n 条边,一定说明经过了负环
        if (!vis[v]) q.push(v), vis[v] = 1;
      }
    }
  }
  return true;
}

Dijkstra 算法

单源最短路。时间复杂度 \(O(n^2)\) 或 \(O(m \log m)\)。必须为非负权图。

搬 OIWiki 甚至懒得打字

简单来说,就是每次选一条集合内目前最短的路径,然后扩展一圈,放回集合。

时间复杂度 \(O(n^2)\) 的。

Code(搬的)

struct edge {
  int v, w;
};

vector<edge> e[maxn];
int dis[maxn], vis[maxn];

void dijkstra(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0;
  for (int i = 1; i <= n; i++) {
    int u = 0, mind = 0x3f3f3f3f;
    for (int j = 1; j <= n; j++)
      if (!vis[j] && dis[j] < mind) u = j, mind = dis[j];
    vis[u] = true;
    for (auto ed : e[u]) {
      int v = ed.v, w = ed.w;
      if (dis[v] > dis[u] + w) dis[v] = dis[u] + w;
    }
  }
}

优先队列优化

时间复杂度 \(O(m\log m)\)。

复杂度易证,略。

终于有我自己写的代码了

P4779 【模板】单源最短路径(标准版)

#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=1e5+7;
int cnt,head[N];
struct edge{
	int to,ne,val;
} e[N<<1]; 
void add(int u,int v,int w) { e[++cnt]={v,head[u],w},head[u]=cnt; }
int n,m,s,u,v;
int w;
struct node{
	int u,dis;
	bool operator>(const node &x)const{
		return dis>x.dis;
	}
};
priority_queue<node,vector<node>,greater<node> > q;
int dis[N];
bool vi[N];
void getans(){
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	q.push({s,0});
	while(!q.empty()){
		int u=q.top().u,d=q.top().dis;
		q.pop();
		if(vi[u]) continue;
		vi[u]=1;
		for(int i=head[u];i;i=e[i].ne){
			int v=e[i].to,w=e[i].val;
			if(d+w<dis[v]){
				dis[v]=d+w;
				q.push({v,dis[v]});
			}
		}
	}
}
int main(){
	sf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;i++){
		sf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	getans();
	for(int i=1;i<=n;i++){
		pf("%d ",dis[i]);
	}
}

Johnson 全源最短路径算法

……到时候补充。

差分约束系统

其实这才是我今天的正题额。

OIWiki参考博客另一篇博客

若对这样一组不等式,我们要求 \(max(x_i-x_j)\),那么:

对于 \(x_v-x_u\leq b_k\) 我们建一条有向边 \((u,v,b_k)\),在图上跑单源最短路 SPFA 算法(当然这个例子没有负权,也可以跑 Dijkstra 算法),\(ans=dis_i-dis_j\)。

为什么呢?

可以发现,在求最短路的过程中,对于边 \((u,v,w)\) 总有 \(dis_v\leq dis_u+w\),我们把它叫做三角不等式。即:

\(dis_v-dis_y\leq w\)

这个式子和

\(x_j-x_i\leq b_k\)

是不是很像呢?

因此差分约束系统可以转换成有向图求解。

因为我们的有向图不一定连通,为了方便,我们一般设一个超级源点 \(s\),令 \(dis_s=D\),连边 \(s\to x,1\leq x \leq n\),边权为 \(0\),即增加关系 \(x-s\leq 0,1\le x \le n\)。这样,我们就可以求出一组解 \(\{ x_1,x_2,\dots,x_n\} \le D\)(毕竟 \(s\) 与所有点都有权为 \(0\) 的边,所以 \(dis_x\) 最大也是 \(D\))。

当然,如果差分约束系统里的所有 \(\le\) 换成 \(\geq\) 也可以做,只是把求最短路变成求最长路罢了。

SPFA 最短路 Code

P5960 【模板】差分约束

#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=5e3+7;
int n,m,u,v,w;
int cnt,head[N];
struct edge{
	int to,ne,val;
}e[N<<1];
int s;
void add(int u,int v,int w){
	e[++cnt]={v,head[u],w};
	head[u]=cnt;
}
int dis[N];
bool vi[N];
int tot[N];
bool SPFA(){
	queue<int> q;
	q.push(s);vi[s]=1;
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();vi[u]=0;
		for(int i=head[u];i;i=e[i].ne){
			int v=e[i].to,w=e[i].val;
			if(dis[u]+w<dis[v]){
				dis[v]=dis[u]+w;
				tot[v]=tot[u]+1;
				if(tot[v]>n){//注意因为加入了超级源点,因此一共有 n+1 个点,最短路包含恰好 n 条边是可以的。 
					return 0;
				}
				if(!vi[v]) q.push(v);
				vi[v]=1;
			}
		}
	}
	return 1;
}
int main(){
	sf("%d%d",&n,&m);
	s=n+1;
	for(int i=1;i<=n;i++) add(s,i,0);
	for(int i=1;i<=m;i++){
		sf("%d%d%d",&v,&u,&w);
		add(u,v,w);
	}
	if(SPFA())
	for(int i=1;i<=n;i++){
		pf("%d ",dis[i]);
	}
	else pf("NO\n");
}

经验

balanced1

标签:int,短路,负环,算法,复杂度,dis
From: https://www.cnblogs.com/liyixin0514/p/18357725

相关文章

  • Python实现PID算法
    目录1.PID算法简介2.PID控制器的数学表达式3.Python实现PID算法场景:温度控制4.代码解释5.场景说明6.总结1.PID算法简介PID算法(Proportional-Integral-DerivativeControl)是经典的控制算法之一,广泛应用于自动控制系统中。PID控制器通过调节控制对象的输入,来实现对......
  • Python实现基因遗传算法
    目录基因遗传算法简介基因遗传算法的基本步骤Python实现基因遗传算法场景:优化二次函数Python代码实现代码解释场景说明总结基因遗传算法简介基因遗传算法(GeneticAlgorithm,GA)是一种基于自然选择和遗传学原理的优化算法,适用于求解复杂的组合优化问题。它通过模拟......
  • STL heap 算法库 堆操作
    算法库-堆操作基本操作make_heap()(1)从一个元素范围创建出一个最大堆(2)将区间内的元素转化为heap.--传比较器push_heap()对heap增加一个元素.将一个元素加入到一个最大堆pop_heap()对heap取出下一个元素.从最大堆中移除最大元素sort_heap()对heap转化为一......
  • 小白怎样理解AI大模型与传统算法?
    面对当前的AI大模型技术大潮的冲击,任何事情不提一提AI大模型,都会觉得自己落伍了,被时代所抛弃了。那AI大模型与传统算法到底有什么不同,是否会完全取代传统技术?我们有一个形象的类比,可以帮助大家更容易理解。这个类比的对象就是中医和西医。首先,我们来看看AI大模型与传统算法......
  • ChatGPT 大模型核心算法深度分析 2024
    在分析核心算法之前,我们先了解chatGPT相关技术发展进程首先介绍自然语言处理、大规模预训练语言模型以及ChatGPT技术的发展历程,接着就ChatGPT的技术优点和不足进行分析,然后讨论核心算法。1.1自然语言处理的发展历史人类语言(又称自然语言)具有无处不在的歧义性、高度......
  • 【算法】求1+2+3+...+n
    1.概述地址:JZ64求1+2+3+…+n描述求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。数据范围:0<n≤2000<n\le2000<n......
  • 嵌入式软件--数据结构与算法 DAY 12
    数据结构和算法是程序的核心,虽然在嵌入式应用中很少会用到,但了解认知这种思维过程是非常有必要的。一个好的程序员应该把数据结构和算法看的比代码更重要。1.数据结构是什么?定义1(宏观):数据结构是为了高效访问数据而设计出的一种数据的组织和存储方式。定义2(微观):数据结构......
  • 嵌入式软件--数据结构与算法 DAY 13
    在嵌入式中,对算法的要求不高,但顺序查找和冒泡排序是经典算法,必须掌握。1.算法定义算法是一个用于解决特定问题的有限指令序列(计算机可以执行的操作)。通俗的理解就是可以解决特定问题的方法。2.时间复杂度时间复杂度不是执行完一段程序的总时间,而是描述为一个算法中基本操作......
  • ICP算法
    一、简介    迭代最近点算法(IteratedClosestPoints,ICP),顾名思义,就是采用迭代优化的思想以空间距离作为匹配点的选择依据,通过不断调整点云的位姿使得匹配点之间距离累计最小。假设有两组点云,其中一个目标点云A另一个为参考点云B,ICP算法的目的是为了算出一个最优的旋转......
  • 【SpringBoot+Vue】基于混合推荐算法的小说在线阅读平台
    【1】系统介绍随着互联网技术的发展和普及,网络文学已经成为人们日常生活中不可或缺的一部分。网络小说因其便捷的获取方式、丰富的题材选择以及个性化的阅读体验而受到广大读者的喜爱。然而,在海量的小说资源中,如何为每位读者提供高质量、个性化的阅读推荐,成为了在线阅读平......