图论之最短路径问题(朴素Dijksra算法\堆优化版Dijksra算法\Bellman-Ford\SPFA)

news2024/11/14 3:10:03

朴素Dijskra算法

时间复杂度:O(n^{2}),适用于稠密图,需要用邻接矩阵来存储。

算法描述

设起点为s,dist[v] 表示起点到v点的最短距离。
a)初始化 dist[v]=INF(v!=s),dist[s] = 0 

这里一共有n个点,第一个点(起点)初始化为0,其余都初始化为inf。

for(int i=1;i<=n;i++)对一个一个点进行遍历

1.在没有被访问过的节点中,找一个顶点u,使得dist[u]是最小的

2.u标记为已经确定的最短路径

3.For与u相连的每一个未确定的最短路径节点v

if(dist[u]+w[u][v] < dist[v]){

        dist[v] = dist[u]+w[u][v];

}

算法结束。

图例

假如有以下图:

初始化:

第一轮:

中间场:

此时要再dist数组中找到一个具体起点最短的点,这里是2号点,要把2好点设置为已经确定是最小路径了(如何证明?百度自己搜doge)。

第二轮:

接着遍历与2号节点相连的并且没有确定最短路径的点(这里是3、5),这里因为d[2]+w[2][3]<d[3]。d[2]+w[2][5]<d[5]。所以更新3和5号点。

中间场:

此时还未确定最短路径的还有3、4、5号点,最小的是3号点。将3号点距离确定为最小。

第三轮:

查看与3好点相连的还没有确定最短路径的点。即4、5号点,更新了4号点。

中间场:

再剩下的没有确定最短路径的点中找到最小的,这里是5号点,将其设置为确定状态。

第四轮:

找到剩余没有确定最短路径的点,即4号点,将其设置为最短的。

算法结束。

例题:

第一行包含整数 n和 m。

接下来 mm 行每行包含三个整数 x,y,z表示存在一条从点 x到点 y 的有向边,边长为 z。

输出格式:

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

#include<iostream>
#include<cstring>
using namespace std;
const int N = 510;
int g[N][N];//图的邻接矩阵
int dist[N];//每个点距离起点的最短距离
bool st[N];//每个点是否已经确定最短距离
int n,m;

int dijkstra(int start){
    memset(dist,0x3f,sizeof(dist));
    dist[start] = 0;
    //st[start] = true;这个不需要,因为后面是从1号遍历到n号,所以第一次选没确定的要选上起点
    for(int i=0;i<n;i++)
    {
        int t = -1;//t来存储剩余的没有确定最短轮径的最小的节点。
        for(int j=1;j<=n;j++)
        {
            if(!st[j] && (t==-1 ||dist[t]>dist[j]))
            {        //并上t==-1是如果t==-1就不执行后面的了
                t = j; //因为在一个集合中,我们不能确定哪一个点还没有被确定最短路径,所以让他自己遍历,遍历到第一个就是t的值
            }
        }
        st[t] = true;
        
        for(int j=1 ; j<=n ;j++)
        {
            if(dist[j]>dist[t] + g[t][j])
            {
                dist[j] = dist[t] + g[t][j];
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)
    {
        return -1;
    }
    else
    {
        return dist[n];
    }
}

int main(){
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof(g));
    for(int i=0;i<m;i++){//存储图
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b] = min(g[a][b],c);//有多条边,只要保存长度最小的那一条。
        
    }
    
    int res = dijkstra(1);
    cout<<res;
    return 0;
}



堆优化版Dijskra算法

时间复杂度:O(mlog(n)),适用于稠密图,用邻接表来存储。

优化点:此时点数很多,如果载用邻接矩阵存储显然不现实,所以需要用邻接表。上面朴素版的是直接循环的,在第二层循环中找到还没有确定最短路径的最小的点是O(n)。如果用优先队列则只需要O(1)。只是建立和维护队列需要O(logn)。

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 150009;
int h[N],e[N],ne[N],w[N],dist[N],idx,n,m;
bool st[N];
void add(int a,int b,int c){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    w[idx] = c;
    idx++;
}

