算法学习(5)-图的遍历

news2024/11/26 23:49:08

目录

什么是深度和广度优先

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

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


什么是深度和广度优先

         使用深度优先搜索来遍历这个图的过程具体是:

  1. 首先从一个未走到过的顶点作为起始顶点, 比如以1号顶点作为起点。
  2. 沿1号顶点的边去尝试访问其它未走到过的顶点, 首先发现2 号顶点还没有走到过, 于是来到了2 号顶点。
  3. 再以2 号顶点作为出发点继续尝试访问其它未走到过的顶点, 这样又来到了4号顶点。
  4. 再以4 号顶点作为出发点继续尝试访问其它未走到过的顶点。
  5. 但是, 此时沿4号顶点的边, 已经不能访问到其它未走到过的顶点了, 所以要返回到2号顶点。
  6. 返回到2号顶点后, 发现沿2 号顶点的边也不能再访问到其它未走到过的顶点。因此还需要继续返回到1号顶点。
  7. 再继续沿1号顶点的边看看还能否访问到其它未走到过的顶点。
  8. 此时又会来到3号顶点, 再以3号顶点作为出发点继续访问其它未走到过的顶点, 千是又来到5号顶点。
  9. 到此, 所有顶点都走到过了, 遍历结束。

        深度优先遍历的主要思想就是:首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;没有未访问过的顶点时, 则回到上一个顶点, 继续试探访问别的顶点, 直到所有的顶点都被访问过。

        显然, 深度优先遍历是沿着图的某一条分支遍历直到末端, 然后回溯, 再沿着另一条进行同样的遍历, 直到所有的顶点都被访问过为止。那这一过程如何用代码来实现呢?在讲代码实现之前我们先来解决如何存储一个图的问题。最常用的方法是使用一个二维数组e来存储, 如下。

 

        上图二维数组中第i行第j列表示的就是顶点 i 到顶点 j 是否有边。1表示有边, ∞表示
没有边, 这里我们将自己到自己(即i等于j)设为0。我们将这种存储图的方法称为图的邻
接矩阵存储法。
        注意观察会发现这个二维数组是沿主对角线对称的, 因为上面这个图是无向图。所谓无向阳指的就是图的边没有方向, 例如边1-5表示, 1号顶点可以到5号顶点, 5号顶点也可以到1号顶点。
        接下来要解决的问题就是如何用深度优先搜索来实现遍历了。 

void dfs(int cur) { // cur是当前所在的顶点编号
    printf("%d. ", cur);
    sum++; // 每访问一个顶点s就加1
    if (sum == n) return; // 所有的顶点都已经访问过则直接退出
    for (i = 1; i <= n; i++) { // 从1号顶点到n号顶点依次尝试,看哪些顶点与当前顶点cur有边相连
        // 判断当前顶点cur到顶点i是否有边,并判断顶点i是否已访问过
        if (e[cur][i] == 1 && book[i] == 0) {
            // 标记顶点i已经访问过
            book[i] = 1;
            // 从顶点i再出发继续遍历
            dfs(i);
        }
    }
    return;
}

        在上面的代码中变揽cur存储的是当前正在遍历的顶点, 二维数组e存储的就是图的边(邻接矩阵), 数组book用来记录哪些顶点已经访问过, 变揽sum用来记录已经访问过多少个顶点, 变证n存储的是图的顶点的总个数。完整代码如下。

#include <stdio.h>

int book[101], sum, n, e[101][101];

void dfs(int cur) { // cur是当前所在的顶点编号
    int i;
    printf("%d ", cur);
    sum++; // 每访问一个顶点,sum就加1
    if (sum == n) return; // 所有的顶点都已经访问过则直接退出
    for (i = 1; i <= n; i++) { // 从1号顶点到n号顶点依次尝试,看哪些顶点与当前顶点cur有边相连
        // 判断当前顶点cur到顶点i是否有边, 并判断顶点i是否已访问过
        if (e[cur][i] == 1 && book[i] == 0) {
            // 标记顶点i已经访问过
            book[i] = 1;
            // 从顶点i再出发继续遍历
            dfs(i);
        }
    }
    return;
}

int main() {
    int i, j, m, a, b;
    scanf("%d %d", &n, &m);
    // 初始化二维矩阵
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
            if (i == j)
                e[i][j] = 0;
            else
                e[i][j] = 99999999; // 我们这里假设999999999为正无穷
        }
    }
    // 读入顶点之间的边
    for (i = 1; i <= m; i++) {
        scanf("%d %d", &a, &b);
        e[a][b] = 1;
        e[b][a] = 1; // 这里是无向图,所以需要将e[b][a]也赋为1
    }
    // 从1号城市出发
    book[1] = 1; // 标记1号顶点已访问
    dfs(1); // 从1号顶点开始遍历
    getchar();
    getchar();
    return 0;
}

        广度优先遍历的主要思想就是:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点, 然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过, 遍历结束。

        代码实现如下:

