图论-最短路

news2024/11/25 6:33:58

 一、不存在负权边-dijkstra算法

dijkstra算法适用于这样一类问题:

从起点 start 到所有其他节点的最短路径。

其实求解最短路径最暴力的方法就是使用bfs广搜一下,但是要一次求得所有点的最短距离我们不可能循环n次,这样复杂度太高,因此dijlstra算法应运而生,算法流程如下:

(待补充)

对于:

稠密图一般使用邻接矩阵+朴素dji

稀疏图使用邻接表+堆优化dji

1.1 朴素djikstra算法

算法模板:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 510;
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra(){
    memset(st,0,sizeof(st));
    memset(dist,0x3f,sizeof(dist));
    
    dist[1] = 0;
    //对于每个点都要遍历所有点,也即是穷举
    for(int i = 0;i < n; i++){
        int t = -1;//临时存储第i次处理的点
        //一、遍历所有点,在所有没有访问过的点中找到距离最近的点
        for(int j = 1; j<= n;j++)
            if(!st[j] && (t == -1 || dist[t] > dist[j])){
                t = j;
            }
            
        //
        st[t] = true;
        //二、用1-t的路径长度 + t - j的边长度来更新dist[j];
        for(int j = 1;j <= n;j++){
            dist[j] = min(dist[j],dist[t] + g[t][j]);
        }
    }
    if(dist[n] == 0x3f3f3f3f)return -1;
    return dist[n];

}

1.2 优先级队列优化的djikstra

首先我们分析,如果是稀疏图,什么导致的朴素版djikstra复杂度高,首先我们用的是邻接表来存图,那么就要用bfs相应的方法进行遍历,那么我们需要先while处理点再内层处理边,如果不优化,复杂度依旧是o(mn)

这是因为我们将所有的顶点都遍历了,用于寻找最小距离点,如果我们使用堆来优化(而不是一般bfs中的普通队列),每次使用优先级队列来存放顶点,因为理想情况下优先级队列中最多装 n 个节点,对优先级队列的操作次数和 m 成正比,所以整体的时间复杂度就是 O(mlogn)

红字解释:

因为在修改其它顶点最短距离的过程中,堆优化版本并没有遍历所有的顶点,而是遍历所有与当前选取的最小顶点有关的边 ,从一小部分顶点出发就能到达所有顶点,因此没有必要遍历所有顶点

优先级队列解释: 

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;

pair中 第一个整数可以表示从源点到该顶点的距离,第二个整数表示顶点的编号。

vector<pair<int, int>>指定了底层容器类型。

greater<>:默认小顶堆,less<>:大顶堆排序

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
//只存一个点,因为另一个点是邻接表的表头
struct Edge {
    int to, weight;
};

vector<Edge> adj[N];
int dist[N];
bool visited[N];

void dijkstra(int source) {
    memset(dist, 0x3f, sizeof(dist));
    memset(visited, 0, sizeof(visited));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
    dist[source] = 0;
    pq.push({0, source});

    while (!pq.empty()) {
        int u = pq.top().second;
        pq.pop();

        if (visited[u]) continue;
        visited[u] = true;

        for (auto &e : adj[u]) {
            int v = e.to;
            int w = e.weight;
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        adj[x].push_back({y, z});
    }

    dijkstra(1); // 假设你想要从节点 1 找到到其他所有节点的最短路径

    if (dist[n] == INF) cout << "-1\n"; // 检查是否存在从1到n的路径
    else cout << dist[n] << "\n";

    return 0;
}

二、存在负权值 -bellma-ford算法

1.朴素Bellman-Ford算法

 三角不等式:

对于所有点都有:

dist[b] <= dist[a] + w

 松弛操作: 

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

 注意:如果一个图中存在负权回路,那么可能不存在最短路

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 510;
const int M = 10010;
int n,m,k;
int dist[N],backup[N];
// 边,a表示出点,b表示入点,w表示边的权重
struct edge{
    int from;
    int to;
    int weight;
}edges[M];


void bellman_ford(){
    memset(dist,0x3f,sizeof dist);
    dist[1] =0;
// 如果第n次迭代仍然会松弛三角不等式(存在更新),就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for(int i = 0;i < k;i++){
// 备份,防止读后写
        memcpy(backup,dist,sizeof(dist));
        for(int j = 0;j < m; j++){
            int from = edges[j].from;
            int to  = edges[j].to;
            int weight = edges[j].weight;
            dist[to] = min(dist[to],backup[from] + weight);
        }
    }

}