int dijkstra(int start){
    memset(dist,0x3f,sizeof(dist));
    dist[start] = 0;
    
    priority_queue<PII,vector<PII>,greater<PII> > heap;
    heap.push({0,start});//这里把编号放在第二位,因为默认比较的时候是比较pari的第一位。
    while(!heap.empty()){
        PII t = heap.top();
        heap.pop();
        int ver = t.second,distance = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j = e[i];
            if(dist[j]>distance+w[i]){
                dist[j] = distance+w[i];
                heap.push({dist[j],j});
            }
        }
        
    }
    
    if(dist[n]==0x3f3f3f3f)
    {
        return -1;
    }
    else
    {
        return dist[n];
    }
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    for(int i=0;i<m;i++){//存储图
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int res = dijkstra(1);
    cout<<res;
    return 0;
}



Bellman-Ford

时间复杂度:O(nm)

可以解决存在负全边的最小路径问题。

算法描述:

//设置s为起点,dist[v]为s到v的最短距离,pre[v]为v的前驱。w[j]为边j的长度,且j连接u、v。
//初始化:dist[s]=0,dist[v]=inf(v!=s),pre[s]=0
//For(i = 1;i<n;i++)
//For(j=1;j<=M;j++)
//  if(dist[u[j]] + w[j] < dist[v[j]])    u[j]、v[j]分别是这条边连接的两个起点和终点
//  {
//      dist[v[j]] = dist[u[j]] + w[j];
//      pre[v[j]] = u[j];
//  }

图例:

假如给出这些边:

2         3         2

1         2         -3

1         5         5

4         5         2

3         4         3

第一轮第一次遍历边:

不用更新

第一轮第二次遍历边:

更新节点2的dist

第一轮第三次遍历边:

更新节点5的dist

第一轮第四次遍历边:

不满足条件。

第一轮第五次遍历边:

不满足条件

第二轮第一次遍历边:

更新3号节点dist

...

最后会得到最短路径。

853. 有边数限制的最短路 - AcWing题库

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510,M=10009;
int n,m,k;
struct Edge{
    int a,b,w;
}edgs[M];

int dist[N],copydist[N];

int Bellman_Ford(int start){
    memset(dist,0x3f,sizeof(dist));
    dist[start] = 0;
    for(int i=0;i<k;i++){这里最多循环到k是因为要求:最多经过 k条边的最短距离,这就只需要最多k次就可以完成。
        memcpy(copydist,dist,sizeof(dist));//这里备份的原因是因为最长的是k,如果一层循环中在刚刚更新的基础上再次循环边,就可能出现超过k的情况
        for(int j=1;j<=m;j++){
            int a = edgs[j].a,b=edgs[j].b,w=edgs[j].w;//注意这里的a、b是节点编号,w是这条边的权重
            dist[b] = min(copydist[a]+w,dist[b]);//注意这里是用原来的dist即备份过的copydist来更新此次的dist
            
        }
    }
    
    if(dist[n]> 0x3f3f3f3f /10) return 0;
    else return dist[n];
    
}


int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int a1,b1,c1;
        scanf("%d%d%d",&a1,&b1,&c1);
        edgs[i] = {a1,b1,c1};
    }
    
    int t = Bellman_Ford(1);
    if(t==0) printf("impossible");
    else{
        printf("%d",t);
    }
    
    return 0;
}

SPFA

时间复杂度:一般O(m),最坏:O(mn)

正常情况下Bellman-Ford算法(不是限制了边数),最主要的就是更新dist。

即:dist[b] = min(dist[a]+w,dist[b]);

我们仔细想一下,好像只有b的前驱a变小了,那么b才有可能变小。

那么我们可以用一个队列来存储所有“变小”的节点,然后每次再从队列中拿出这个点,只更新这个点的指向的边。

算法描述:

queue<--1

while queue非空

1.  t<--q.front();

     q.pop();

2.更新t的所有出边 t-->b

    queue<--b

851. spfa求最短路 - AcWing题库

这里初始化dist为最大值时有点问题,先自定义一个INF然后用fill这样安全一点。

如果初始化0或者-1可以直接用memset。

#include<iostream>
#include<queue>
#include<cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
const int INF = 1000000000;
int n,m;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool st[N];//用来判断这个点是否已经在队列中

void add(int a,int b,int c){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    w[idx] = c;
    idx++;
}

int spfa(int start){
    fill(dist,dist+N,INF);
    dist[start] = 0;
    queue<int> q;
    q.push(start);
    st[start] = true;
    while(!q.empty()){
        int t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t];i != -1 ;i = ne[i]){
            int j = e[i];
            if(dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == INF) return 0;
    return dist[n];
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int res = spfa(1);
    if(res == 0) printf("impossible");
    else{
        printf("%d",res);
    }
    
    return 0;
}

