《啊哈算法图的遍历》(14张图解)

news2024/12/25 14:16:04

目录

前言 

一,dfs和bfs是什么

二,城市地图--图的深度优先遍历

三,最少转机--图的广度优先遍历


前言 

🌼说爱你(超甜女声版) - 逗仔 - 单曲 - 网易云音乐

1月22日一个女孩加了我,她和我聊音乐,聊文学,聊人生理想。2月2日,她跟我聊起了她在山里种茶叶命苦的爷爷👺

🌼南山南 - 马頔 - 单曲 - 网易云音乐

--------------------------------------------------------------------------------------------------------------- 

一,dfs和bfs是什么

前面我么已经学过深搜广搜,为什么叫深搜和广搜呢?这是针对图的遍历而言的,看图

是什么呢,图就是由一些小圆点(顶点)和连接这些小圆点的直线()组成的,比如上图由5个顶点(1,2,3,4,5)和5条边(1-2,1-3,1-4,2-4,3-5)组成 

 现在我们从1号顶点开始遍历这个图,遍历就是把图的每个顶点都访问一次,使用深搜遍历会得到下图结果(图中每个顶点上方的数字,表示该顶点第几个被访问到,叫时间戳

深搜遍历该图的具体过程是:

1,未走过的顶点作为起始,比如1号作为顶点

2,从1号尝试访问其他未走过的顶点

3,来到2号,再从2号作为出发点继续访问其他未走过的

4,来到4号,发现没有未访问过的,返回上一步(2号顶点)

5,2号-->1号-->3号-->5号

6,至此,所有顶点都访问过了,遍历结束 

深度优先的思想是,沿着图某一条分支遍历到末端,然后回溯,再沿着另一条进行同样的遍历,直到所有顶点都被访问过为止。那这一过程如何用代码实现呢?

在那之前,我们先解决如何存储一个图,我们用一个二维数组来存储,看图 

上图二维数组第 i 行第 j 列表示的是顶点 i 到顶点 j 是否有边。1 表示有边,∞ 表示没边,这里我们将自己到自己(i 到 j)设为0,此谓“图的邻接矩阵存储法

观察发现,这个二维数组沿主对角线对称,因为该图是无向图(图的边没有方向,如果 i 到 j 有边,那么 j 到 i 也有边),例如1-5表示1号到5号有边,5号到1号也有边

接下来写个dfs实现图的遍历 

void dfs(int cur) //current当前顶点编号
{
    printf("%d ", cur);
    sum++; //已访问顶点个数
    if(sum == n) return; //遍历完毕退出
    for(i = 1; i <= n; ++i) { //从1号顶点到n号依次尝试
        //如果有边且未访问
        if(e[cur][i] == 1) {
            book[i] = 1; //标记已访问
            dfs(i); //从顶点i出发继续遍历
        }
    }
    return;
}

上面代码中,变量cur(rent)存储当前顶点,二维数组e存储图的边邻接矩阵),赋值e[cur][i]为-1记录哪些顶点被访问过,变量sum记录已访问顶点个数,变量n存储图顶点的总个数

完整代码   dfs遍历图

#include<iostream>
using namespace std;
int n, sum = 0, book[101], e[110][110];
void dfs(int cur) //current当前顶点
{
    cout<<cur<<" ";
    sum++;
    if(sum == n) return; //遍历结束
    for(int i = 1; i <= n; ++i) {
        if(e[cur][i] == 1 && book[i] == 0) { //有边且未被访问
            book[i] = 1;
            dfs(i);
        }
    }
    return;
}
int main()
{
    int a, b; //可联通的两点
    cin>>n;
    for(int i = 1; i <= n; ++i) //初始化矩阵
        for(int j = 1; j <= n; ++j) {
            if(i == j) e[i][j]= 0; //自己到自己
            else e[i][j] = 99999999; //正无穷
        }
    for(int i = 1; i <= n; ++i) { //标记可联通
        cin>>a>>b;
        e[a][b] = 1;
        e[b][a] = 1; //无向图
    }
    book[1] = 1; //标记访问
    dfs(1); //从1号顶点开始遍历
    return 0;
}
5
1 2
1 3
1 5
2 4
3 5
1 2 4 3 5

使用广搜遍历这个图的结果是,看图

