单源最短路

1. 什么是单源最短路问题?
  • 定义:给定一个图(有向或无向),每条边有一个权重(可以是正数、负数或零),以及一个源点 s s s(起始节点),目标是找到从 s s s到所有其他节点的最短路径长度(即路径上所有边权重之和的最小值)。
  • 重要性广泛应用于现实世界,如地图导航(求最短行驶距离)、网络路由(求最小延迟路径)等。 广泛地出现在NOIP T2/T3 中。
  • 关键术语
    • 节点(顶点):图中的点。
    • 边:连接节点的线,权重用 w ( u , v ) w(u,v) w(u,v)表示(例如,边 ( u , v ) (u,v) (u,v)的权重)。
    • 源点:固定起点 s s s
    • 最短路径长度:从 s s s到节点 v v v的最小权重和,记为 d i s t [ v ] dist[v] dist[v]
2. 基本概念和前提
  • 图类型:可以是稀疏图(边少)或稠密图(边多),权重可正可负。
  • 核心思想:通过“松弛”操作更新距离估计。松弛是指:对于边 ( u , v ) (u,v) (u,v),如果 d i s t [ v ] > d i s t [ u ] + w ( u , v ) dist[v] > dist[u] + w(u,v) dist[v]>dist[u]+w(u,v),则更新 d i s t [ v ] dist[v] dist[v] d i s t [ u ] + w ( u , v ) dist[u] + w(u,v) dist[u]+w(u,v)
  • 问题分类
    • 非负权图:所有边权重$ \geq 0$,适合Dijkstra算法。
    • 含负权图:允许负权重,但需避免负权环(否则无解),适合SPFA算法。
  • 新手提示:先从简单例子入手,理解权重和路径的概念。
3. Dijkstra算法(非负权图专用)
  • 适用条件:图中所有边权重非负(即 w ( e ) ≥ 0 w(e) \geq 0 w(e)0)。如果图有负权,结果可能错误。
  • 原理:贪心策略。每次选择当前距离最小的未处理节点,逐步扩展,确保每一步都是局部最优。
  • 步骤详解(简单版):
    1. 初始化:设 d i s t [ s ] = 0 dist[s] = 0 dist[s]=0,其他 d i s t [ v ] = ∞ dist[v] = \infty dist[v]=(一个大数),所有节点标记为未访问。
    2. 循环:从所有未访问节点中选取 d i s t [ u ] dist[u] dist[u]最小的节点 u u u
    3. 松弛:对 u u u的每个邻居 v v v,检查是否 d i s t [ v ] > d i s t [ u ] + w ( u , v ) dist[v] > dist[u] + w(u,v) dist[v]>dist[u]+w(u,v),如果是则更新 d i s t [ v ] dist[v] dist[v]
    4. 标记 u u u为已访问。
    5. 重复步骤2-4,直到所有节点访问完。
  • 时间复杂度:使用优先队列时为 O ( ( V + E ) log ⁡ V ) O((V+E)\log V) O((V+E)logV) V V V为节点数, E E E为边数)。
  • 优点:高效、简单。
  • 代码示例(C++)
#include<bits/stdc++.h>
using namespace std;

const int N = 5e6+10;

int head[N], dis[N], cnt;
bool vis[N];
int n, m, s;

struct edge { int to, dis, next; } e[N];

inline void add(int u, int v, int d)
{
 cnt++;
 e[cnt].dis = d;
 e[cnt].to = v;
 e[cnt].next = head[u];
 head[u] = cnt;
}

struct node
{
 int dis;
 int pos;
 bool operator < (const node &x)const { return x.dis < dis; }
};

priority_queue <node> q;


void dijkstra()
{
 dis[s] = 0;
 q.push((node){0, s});
 while(!q.empty())
 {
     node tmp = q.top();
     q.pop();
     int x = tmp.pos, d = tmp.dis;
     if(vis[x]) continue;
     vis[x] = 1;
     for (int i = head[x]; i; i = e[i].next)
     {
         int y = e[i].to;
         if (dis[y] > dis[x] + e[i].dis)
         {
             dis[y] = dis[x] + e[i].dis;
             if(!vis[y])
                 q.push(( node ){dis[y], y});
         }
     }
 }
}


int main()
{
 ios::sync_with_stdio(false);
 cin.tie(0);

 cin >> n >> m >> s;

 for(int i = 1; i <= n; ++i)
     dis[i] = INT_MAX;
 for( register int i = 0; i < m; ++i )
 {
     register int u, v, d;
     cin >> u >> v >> d;
     add( u, v, d );
 }

 dijkstra();

 for( int i = 1; i <= n; i++ )
     cout << dis[i] << ' ';
 return 0;
}