int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 0;i < m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edges[i] = {a,b,c};
    }
    bellman_ford();
    if(dist[n] > 0x3f3f3f3f/2){
        puts("impossible");
    }else{
        printf("%d\n",dist[n]);
    }
    
    return 0;
}

除了可以用邻接矩阵和邻接表外,还可用三元组存储图
允许存在负权边,而Dijkstra算法不允许
外循环次数决定最小路径的最大边数
若第n次迭代有修改,根据容斥原理知道,一定存在负权环(整个环的权重和为负数)
实际应用:换乘不超过k次的最短路径(限制路径的边数)
backup用于保存上次迭代的结果,避免“写后读”。Dijkstra算法不存在这种情况
由于存在负权回路(注意不是负权边),因此负权回路有可能把自定义的无穷大0x7f7f7f7f变小,由于最多修改10000×10000=108,10000×10000=108,而0x7f7f7f7f>2×108>2×108,故0x7f7f7f7f / 2依旧是“无穷大”,故可用dist[n] > 0x7f7f7f7f / 2判断是否是无穷大
时间复杂度为O(mn)

2.普通队列优化的bellman-ford算法

#include<cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
//只存一个点,因为另一个点是邻接表的表头
struct Edge {
    int to, weight;
};

vector<Edge> adj[N];
int dist[N];
bool used[N];
int n, m,k;

void spfa(int source) {
    memset(dist, 0x3f, sizeof(dist));
    dist[source] = 0;
    queue<int> pq;
    pq.push(source);
    used[source] = true;
    while (!pq.empty()) {
        int u = pq.front();pq.pop();
        used[u] = false;
        for (auto &e : adj[u]) {
            int v = e.to;
            int w = e.weight;
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                if(!used[v]){
                    pq.push(v);
                    used[v] = true;
                }
                
            }
        }
    }
}

int main() {

    cin >> n >> m ;

    for (int i = 0; i < m; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        adj[x].push_back({y, z});
    }

    spfa(1);
    if(dist[n] > 0x3f3f3f3f/2){
        puts("impossible");
    }else{
        printf("%d\n",dist[n]);
    }

    return 0;
}

时间复杂度:

最好:o(m) 

最差:o(mn)

spfa相较于djikstra,如果题目中不进行针对性限制,一般是会比djikstra更快的

spfa求负环数量:

#include<cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
//只存一个点,因为另一个点是邻接表的表头
struct Edge {
    int to, weight;
};

vector<Edge> adj[N];
int dist[N],cnt[N];
bool used[N];
int n, m,k;
bool iscir = false;

void spfa(int source) {
    // memset(dist, 0x3f, sizeof(dist));
    queue<int> pq;
    for(int i =1;i <= n;i++){
        // dist[i] = 0;
        pq.push(i);
        used[i] = true;
    }

    
    while (!pq.empty()) {
        int u = pq.front();pq.pop();
        used[u] = false;
        for (auto &e : adj[u]) {
            int v = e.to;
            int w = e.weight;
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                //判断是否存在负权值
                cnt[v] = cnt[u] + 1;
                if(cnt[v] >= n) {
                    iscir = true;
                    return;
                }
                if(!used[v]){
                    pq.push(v);
                    used[v] = true;
                }
                
            }
        }
    }
}