1,先以一个未访问顶点作为起始,比如1号,将1号放入队列

2,接着将与1号相邻的,未访问的顶点2号,3号,5号依次放入队列(如下图)

3,再将与2号相邻且未访问的4号放入队列,至此访问完毕,遍历结束(如下图)

广度优先遍历的思想是

1,未访问顶点作为起始,访问所有相邻

2,接着对每个相邻顶点,再访问它们相邻未访问顶点,直至所有顶点都被访问,遍历结束

3,通俗点讲,就是访问起始顶点一步以内,两步以内直至n步以内的顶点

完整代码    bfs遍历图

#include<iostream>
using namespace std;
int main()
{
    int n, a, b, cur, book[101] = {0}, e[101][101];
    int que[10010], head, tail;
    cin>>n;
    //初始化二维矩阵
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j) {
            if(i == j) e[i][j] = 0; //自己到自己
            else e[i][j] = 99999999; //正无穷
        }
    //读入边(可联通)
    for(int i = 1; i <= n; ++i) {
        cin>>a>>b;
        e[a][b] = 1;
        e[b][a] = 1; //无向图,可逆
    }
    //队列初始化
    head = 1; tail = 1;
    //1号顶点出发,将1号加入队列
    que[tail] = 1;
    tail++;
    book[1] = 1; //已访问
    //当队列不为空
    while(head < tail && tail <= n) {
        cur = que[head]; //当前顶点
        for(int i = 1; i <= n; ++i) { //从1~n依次尝试
            //有边且未访问过
            if(e[cur][i] == 1 && book[i] == 0) {
                //将顶点i入队
                que[tail] = i;
                tail++;
                book[i] = 1; //标记访问
            }
            if(tail > n) break;
        }
        head++; //一个顶点扩展结束后,head++才能继续扩展
    }
    for(int i = 1; i <= n; ++i) cout<<que[i]<<" ";
    return 0;
}
5
1 2
1 3
1 5
2 4
3 5
1 2 3 5 4

使用深搜和广搜遍历图,都会得到这个图的生成树,那么什么叫生成树?生成树又有哪些作用呢?我们将在《啊哈算法》最后的博客讨论 

下面来看有什么作用,能解决什么实际问题,请看下节,城市地图↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

二,城市地图--图的深度优先遍历

寒假我(小哼)想去女朋友(小哈)家里玩,我们异地恋,住在不同城市,怎么去呢?

 这时小哼想起了百度地图,百度地图一下子给出了小哼家到小哈家的最短行车方案,爱思考的小哼想知道百度地图是如何计算最短行车方案

城市地图 

数据是这样给出的:

5 8
1 2 2
1 5 10
2 3 3 
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3

第一行5表示5个城市(城市编号1~5),8表示8条公路

接下来8行,每行类似"a b c",表示有一条路可以从城市a到城市b,路程c公里 

需要注意的是,这里的公路都是单行的,即有向图 

小哼家在1号城市,女朋友家在5号城市,现在请算出1号城市到5号城市的最短路径(程) 

为了去女朋友家玩,一定要找到最短路程!🙃 

已知有5个城市,8条公路,我们用一个5 * 5矩阵(二维数组a)存储这些信息,看图: 

这个二维数组表示城市 i 到城市 j 之间的路程,比如a[1][2]的值为2表示1号到2号路程为2公里

表示无法到达,约定自己到自己的距离为0

具体实现一条1号到5号的路,再返回并逐个尝试的过程,就不讲了,我们直接开始dfs 

用全局变量Min更新每次找到路径的最小值,用book[]数组记录走过的城市,用1000000000表示

代码 