[Luogu模版提P4779](https://www.luogu.com.cn/problem/P4779)
#### 4. **SPFA算法(含负权图专用)**
*其实并非专用,而是Dijkstra无法解决负环图问题,在一般情况下Dijkstra比SPFA效率高*
- **适用条件**:允许边权重为负,但图中不能有负权环(否则无解)。SPFA是Bellman-Ford算法的优化版。
- **原理**:基于动态逼近。使用队列存储待处理节点,只对距离更新的节点进行松弛,避免重复计算。
- **步骤详解**1. 初始化:$dist[s] = 0$,其他$dist[v] = \infty$;队列$q$初始含$s$。
  2. 循环:从队列取节点$u$。
  3. 松弛:对$u$的每个邻居$v$,如果$dist[v] > dist[u] + w(u,v)$,则更新$dist[v]$,并将$v$加入队列(如果$v$不在队列中)。
  4. 重复步骤2-3,直到队列空。
  - **负环检测**:如果某个节点入队次数超过$V$次(节点总数),则存在负权环(问题无解)。
- **时间复杂度**:平均$O(E)$,最坏$O(VE)$。
- **优点**:比Bellman-Ford高效,适合网络优化等有负权场景。
- **代码示例(C++**:

首先定义两个数组
```cpp
int dist[maxn];//dist[i]到i点的最短路长度 
bool inq[maxn];//inq[i]代表i点是否在队列中 

接下来直接跑一遍SPFA

inline void spfa(int s)
{
    // 初始化
	memset(dist,0x3f,sizeof(dist));
	dist[s]=0;
	queue<int> q;//用来存储可能改变其他点最短路的点
	q.push(s);
	inq[s] = 1;
	while (q.size() != 0)
	{
        // 取出队首
		int a = q.front();
		q.pop();
		inq[a] = false;
		for (int i = 0; i < e[a].size(); i++)
		{
            // 存边
			int b = e[a][i].first;
			int c = e[a][i].second;
            // 松弛
			if (dist[b] > dist[a] + c)
			{
				dist[b] = dist[a] + c;
				if (!inq[b])
				{
					inq[b] = 1;
					q.push(b);
				}
			} 
		}
	} 
}

Luogu模版题P3371

5. 补充:差分约束系统
  • 定义:一组线性不等式约束,形式如 x j − x i ≤ b k x_j - x_i \leq b_k xjxibk(其中 x i , x j x_i, x_j xi,xj是变量, b k b_k bk是常数)。例如,在任务调度中, x j x_j xj表示任务结束时间, x i x_i xi表示开始时间。
  • 与单源最短路的联系:差分约束问题可以转化为单源最短路问题。具体步骤:
    • 构建图:每个变量对应一个节点;每个约束 x j − x i ≤ b k x_j - x_i \leq b_k xjxibk对应一条边 ( i , j ) (i,j) (i,j),权重 w ( i , j ) = b k w(i,j) = b_k w(i,j)=bk
    • 添加源点:引入一个虚拟源点 s s s,添加边 ( s , v ) (s,v) (s,v),权重 0 0 0(确保所有点可达)。
    • 求解:运行SPFA算法(因可能含负权),得到 d i s t [ v ] dist[v] dist[v]即为变量 x v x_v xv的值。
    • 数学解释:约束 x j − x i ≤ b k x_j - x_i \leq b_k xjxibk等价于 d i s t [ j ] ≤ d i s t [ i ] + b k dist[j] \leq dist[i] + b_k dist[j]dist[i]+bk,这正是松弛操作的目标。
  • 适用场景:资源分配、时间表规划等。
  • 简单示例
    • 约束: x 2 − x 1 ≤ 3 x_2 - x_1 \leq 3 x2x13 x 3 − x 2 ≤ − 1 x_3 - x_2 \leq -1 x3x21
    • 转化图:节点 x 1 , x 2 , x 3 x_1, x_2, x_3 x1,x2,x3;边 ( x 1 , x 2 ) (x_1,x_2) (x1,x2)权重3, ( x 2 , x 3 ) (x_2,x_3) (x2,x3)权重-1;添加源点 s s s到所有点权重0。
    • 求解:SPFA输出 d i s t dist dist即变量值。
  • 新手提示:先掌握Dijkstra和SPFA,再尝试此应用。

Luogu模版题P5960

6. 总结与比较
  • 算法选择指南
    • 权重全非负?用Dijkstra(更快)。
    • 有负权重?用SPFA(需检测负环)。
    • 差分约束问题?转化为SPFA求解。
  • 常见陷阱
    • 负权环:SPFA中需主动检测。
    • 初始化:确保 d i s t [ s ] = 0 dist[s] = 0 dist[s]=0
  • 学习建议
    • 先理解算法步骤,再动手写代码。
    • 用简单图测试(如3-5个节点)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值