int main() {

    cin >> n >> m ;

    for (int i = 0; i < m; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        adj[x].push_back({y, z});
    }

    spfa(1);
    if(iscir){
        printf("Yes");
    }else{
        printf("No");
    }

    return 0;
}

三、Floyd算法

const int INF = 1E9;
// 初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

最短距离需要把d[i][i] = 0;
时间复杂度为O(n3) 

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

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

相关文章

Mac air 个人免费版VMWare Fusion安装及配置教程

Mac air 安装免费版VMWare Fusion教程及问题解决 1、下载VMWare Fusion2、下载wins镜像文件3、开始配置4、出现的问题及解决方法4.1 如何跳过启动时的网络连接4.2 启动后&#xff0c;无法连接网络怎么办4.3 怎么实现将文件拖拽到虚拟机中 当你手上是一台Mac电脑&#xff0c;却需…

薪酬、人数上不封顶,这家互联网大厂正在疯抢超级毕业生

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 发布在https://it.weoknow.com 更多资源欢迎关注 又是一年一度校园春招季。在生成式 AI 一路狂飙的时代浪潮下&#xff0c;人工…

Linux: 进程地址空间究竟是什么?进程地址空间存在意义何在?

Linux: 进程地址空间究竟是什么&#xff1f; 一、内存究竟是什么&#xff1f;分为哪些&#xff1f;二、内存是真实物理空间&#xff1f;三、进程地址空间&#xff08;虚拟地址&#xff09;3.1 为何同一个变量地址相同&#xff0c;保存的数据却不同&#xff1f; 四、为什么需要地…

ssm012医院住院管理系统+vue

医院住院管理关系 摘 要 随着时代的发展&#xff0c;医疗设备愈来愈完善&#xff0c;医院也变成人们生活中必不可少的场所。如今&#xff0c;已经2021年了&#xff0c;虽然医院的数量和设备愈加完善&#xff0c;但是老龄人口也越来越多。在如此大的人口压力下&#xff0c;医院…

3.31总结

这两天对于java知识的学习又收获了一些新的东西&#xff0c;如内部类、抽象类、接口、权限修饰符、代码块、final. final final多用于方法、类、变量的修饰 方法&#xff1a;表示该方法是最终方法&#xff0c;不能被重写 类&#xff1a;表明该类是最终类&#xff0c;不能被…

latex伪代码一些记录

参考一 参考二 参考三 使用minipage 最终调整好的效果&#xff1a; $ \begin{document} \begin{center} \begin{minipage}{15.92cm} \renewcommand{\thealgorithm}{1} \begin{CJK}{GBK}{song} \begin{algorithm}[H]\caption{ \text{算法1&#xff1a;xxx}}\begin{algorith…

Linux 基础IO [缓冲区文件系统]

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 一.Linux下一切皆文件 二.缓冲…

调试技巧安全预编译头文件(C++基础)

调试 调试可以选择条件调试和操作调试&#xff1a; 条件调试来选择条件进入断点设置&#xff0c;操作调试来使达到断点条件后完成某些操作&#xff08;一般是output窗口输出&#xff09;。 在这里就只输出了小于6的条件。 安全 降低崩溃、内存泄露、非法访问等问题。 应该转…

vue基础教程(5)——构建项目级登录页

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、创建首页二、登录页代码讲解三、对应的vue知识点&#xff1a;四、附件-各文件代码总结 前言 前面我们已经把vue自带的页面删除&#xff0c;也搭建了最简单的router路由&#xff0c;下面就可以真正开发我们自己的项目…

蓝桥杯-python-常用库归纳

目录 日期和时间 datetime模块 date日期类&#xff0c;time时间类&#xff0c;datetime日期时间类 定义date&#xff08;年&#xff0c;月&#xff0c;日&#xff09; data之间的减法 定义时间&#xff08;时&#xff0c;分&#xff0c;秒&#xff09; 定义datetime&#xf…