#include<cstdio> //scanf()
int Min = 99999999, book[101], a[101][101];
int n; //n个城市作为全局变量,在dfs函数中用到
void dfs(int i, int sum)
{
    int j;
    if(sum > Min) return; //当前路程大于当前最短路
    if(i == n) { //到达目标城市
        if(sum < Min) Min = sum; //更新
        return;
    }
    for(j = 1; j <= n; ++j) { //从1号到n号城市依次尝试
        //i到j有路且j未走过
        if(a[i][j] != 99999999 && book[j] == 0) {
            book[j] = 1; //标记
            dfs(j, sum + a[i][j]); //从j城市再出发
            book[j] = 0; //取消标记
        }
    }
    return;
}
int main()
{
    int i, j, m, b, c, d; //n个城市, m条公路
    scanf("%d%d", &n, &m);
    //初始化二维数组
    for(i = 1; i <= n; ++i) //不要漏=
        for(j = 1; j <= n; ++j) { //不要漏=
            if(i == j) a[i][j] = 0;
            else a[i][j] = 99999999;
        }
    //读入城市间道路
    for(i = 1; i <= m; ++i) {
        scanf("%d%d%d", &b, &c, &d);
        a[b][c] = d;
    }
    //从1号城市出发
    book[1] = 1;
    dfs(1, 0);
    printf("%d", Min);
    return 0;
}
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
9

现在总结一下图的概念,图就是由N个顶点M条边组成的集合,题目中城市地图就是一个图(有向图),图中每个城市就是一个顶点,而两个城市之间的公路则是顶点的边

我们知道图分为有向图无向图,如果给图的每条边规定一个方向,会得到有向图,其边称为有向边。在有向图中,与一个点相关联的边有出边和入边之分,而与一个有向边相关联的两个点也有始点和终点之分

边没有方向的图称为无向图,我们将上面城市地图改为无向图后: 

处理无向图和有向图基本一样,除了...."b c d"表示城市b和c可以互相到达,路程均为d公里,所以我们需要将a[1][2]和a[2][1]都初始化为2,因为这条公路是双行道

初始化后的数组a如下图:

这个表是对称的(无向图的特征),我们发现此时从1号城市到5号城市的最短路径不再是1->2->5

而是1->3->5,路径长度为7

我们只需要在代码第35行(读入城市道路)加个 a[c][b] = d; 即可

#include<cstdio> //scanf()
int Min = 99999999, book[101], a[101][101];
int n; //n个城市作为全局变量,在dfs函数中用到
void dfs(int i, int sum)
{
    int j;
    if(sum > Min) return; //当前路程大于当前最短路
    if(i == n) { //到达目标城市
        if(sum < Min) Min = sum; //更新
        return;
    }
    for(j = 1; j <= n; ++j) { //从1号到n号城市依次尝试
        //i到j有路且j未走过
        if(a[i][j] != 99999999 && book[j] == 0) {
            book[j] = 1; //标记
            dfs(j, sum + a[i][j]); //从j城市再出发
            book[j] = 0; //取消标记
        }
    }
    return;
}
int main()
{
    int i, j, m, b, c, d; //n个城市, m条公路
    scanf("%d%d", &n, &m);
    //初始化二维数组
    for(i = 1; i <= n; ++i) //不要漏=
        for(j = 1; j <= n; ++j) { //不要漏=
            if(i == j) a[i][j] = 0;
            else a[i][j] = 99999999;
        }
    //读入城市间道路
    for(i = 1; i <= m; ++i) {
        scanf("%d%d%d", &b, &c, &d);
        a[b][c] = d; a[c][b] = d;
    }
    //从1号城市出发
    book[1] = 1;
    dfs(1, 0);
    printf("%d", Min);
    return 0;
}
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
7

本节我们采用二维数组存储这个图(顶点和边的关系),这种方法叫"邻接矩阵法"

存储图的方法还有"邻接表法".

求图上两点最短路径的方法,除了dfs, 还有bfs, Floyd, Bellman-Ford, Dijkstra等,我们将在下一章讲述

三,最少转机--图的广度优先遍历

小哼和女朋友小哈一起坐飞机去旅游,他们现在位于1号城市,目标是5号城市,可是1号城市没有到5号城市的直航,现在小哼已经收集了很多航班的信息,怎样找到一种乘坐方式,使转机次数最少呢?

5 7 1 5
1 2
1 3
2 3
2 4
3 4
3 5
4 5

第一行表示5个城市, 7条航线, 起点为1号城市, 目标为5号城市

接下来7行,每行"a b"表示城市a和b之间有航线,可以互相抵达(无向图)

要求转机次数最少,我们假设所有边长度都为1,接下来用bfs解决最少转机问题

步骤

        一步之内 

先将1号城市入队,一步之内可以到达2号或3号城市,将2号,3号入队

        两步之内 