#include <stdio.h>

int main() {
    int i, j, n, m, a, b, cur;
    int book[101] = {0}; // 使用数组初始化语法将book数组初始化为全0
    int e[101][101];
    int que[10001], head = 0, tail = 0; // 将队列初始化为0

    scanf("%d %d", &n, &m);

    // 初始化二维矩阵
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
            if (i == j) {
                e[i][j] = 0;
            }
            else {
                e[i][j] = 99999999; // 假设999999999为正无穷
            }
        }
    }

    // 读入顶点之间的边
    for (i = 1; i <= m; i++) {
        scanf("%d %d", &a, &b);
        e[a][b] = 1;
        e[b][a] = 1; // 无向图,需要双向赋值
    }

    // 从1号顶点出发,将1号顶点加入队列
    que[tail] = 1;
    tail++;
    book[1] = 1; // 标记1号顶点已访问

    // 当队列不为空时循环
    while (head < tail) {
        cur = que[head]; // 当前正在访问的顶点编号
        // 从1~n依次尝试
        for (i = 1; i <= n; i++) {
            // 判断从顶点cur到顶点i是否有边,且顶点i是否已经访问过
            if (e[cur][i] == 1 && book[i] == 0) {
                // 如果从顶点cur到顶点i有边,并且顶点i没有被访问过,则将顶点i入队
                que[tail] = i;
                tail++;
                book[i] = 1; // 标记顶点i已访问
            }
        }
        // 如果tail大于n,则所有顶点都已经被访问过,退出循环
        if (tail > n) {
            break;
        }
        head++; // 顶点扩展结束后,head++,继续往下扩展
    }

    // 输出队列中的顶点编号
    for (i = 0; i < tail; i++) {
        printf("%d ", que[i]);
    }
    getchar();
    getchar();
    return 0;
}

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

         我们可以创建一个5*5的邻接矩阵,如下图:

        用book数组记录哪些城市已经走过,用全局变量min来更新每次找到的路径的最小值,代码实现如下:

#include <stdio.h>
int min = 99999999, book[101], n, e[101][101]; // 我们这里假设999999999为正无穷

// cur是当前所在的城市编号,dis是当前已经走过的路程
void dfs(int cur, int dis) {
    int j;
    // 如果当前走过的路程已经大于之前找到的最短路,则没有必要再往下尝试了,立即返回
    if (dis > min)
        return;
    if (cur == n)  // 判断是否到达了目标城市
    {
        if (dis < min) {
            min = dis;  // 更新最小值
            return;
        }
    }
    for (j = 1; j <= n; j++) { // 从1号城市到n号城市依次尝试
        // 判断当前城市cur到城市j是否有路,并判断城市j是否在已走过的路径中
        if (e[cur][j] != 99999999 && book[j] == 0) {
            book[j] = 1;  // 标记城市j已经在路径中
            dfs(j, dis + e[cur][j]);  // 从城市j再出发,继续寻找目标城市
            book[j] = 0;  // 之前一步探索完毕之后,取消对城市j的标记
        }
    }
}

int main() {
    int i, j, m, a, b, c;
    scanf("%d %d", &n, &m);

    // 初始化二维矩阵
    for (i = 1; i <= n; i++)
        for (j = 1; j <= n; j++)
            if (i == j)
                e[i][j] = 0;
            else
                e[i][j] = 99999999;

    // 读入城市之间的道路
    for (i = 1; i <= m; i++) {
        scanf("%d %d %d", &a, &b, &c);
        e[a][b] = c;
    }

    // 从1号城市出发
    book[1] = 1;  // 标记1号城市已经在路径中
    dfs(1, 0);  // 1表示当前所在的城市编号,0表示当前已经走过的路程

    printf("%d\n", min);  // 打印1号城市到目标城市的最短路径
    getchar();
    getchar();
    return 0;
}


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

 

#include <stdio.h>

struct note {
    int x;  // 城市编号
    int s;  // 转机次数
};