判断是否存在负环

我们的SPFA算法也可以判断一个图中是否存在负数环。

求解思想如下:

我们可以增加一个数组来记录当前节点距离起点的边数,我们每次更新dist数组时有dist[t]=dist[j]+w[i],此时我们也更新cnt[t] = cnt[j] + 1;这里cnt[v]存储的是点v到起点的最短路径的边的数量。又因为只要有负数边,那么就一定会更新,一个整数加一个负数一定小于原来的数。因为原点1不一定可以到达负边的圈,所以需要把所有点都入队。

#include<iostream>
#include<queue>
#include<cstring>
#include <algorithm>
using namespace std;
const int N = 2009,M=10009;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int cnt[N];

void add(int a,int b,int c){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    w[idx] = c;
    idx++;
}

int spfa(){
    queue<int> q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i] = true;
    }
    while(!q.empty()){
        int t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t];i != -1 ;i = ne[i]){
            int j = e[i];
            if(dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] +1;
                if(cnt[j]>=n) return 1;
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return 0;
}

int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int res = spfa();
    if(res) printf("Yes\n");
    else{
        printf("No");
    }
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1966643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

COFFEE AI PARTNER -- 神奇的AI工具,相当我雇佣了一个AI员工,淘汰你的是会使用AI的人

COFFEE AI PARTNER介绍 COFFEE AI PARTNER是由 AI JAVA开发的一款生成式人工智能工具&#xff08;又名AI助手&#xff09;&#xff0c;尝试一下。 首先域名似乎正在备案中&#xff0c;企业邮箱似乎正在采购&#xff0c;目前服务地址是&#xff1a;COFFEE AI PARTNER-官网 官网…

基于JSP技术的教学质量评价系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;JSP 数据库&#xff1a;MySQL 技术&#xff1a;JSPJavaBeans 工具&#xff1a;MyEclipse、Tomcat、Navicat 系统展示 首页 管理员功能模块 学生功…

前端面试:八股文系列(一)

更多详情&#xff1a;爱米的前端小笔记&#xff08;csdn~xitujuejin~zhiHu~Baidu~小红shu&#xff09;同步更新&#xff0c;等你来看&#xff01;都是利用下班时间整理的&#xff0c;整理不易&#xff0c;大家多多&#x1f44d;&#x1f49b;➕&#x1f914;哦&#xff01;你们…

大学新生入门编程的最佳路径

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

微服务架构三大利器:限流、降级与熔断

文章目录 前言一、限流&#xff08;Rate Limiting&#xff09;二、降级&#xff08;Degradation&#xff09;三、熔断&#xff08;Circuit Breaker&#xff09;四、三者关系总结 前言 限流、降级和熔断是分布式系统中常用的容错策略&#xff0c;它们各自承担着不同的角色&#…

5.Gateway-微服务统一网关

5.Gateway-微服务统一网关 1.为什么需要网关2.Spring Cloud Gateway2.1 引入依赖2.2 编写启动类2.3 配置路由规则2.4 路由断言&#xff08;Predicates&#xff09;2.5 过滤器&#xff08;Filters&#xff09;2.6 熔断机制 1.为什么需要网关 统一访问入口&#xff1a;在微服务架…

【微分方程——高数】

7.二阶常系数非齐次线性微分方程&#xff08;重点&#xff09; 8.欧拉方程&#xff08;重点&#xff09;

Prompt提示工程上手指南:基础原理及实践-Prompt个性知识库引导

前言 Prompt系列的第二期文章已经将所有的Prompt工程主流策略讲解完毕&#xff0c;共涉及到六种Prompt类别模型以及具体生产内容详解。再结合系列第一篇文章具体对Prompt工程的详细介绍&#xff0c;也就可以达到Prompt工程师的初步入门&#xff0c;现在如果掌握了这些基础技能…

缓存击穿

概念 缓存击穿问题也叫热点key问题&#xff0c;指的是一个被高并发访问并且缓存重建业务较为复杂的key突然失效了&#xff0c;大量的请求会到达数据库给数据库带来巨大的冲击。 常见解决方法有两种&#xff1a;互斥锁&#xff0c;逻辑过期。 优缺点 &#xff1a; 基于互斥锁的…