2号城市可以扩展出3号和4号城市,因为3号已在队列中,只需将4号入队

3号城市可以扩展出4号和5号城市,因为4号已在队列中,只需将5号入队

由于head(队首),是在每次对一个点完整遍历完一遍才会++,所以会出现多个点转机次数相同的情况,代表着距离出发点或n步以内 

完整代码

5 7 1 5
1 2
1 3
2 3
2 4
3 4
3 5
4 5
2

为什么最少转机次数不用dfs呢,因为广度优先搜索更适用于所有边权值相同的情况,会更快 

总结

城市地图(所有边权值不同)用深搜

最少转机次数(所有边权值相同)用广搜

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

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

相关文章

adb常用指令合集

adb文件管理指令 1.复制设备里的文件到电脑 adb pull <设备里的文件路径> [电脑上的目录] 电脑上的目录 参数可以省略&#xff0c;默认复制到当前目录 例&#xff1a;adb pull /data/tsplogtool /home/jxq/文档/场景魔方 2.复制电脑里的文件到设备 adb push <电脑上的…

浅谈未来10年IT行业的变局与抉择,一文带你认识元宇宙

一. 困局据国家就业部门最新统计数据报告&#xff0c;2022年应届毕业生的数量首次突破1000万大关。其中研究生达到130万&#xff0c;985、211等名校毕业生75万&#xff0c;普通本科毕业生470万&#xff0c;专科生460万&#xff0c;另外还有几十万的归国留学生&#xff01;但这还…

《从0开始学大数据》之Spark性能优化案例

基于软件性能优化原则和 Spark 的特点&#xff0c;Spark 性能优化可以分解为下面几步。 性能测试&#xff0c;观察 Spark 性能特性和资源&#xff08;CPU、Memory、Disk、Net&#xff09;利用情况。分析、寻找资源瓶颈。分析系统架构、代码&#xff0c;发现资源利用关键所在&a…

【前端】Vue项目:旅游App-(17)home:页面滚动显示搜索栏、节流、时间同步

文章目录目标过程与代码页面滚动到目标位置显示搜索框优化&#xff1a;节流搜索栏显示时间同步效果总代码修改或添加的文件search-bar.vueuseScroll.jsstore的main.jsformatDate.jshome.vue参考本项目博客总结&#xff1a;【前端】Vue项目&#xff1a;旅游App-博客总结 目标 …

HDFS文件浏览器功能OOM排查

现象描述 涉及HDFS文件浏览器的某个功能运行一段时间后会出现OOM的情况 错误日志如下&#xff1a; service.log.2023-02-01-0.log:java.lang.OutOfMemoryError: Java heap space排查过程 需要查看dump文件排查一下造成OOM的原因 查看jvm参数如下&#xff1a; java -Duser.t…

一文讲明Docker的基本使用,常见Docker命令使用 、Docker的安装使用等【详细说明+图解+概念+实践】

一个混迹于Github、Stack Overflow、开源中国、CSDN、博客园、稀土掘金、51CTO等 的野生程序员。 目标&#xff1a;分享更多的知识&#xff0c;充实自己&#xff0c;帮助他人 GitHub公共仓库&#xff1a;https://github.com/zhengyuzh 以github为主&#xff1a; 1、分享前端后端…

【Python合集系列】2023兔年吉祥,新的一年希望放烟花的人跟看烟花的人都能平平安安哦~(附多种源码)

前言 希望放烟花的人跟看烟花的人都能平平安安。 &#x1f440; NICE TO MEET YOU :)&#x1f319; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 ​哈喽&#xff01;我是木子&#xff0c;新…

设计模式之适配器模式,以C++为例。

今天来盘一盘适配器模式。适配器&#xff1a;顾名思义&#xff0c;就是让原本不合适的变为合适的&#xff0c;好似一对男女&#xff0c;没有中间的媒婆是不会互相了解的&#xff0c;好像不太恰当&#xff0c;就这么解释吧&#xff0c;只有有了这个中间人他们才会产生联系&#…

智能驾驶开启高精定位新赛道,这家供应商正加码布局海外市场

高工智能汽车研究院监测数据显示&#xff0c;2022年1-11月中国市场乘用车前装标配搭载NOA交付达到18.38万辆&#xff0c;同比增长91.86%&#xff1b;同时&#xff0c;NOA搭载的车型配置价格还在不断下滑&#xff0c;正在把NOA的配置拉至15万元价格区间。 而作为高精定位&#x…