int main() {
    struct note que[2501];
    // 初始化
    int e[51][51] = {0}, book[51] = {0};
    int head, tail;
    int i, j, n, m, a, b, cur, start, end, flag = 0;
    scanf("%d %d %d %d", &n, &m, &start, &end);

    // 初始化二维矩阵
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
            if (i == j)
                e[i][j] = 0;
            else
                e[i][j] = 99999999;
        }
    }

    // 读入城市之间的航班
    for (i = 1; i <= m; i++) {
        scanf("%d %d", &a, &b);
        // 注:这里是无向图
        e[a][b] = 1;
        e[b][a] = 1;
    }

    // 队列初始化
    head = 1;
    tail = 1;
    // 从start号城市出发,将start号城市加入队列
    que[tail].x = start;
    que[tail].s = 0;
    tail++;
    book[start] = 1;  // 标记start号城市已在队列中

    // 当队列不为空的时候循环
    while (head < tail) {
        cur = que[head].x;  // 当前队列中首城市的编号
        for (j = 1; j <= n; j++) {  // 从1~n依次尝试
            // 从城市cur到城市j是否有航班并且判断城市j是否已经在队列中
            if (e[cur][j] != 99999999 && book[j] == 0) {
                // 如果从城市cur到城市j有航班并且城市j不在队列中,则将城市j入队
                que[tail].x = j;
                que[tail].s = que[head].s + 1;  // 转机次数+1
                tail++;
                // 标记城市j已经在队列中
                book[j] = 1;
                // 如果到达目标城市,停止扩展,任务结束,退出循环
                if (que[tail].x == end) {
                    // 注意下面两句话的位置千万不要写颠倒了
                    flag = 1;
                    break;
                }
            }
        }
        if (flag == 1)
            break;
        head++;  // 注意这地方,千万不要忘记当一个点扩展结束后,head++才能继续扩展
    }

    // 打印队列中末尾最后一个(目标城市)的转机次数
    // 注意:tail是指向队列队尾(即最后一位)的下一个位置,所以这里要-1
    printf("%d\n", que[tail - 1].s);
    getchar();
    getchar();
    return 0;
}

         当然也可以使用深度优先搜索解决, 但是这里用广度优先搜索会更快。广度优先搜索更
加适用于所有边的权值相同的情况。

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

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

相关文章

用Jenkins实现cherry-pick多个未入库的gerrit编译Android固件

背景: 在做Android固件开发的时候,通常我们可以利用gerrit-trigger插件,开发者提交一笔的时候自动触发jenkins编译,如果提交的这一笔的编译依赖其他gerrit才能编译过,我们可以在commit message中加入特殊字段,让jenkins在编译此笔patch的时候同时抓取依赖的gerrit代码下…

selenium在Pycharm中结合python的基本使用、交互、无界面访问

下载 下载与浏览器匹配的浏览器驱动文件&#xff0c;这里一定注意的是&#xff0c;要选择和浏览器版本号相同的驱动程序&#xff0c;否则后面会有很多问题。 &#xff08;1&#xff09;浏览器&#xff08;以google为例&#xff09;版本号的查询&#xff1a; 我这里的版本号是1…

RT-Thread V5.2.0版本尝鲜

文章目录 配置界面的更新新旧内核生成的二进制文件大小差异旧版本V5.1.0内核旧版本V5.2.0内核 配置界面的更新 尝试将手头RT-Thread工程的OS部分源码进行了更新&#xff0c;发现不少新的变化 配置界面变得更醒目&#xff1a; 配置变更保存提醒界面更新&#xff1a; 新旧内核…

Android Widget开发代码示例详细说明

因为AppWidgetProvider扩展自BroadcastReceiver, 所以你不能保证回调函数完成调用后&#xff0c;AppWidgetProvider还在继续运行。 a. AppWidgetProvider 的实现 /*** Copyright(C):教育电子有限公司 * Project Name: NineSync* Filename: SynWidgetProvider.java * Author(S…

【Node.js工程师养成计划】之原生node开发web服务器

一、使用node创建http服务器 var http require(http);// 获取到服务器实例对象 var server http.createServer() server.listen(8080, function() {console.log(http://127.0.0.1:8080); })server.on(request, function(req, res){console.log(request);res.write(6666666688…

VitePress 构建的博客如何部署到 github 平台?

VitePress 构建的博客如何部署到 github 平台&#xff1f; 1. 新建 github 项目 2. 构建 VitePress 项目 2.1. 设置 config 中的 base 由于我们的项目名称为 vite-press-demo&#xff0c;所以我们把 base 设置为 /vite-press-demo/&#xff0c;需注意前后 / export default…

【数据结构与算法(C语言)】1. 线性表的顺序存储

文章目录 前言一. 线性表插入和删除1. 元素的插入2. 元素的删除 二. 代码三. 优缺点 前言 线性表的顺序存储结构&#xff0c;指的是用一段地址连续的存储单元依次存储线性表的数据结构 一. 线性表插入和删除 1. 元素的插入 插入位置之后的数据都向后移一位&#xff0c;上图中元…

chrome 安装devtools

chrome 安装devtools 下载安装 链接&#xff1a;https://github.com/vuejs/devtools 选择对应版本&#xff1a; 安装yarn 下载 npm install -g yarn --registryhttps://registry.npmmirror.com进入下载的目录安装依赖 yarn install --registryhttps://registry.npmmirror.…