文献学习-23-MRM:用于遗传学医学图像预训练的掩码关系建模

MRM: Masked Relation Modeling for Medical Image Pre-Training with Genetics Authors: Qiushi Yang, Wuyang Li, Baopu Li, Yixuan Yuan Source: ICCV 2023 Abstract: 关于自动多模态医疗诊断的 ODERN 深度学习技术依赖于大量的专家注释&#xff0c;这既耗时又令人望而却…

DeepL Pro3.1 下载地址及安装教程

DeepL Pro是DeepL公司推出的专业翻译服务。DeepL是一家专注于机器翻译和自然语言处理技术的公司&#xff0c;其翻译引擎被认为在质量和准确性方面表现优秀.DeepL Pro提供了一系列高级功能和服务&#xff0c;以满足专业用户的翻译需求。其中包括&#xff1a; 高质量翻译&#xf…

Python 常用内置库 time库、random库、turtle库

文章目录 一、time库二、random库三、turtle库1. 绘制正方形2. 使用海龟对象绘制六边形3. 绘制多个起点相同大小不同起点的五角星4. 绘制多个图形和添加文字 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、time库 time是最基础的时间处理库&#…

系统慢查询的思考

系统慢查询的思考 在一个系统中发现慢查询的功能或很卡的现象。你是怎么思考的&#xff1f;从哪几个方面去思考&#xff1f;会用什么工具&#xff1f; 一个系统使用了几年后都可能会出现这样的问题。原因可能有以下几点。 数据量的增加。系统中平时的使用中数据量是有一个累…

HTML块级元素和内联元素(头部和布局)

目录 1.HTML块级和内联标签&#xff1a; 1.块级元素&#xff1a; 2.内联元素: 3.元素嵌套&#xff1a; 4.元素转换&#xff1a; 示例如下: 2.内联框架&#xff1a; 前言&#xff1a; 示例如下: 3.布局&#xff1a; 4.头部标签&#xff1a; 前言&#xff1a; 说明&…

GT收发器PHY层设计(3)PHY层设计

文章目录 前言一、设计框图二、PHY层基本传输协议三、PHY_TX模块3.1、模块接口3.2、组帧状态机描述3.3、数据大小端问题3.4、字节对齐 四、PHY_RX模块4.1、模块接口4.2、大小端转换4.3、起始位4.4、结束位4.5、axis数据流恢复 五、LFSR伪随机码六、链路空闲时期处理 前言 上一…

windows平台虚拟机安装

windows平台虚拟机安装 1. 安装VMwareWorkstationPro 1.1 软件下载 官网下载 官网 百度网盘下载 版本 VMwareWorkstationPro16 链接&#xff1a;https://pan.baidu.com/s/1LidMxoM9e4a4CANixyRoyg?pwd1157 提取码&#xff1a;1157 1.2 软件安装 软件安装注意事项 软件…

类的新功能

类的新功能 默认成员函数 在C11之前&#xff0c;一个类中有如下六个默认成员函数&#xff1a; 构造函数。拷贝构造函数赋值重载析构函数取地址重载函数const取地址函数 其中前四个默认成员函数最重要&#xff0c;后面两个默认成员函数一般不会用到&#xff0c;这里默认成员…

Js之运算符与表达式

运算符&#xff1a;也叫操作符&#xff0c;是一种符号。通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果。 表达式&#xff1a;由数字、运算符、变量的组合&#xff08;组成的式子&#xff09;。 表达式最终都会有一个运算结果&#xff0c;我们将这个结果称…

从输入url到页面展示的过程

唠唠叨&#xff1a;我不想误人子弟&#xff0c;我这篇算是搬运工&#xff0c;加上自己的理解做点总结&#xff0c;所以还请大家科学上网去看这篇&#xff1a;https://aws.amazon.com/cn/blogs/mobile/what-happens-when-you-type-a-url-into-your-browser/ 是这六个步骤&#…