面向对象——static(静态)Math类自定义工具类代码块

目录 static&#xff08;静态&#xff09;关键字 static的注意事项 static的优点和缺点 应用场景 自定义工具类 代码块 static&#xff08;静态&#xff09;关键字 static是一个修饰符&#xff0c;用于修饰成员&#xff08;成员变量 、成员方法&#xff09;static的特点…

Redis处理client连接数过多,大量空闲链接无法释放问题

打开redis命令终端&#xff0c;输入&#xff1a; client list 查看连接数&#xff0c;用于返回所有连接到服务器的客户端信息和统计数据 参数解析&#xff1a; id: 唯一的64位的客户端ID(Redis 2.8.12加入)。 addr: 客户端的地址和端口 fd: 套接字所使用的文件描述符 age…

python真的很骚可惜你不会

python基本语法 &#x1f4d2;博客主页&#xff1a; 微笑的段嘉许博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐留言&#x1f4dd; &#x1f4cc;本文由微笑的段嘉许原创&#xff01; &#x1f4c6;51CTO首发时间&#xff1a;&#x1f334;2023年1月日3…

redis分布式缓存

文章目录一、redis持久化1.1.RDB持久化1.1.1.执行时机1.1.2.RDB原理1.1.3.小结1.2.AOF持久化1.2.1.AOF原理1.2.2.AOF配置1.2.3.AOF文件重写1.2.4.小结1.3.RDB与AOF对比二、Redis主从集群2.1.集群结构2.2.准备实例和配置2.3.启动2.4.开启主从关系2.5.测试2.6.主从数据同步原理2.…

Codeforces Round #848 (Div. 2) A-E 赛时思路+正解

青大蒟蒻第一次在正式的div2div2div2中AcAcAc了五道题&#xff0c;也是小蒟蒻有史以来发挥最好的一场&#xff0c;这场过后我的cf也许可能也要变成黄了。 A. Flip Flop Sum 题意&#xff1a;给定一个a——ia——ia——i数组&#xff0c;权值仅为1或-1&#xff0c;我们选择相邻…

《死亡空间》重制回归!无法启动怎么办?

作为科幻生存恐怖系列的经典之作&#xff0c;《死亡空间》在推出15年后再次回归&#xff0c;果然引发热潮。精美震撼的科幻场景&#xff0c;强烈的视觉画面&#xff0c;加上阴森的3D 音效&#xff0c;重制版提升了身临其境之感&#xff0c;完全是沉浸式恐怖体验&#xff0c;只能…

红外遥控数码管显示

红外遥控器实物图红外遥控器接口电路数码管接口电路红外遥控数码管显示程序源代码/**************************红外遥控数码管显示************************** * 单片机&#xff1a;51单片机* 开发环境&#xff1a;keil * 名称:红外遥控数码管显示 * 功能&#xff1a;遥控器红外…

C语言常量

常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。常量可以是任何的基本数据类型&#xff0c;比如整数常量、浮点常量、字符常量&#xff0c;或字符串字面值&#xff0c;也有枚举常量。常量就像是常规的变量&#xff0c;只不过常量的值…

OpenMMLAB AI实战营第一课笔记

计算机视觉的发展 计算机视觉是什么 计算机视觉是一门让计算机学会"看"的学科&#xff0c;研究如何自动理解图像和视频中的内容 计算机视觉的发展 早期萌芽&#xff08;1960-1980&#xff09; 统计机器学习与模式识别(1990-2000) ImageNet 大型数据库(2006) 斯坦…

ocelot+consul治理服务

consulConsul 是HashiCorp公司推出的开源工具&#xff0c;用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案相比&#xff0c;Consul的方案更“一站式”&#xff0c;内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方…

程序员副业接单做私活避坑指南

这篇文章系统的分享了对接单做私活这件事情的思考&#xff0c;也给出一些干货建议。希望让大家少走一些弯路&#xff0c;不要被坑。 先说结论 不建议大家在接单这个事情上投入太大精力&#xff0c;如果你“贼心不改”&#xff0c;建议大家以比较随缘的方式对待这件事情。 再说…