Python多进程:如何在不依赖Queue的情况下传递结果

随着数据的爆炸式增长&#xff0c;网络爬虫成为获取信息的强大工具。在爬取大量数据时&#xff0c;多进程技术可以显著提高效率。然而&#xff0c;如何在多进程中传递结果&#xff0c;而不依赖Queue&#xff0c;成为了一个值得探讨的问题。本文将以采集抖音短视频为案例&#x…

web框架:Django进阶(一)

文章目录 django进阶内容回顾1.模板1.1 寻找html模板顺序1.2 模板处理的本质1.3 常用语法1.4 内置函数1.5 自定义模板功能1.6 继承和母版1.7 模板的导入 2.django中间件2.1 原始方式2.2 MiddlewareMixin&#xff08;建议&#xff09;2.3 prcess_request的执行时&#xff0c;是否…

【系统设计】软件项目概要设计说明书(2024原件完整版)

1引言 1.1编写目的 1.2项目背景 1.3参考资料 2系统总体设计 2.1整体架构 2.2整体功能架构 2.3整体技术架构 2.4运行环境设计 2.5设计目标 3系统功能模块设计 3.1个人办公 3.2系统管理 4性能设计 4.1响应时间 4.2并发用户数 5接口设计 5.1接口设计原则 5.2接口实现方式 6运行设计…

qiankun 微前端 隔离子应用样式,解决 ant-design-vue 子应用样式污染问题(已落地)

样式冲突产生原因 先分析乾坤qiankun 构建之后&#xff0c;会根据你的配置 给每个子应用生成一个id&#xff0c; 当加载到对应子应用的时候&#xff0c;就把内容放到对应的id 标签里去&#xff0c; 这样能有效的隔离 js 代码&#xff0c;但是样式是加载在全局的 所以 当两个子…

【CSS】分享个纯CSS实现去除白底图效果的小技巧

效果 原理 技巧来源&#xff1a;Amazon的产品列表页 通过底色与遮罩层的透明度搭配实现&#xff0c;整体的"去白底"效果 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"view…

实施数据治理的十大优势

关注公众号网络研究观获取更多内容。 数据治理不仅仅是一个流行词&#xff0c;也是强大数据管理策略的基础要素。通过实施结构化数据治理&#xff0c;组织可以获得显著的好处&#xff0c;从而提高效率、合规性和决策能力。 本博客探讨了数据治理的最大优势&#xff0c;详细介…

android13禁止应用卸载功能 禁止卸载包名 安卓禁止卸载应用

总纲 android13 rom 开发总纲说明 目录 1.前言 2.情况分析 3.代码修改 4.编译运行 5.写在最后 6.彩蛋 1.前言 Android 13增加禁止卸载对应包名的应用,这一功能主要是为了增强系统的安全性和稳定性。通过禁止用户卸载某些预装应用,防止这些应用被误删或恶意卸载,从而…

详细记录swfit微调interVL2-8B多模态大模型进行目标检测(附代码)

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 RAGOnMedicalKG&#xff1a;大模型结合知识图谱的RAG实现DSPy&#xff1a;变革式大模…

Python 线程的自修复

在 Python 中&#xff0c;线程的自修复通常涉及异常处理和适当的线程管理。在线程的 run() 方法中使用 try-except 块来捕获可能发生的异常。在捕获异常后&#xff0c;可以记录异常信息或者尝试重新启动线程以恢复正常运行。下面看看我最近的一个实操案例。 1、问题背景 我创建…

bugku.ctf ---WEB(还有后续)

bugku.ctf ---WEB 1.Simple_SSTI_1 1.启动场景 2. 页面说你需要输入一个名为flag的参数。 3.查看网页源代码&#xff0c;提示在flask中&#xff0c;设置了secret_key。意思是在注入模板中输入内容就会显示对应的值。 4.传入?flag{{config.SECRET_KEY}}显示flag 2.Simple_SST…

【Android】安卓多媒体之通知、摄像头、相册、播放音乐、视频用法总结

文章目录 一、通知1. 申请权限2. 创建通道3. 创建通知4. 发送通知拓展功能点击行为更新通知取消通知锁屏通知富文本通知 二、摄像头1. 申请权限2. 调用逻辑3. 声明内容提供器 三、打开相册1. 申请权限检查并请求权限处理权限请求结果 2.处理图片从相册中选择图片处理选择图片的…