ASP.NET汽车销售管理系统的设计与开发

摘 要 随着人们生活水平的不断提高&#xff0c;人们对汽车的消费和需求也越来越旺盛。很多汽车销售公司的业务环节仍然运用人工记账的传统方法&#xff0c;既容易出错又会导致账目混乱&#xff0c;查询和统计起来也非常不方便&#xff0c;费时又费力&#xff0c;严重时会给公…

stm32单片机开发二、定时器-内部时钟中断和外部时钟中断、编码器

定时器本质就是一个计数器 案例&#xff1a;定时器定时中断 内部时钟中断 Timer_Init(); //定时中断初始化 /*** 函 数&#xff1a;定时中断初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void Timer_Init(void) {/*开启时钟*/RCC_APB1PeriphClockCmd(RCC…

CSS-复合选择器

作用&#xff1a; 后代选择器&#xff1a; 子代选择器 并集选择器 用逗号隔开&#xff0c;在style里面写的时候&#xff0c;每一个标签空一行。 <title>Document</title><style>p,div,span{color: aqua;}</style> </head> <body><p>…

在Linux操作系统中关于磁盘(硬盘)管理的操作

电脑中数据存储设备&#xff1a;硬盘&#xff08;实现数据的持久化存储&#xff09;&#xff0c;内存 在Linux操作系统中一切皆文件的思想&#xff0c;所有的设备在Linux操作系统中都是通过文件来标识的&#xff0c;所以每一个硬盘都对应一个块设备文件。 在Linux操作系统中所…

Golang | Leetcode Golang题解之第55题跳跃游戏

题目&#xff1a; 题解&#xff1a; // 贪心算法 func canJump(nums []int) bool {cover : 0n : len(nums)-1for i : 0; i < cover; i { // 每次与覆盖值比较cover max(inums[i], cover) //每走一步都将 cover 更新为最大值if cover > n {return true}}return false } …

YOLov5 + Gradio搭建简单的Web GUI

写在前面&#xff1a;当我们将模型训练出来了&#xff0c;此时就需要做UI界面给别人展示了。python提供的Gradio可以快速的搭建web页面。生成本地网址和公网网址&#xff0c;方面自己测试和用户测试。 一、安装 Gradio介绍 Gradio是一个开源的python库&#xff0c;用于构建机…

深度学习模型Deep Learning Model

什么是深度学习&#xff1f;&#xff1f; 深度学习模型是一种基于人工神经网络&#xff08;Artificial Neural Networks, ANN&#xff09;的机器学习模型&#xff0c;其核心思想是通过多层次的神经网络结构来学习数据的特征表示和模式。这些模型通常由多个层次&#xff08;深度…

WPS的JS宏如何设置Word文档的表格的单元格文字重新编号

希望对Word文档中的表格进行统一处理&#xff0c;表格内的编号&#xff0c;有时候会出现紊乱&#xff0c;下一个表格的编号承接了上一个表格的编号&#xff0c;实际需要重新编号。 当表格比较多时&#xff0c;手动更改非常麻烦&#xff0c;而且更改一遍并不能完成&#xff0c;…

测算sample gpt

测算代码 import pandas as pd import matplotlib.pyplot as pltlosspd.read_pickle("loss_8.pkl") plt.plot(loss) losspd.read_pickle("loss_16.pkl") plt.plot(loss) losspd.read_pickle("loss_4_8.pkl") plt.plot(loss) losspd.read_pickle(…

第5篇:创建Nios II工程之Hello_World<四>

Q&#xff1a;最后我们在DE2-115开发板上演示运行Hello_World程序。 A&#xff1a;先烧录编译Quartus硬件工程时生成的.sof文件&#xff0c;在FPGA上成功配置Nios II系统&#xff1b;然后在Nios II Eclipse窗口右键点击工程名hello_world&#xff0c;选择Run As-->Nios II …

低代码+定制物资管理:创新解决方案探析

引言 在当今快速变化的商业环境中&#xff0c;企业面临着不断增长的挑战&#xff0c;如提高效率、降低成本、满足客户需求等。为了应对这些挑战&#xff0c;企业需要不断创新并采用先进的技术解决方案。在这样的背景下&#xff0c;低代码开发和定制化物资管理成为了引领企业变…

03.Kafka 基本使用

Kafka 提供了一系列脚本用于命令行来操作 kafka。 1 Topic 操作 1.1 创建 Topic 创建一个名为 oldersix-topic 的 topic&#xff0c;副本数设置为3&#xff0c;分区数设置为2&#xff1a; bin/kafka-topics.sh \ --create \ --zookeeper 192.168.31.162:2181 \ --replication…