1.最短路问题
- 从起点到终点的路径中,边的权值和最小的路径(带权图)
- 单源最短路问题和任意两点间最短路问题(全源最短路问题)
2.单源最短路问题
- SSSP:Single Source Shortest Path
- 求以st点位起点到其他结点的最短路径长度
- min dist[1...n] 特别地, dis[st]=0
单源最短路的算法:
- Dijkstra
- Bellman-Ford
- SPFA
2.1松弛操作
用数组d记录每个顶点的最短路径估计值。若用 u 来松弛v,就是判断是否 d[u] + w[u][v] < d[v],
如果该式成立则用 d[u] + w[u][v] 更新 d[v] ,否则不变。
2.2 Dijkstra算法
- 基于贪心思想
- 仅适用于求边权为正的有向图或无向图的单源最短路。
算法流程
- 初始化 d[源点] = 0,其余顶点的d值为无穷大;
- 在所有未标记顶点中找d值最小的顶点x,并标记之;
- 对x的所有未标记邻接点进行 "松弛操作” 。
- 重复上述2~3两个步骤, 直至所有顶点都被标记。
特别说明: Dijkstra算法基于贪心思想,一旦某顶点被选中并加了访问标记,
则不再被其他顶点松弛!
算法框架
定义数组G,存储图
定义数组d,存储源点到各点的最短距离
dijkstra(s) { //s是源点
初始化d数组;
for(循环n次) {
u=使d[u]最小的还未被访问的定点的标号
记u已经被访问
for(从u出发能到达的所有顶点v)
if(v未被访问 && d[v]可以被松弛)
优化d[v];
}
}
核心代码(邻接矩阵)
void Dijkstra(int s){ //s为源点
fill(d, d+MaxV, INF); //初始化d数组
d[s]=0;
for(int i=0;i<n;i++){
int u=-1, minu=INF; //u使d[u]最小,minu存放最小的d[u]
for(int j=0;j<n;j++) //找到未访问的顶点中d值最小的顶点
if(vis[j]==false && d[j]<minu)
u=j, minu=d[j];
if(u==-1) return; //找不到小于inf的d[u], 说明顶点和源点s不连通
vis[u]=true; //标记u为已访问
for(int v=0;v<n;v++) //松弛操作
if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v])
d[v]=d[u]+G[u][v];
}
}
核心代码(邻接表)
时间复杂度为O(N^2)
void Dijkstra(int s){ //s为源点
fill(d, d+MaxV, INF); //初始化d数组
d[s]=0;
for(int i=0;i<n;i++){
int u=-1, minu=INF; //u使d[u]最小,minu存放最小的d[u]
for(int j=0;j<n;j++) //找到未访问的顶点中d值最小的顶点
if(vis[j]==false && d[j]<minu)
u=j, minu=d[j];
if(u==-1) return; //找不到小于inf的d[u], 说明顶点和源点s不连通
vis[u]=true; //标记u为已访问
for(int j=0;j<G[u].size();j++) //松弛操作 ,以下是两段代码的主要区别
int v=G[u][j].v;
if(vis[v]==false && d[u]+G[u][j].dis<d[v])
d[v]=d[u]+G[u][j].dis;
}
}
算法缺陷
- 基于贪心思想,一旦某顶点被选中并加了访问标记,则不再被其他顶点松弛!
- 仅适用于求边权为正的有向图或无向图的单源最短路。
2.3 Bellman-Ford算法
- 基于迭代思想,解决含负权边的有向图的单源最短路问题。
- 不能处理带权边的无向图(因可以来回走一条负权边)
- 不能处理负环图,如下图所示:
算法思路
- 对图中的边进行n-1轮操作,每轮都遍历图中所有的边。
- 对每条边<u,v>, 如果d[u] + w[u][v] < d[v], 则更新d[v]的值。代码实现
关于Bellman-Ford松弛n-1轮
简单来说,从源点到一个点的最短路的极端情况是路径经过n-1个顶点(不算源点),也就是需要松弛n-1轮。这样,我们执行n-1轮就可以保证所有点都被松弛到最佳情况。
- 如果执行了n-1轮迭代后还能继续松弛,说明图中有负环。
- 有负环,最短路径不存在。
- Yes是存在负环,No是不存在负环
代码实现
Bellman(int s){ //s为源点
fill(d, d+MaxV, INF); //初始化d数组
d[s]=0;
for(int i=0;i<n-1;i++){ //执行n-1轮操作,n为顶点数
for(int u=0;u<n;u++) //每轮都遍历所有的边
for(int j=0;j<G[u].size();j++){
int v=G[u][j].v;
int dis=G[u][j].dis;
if(d[u]+dis<d[v]) //松弛操作
d[v]=d[u]+dis;
}
}
队列优化
- SPFA算法是队列优化的Bellman-Ford算法的别称
- 流程:
- 建立一个队列,最初队列中只包含源点,并给起点打上标记
- 取队头结点想,清除结点x的访问标记,以便再次入队;对x的所有邻接点y进行松弛操作。若y不在队列中,则把y入队。
- 重复上述步骤,直到队列为空。
SPFA代码实现
void SPFA(int s){
queue<int> q;
q.push(s);
vis[s]=true; //设置源点入队
d[s]=0;
while(!q.empty()){
int u=q.front();
q.pop;
vis[u]=false; //设置顶点u不在队列中
for(int j=0;j<G[u].size();j++){
int v=G[u][j].v;
int dis=G[u][j].dis;
if(d[u]+dis<d[v]){
d[v]=d[u]+dis;
if(!vis[v]){
q.push(v); //设置顶点v入队
vis[v]=true;
}
}
}
}
}