6.5 图的遍历

news2024/12/23 13:48:30

 

 前言:

主要内容:

这段文字描述的是图的遍历。图的遍历与树的遍历有些相似,都是从某个点开始,按照一定的规则遍历其他的点,但是图的遍历更加复杂,因为图中存在循环和回路,这意味着可能会多次访问到同一顶点。

### 主要内容:
1. **图的遍历**
   - 图的遍历是从图中的某一顶点出发,按照某种方法,对图中的所有顶点进行访问,每个顶点都只访问一次。
   - 图的遍历是图算法的基础,可以用于解决连通性问题、拓扑排序、寻找关键路径等。

2. **复杂性**
   - 图的遍历比树的遍历要复杂,因为图中的任一顶点都可能与其它顶点相邻,可能会存在回路,导致多次访问同一顶点。

3. **避免重复访问**
   - 为了避免同一顶点被访问多次,使用一个辅助数组 `visited` 来记录每个已经访问过的顶点。
   - 当顶点被访问时,将相应的 `visited` 数组的值设为 `true`。

4. **遍历方法**
   - 图的遍历主要有两种方法:深度优先搜索(DFS)和广度优先搜索(BFS)。
   - 这两种遍历方法都适用于无向图和有向图。

### 总结:
在图的遍历中,深度优先搜索和广度优先搜索是两种主要的遍历方法。深度优先搜索在探索尽可能深的分支时,会一直前进,直到没有更深的分支为止。而广度优先搜索则是一层一层地进行遍历。在进行图的遍历时,为了避免重复访问,通常需要一个 `visited` 数组来标记已经访问过的顶点。

 

 6.5.1 深度优先搜索

主要内容:

### 6.5.1 深度优先搜索 (DFS)

#### 1. 深度优先搜索遍历过程

- 深度优先搜索遍历类似于树的先序遍历。
- 对于连通图的遍历过程:
  1. 从图中某顶点v出发,访问v。
  2. 找出刚访问过的顶点的未被访问的邻接点,访问该顶点,并以该顶点为新顶点重复此步骤,直至没有未被访问的邻接点为止。
  3. 返回至上一个有未访问邻接点的顶点,访问其未被访问的邻接点。
  4. 重复2和3步骤,直至所有顶点都被访问过。

- 举例: 无向图G₄的深度优先搜索遍历图如图6.17(b)所示。

- 深度优先搜索所构成的所有顶点和实箭头的边,称为深度优先生成树。

#### 2. 算法实现

- 深度优先搜索遍历是递归的。
- 使用访问标志数组`visited[n]`来标记顶点是否被访问。

**算法6.3 深度优先搜索遍历连通图**

void DFS(Graph G, int v) {
    cout << v;
    visited[v] = true;
    for (w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w))
        if (!visited[w]) DFS(G, w);
}


- 如果是非连通图,需要循环调用该算法,直到所有顶点都被访问过。

**算法6.4 深度优先搜索遍历非连通图**

void DFSTraverse(Graph G) {
    for (v = 0; v < G.vexnum; ++v) visited[v] = false;
    for (v = 0; v < G.vexnum; ++v) if (!visited[v]) DFS(G, v);
}

- 算法6.5 和算法6.6 分别采用邻接矩阵和邻接表实现了深度优先搜索遍历。

#### 3. 算法分析

- 每个顶点至多调用一次DFS()函数。
- 时间复杂度取决于存储结构。邻接矩阵为O(n²);邻接表为O(e),其中n为顶点数,e为边数。

### 总结

深度优先搜索(DFS)是一种常用的图遍历算法,适用于连通图和非连通图。它从图中的一个顶点出发,递归地访问其所有未访问过的邻接点。DFS的实现依赖于图的存储结构,因此,不同的存储结构会导致不同的实现方法和时间复杂度。在实际应用中,需要根据具体情况选择合适的存储结构和实现方法。

我的理解:

我们可以将深度优先搜索(DFS)比喻为一个人在迷宫中探路的过程。

### 深度优先搜索(DFS)如同探索迷宫:
1. **迷宫与图**
   - 想象一个迷宫,其中每个房间是图中的一个顶点,每个通道是边。
   - 迷宫的入口是搜索的起始顶点。

2. **探险家与算法执行**
   - 想象探险家是在执行DFS的算法。
   - 探险家进入一个房间(访问一个顶点)时会做记号,避免再次进入(标记为已访问)。

3. **深入探索与递归**
   - 探险家每次会选择一个未探索的通道深入探索,直到来到一个没有未探索通道的房间(递归访问邻接点)。
   - 在无法继续深入时,他会回溯到上一个房间,寻找其他未探索的通道(回溯)。

4. **全图遍历**
   - 如果迷宫有多个不连通的区域,探险家会完成一个区域的探索后,返回到入口,选择另一个未探索的区域进行探索,直到整个迷宫都被探索完毕。
   - 这就如同在非连通图中,从每个未访问的顶点出发进行DFS,直至图中的所有顶点都被访问。

### 深度优先生成树
就像探险家探索迷宫时可能会画出一张地图,这张地图记录了他访问过的所有房间和他通过的通道,这张地图就像是深度优先搜索过程中生成的树,称为深度优先生成树。

### 总结
通过以上的比喻,深度优先搜索就像一个探险家在未知的迷宫中进行探索,他会深入每一个通道,直到不能再深入为止,然后回溯到上一个分岔口,继续探索其他通道,直到整个迷宫都被探索完毕。在这个过程中,探险家画出的迷宫地图就是深度优先生成树。

 6.5.2 广度优先搜索

主要内容:

### 6.5.2 广度优先搜索

#### 1. 广度优先搜索遍历的过程
广度优先搜索(BFS)类似于树的层次遍历。过程如下:
- (1) 从图中某个顶点v出发并访问v。
- (2) 依次访问v的各个未曾访问过的邻接点。
- (3) 对每个邻接点,再依次访问它们的邻接点,重复此过程,直至所有已被访问的顶点的邻接点都被访问到。
- 例子中展示了一种具体的广度优先搜索过程,所有的顶点和边构成了一棵广度优先生成树。

#### 2. 广度优先搜索遍历的算法实现
广度优先搜索的特点是尽可能先进行横向搜索。算法实现时需要引入队列来保存已被访问过的顶点,并需要一个访问标志数组来记录哪些顶点被访问过。

【算法步骤】
- ① 从图中某个顶点v出发,访问v,并将visited[v]置为true,然后使v入队。
- ② 只要队列不空,重复以下操作:
  - 队头元素u出队;
  - 依次检查u的所有邻接点w,如果visited[w]为false, 则访问w,并将visited[w]置为true,然后使w入队。

【算法描述】

void BFS(Graph G, int v) {
    cout << v;
    visited[v] = true; // 访问第v个顶点,并将访问标志数组的相应分量值置为true
    InitQueue(Q); // 辅助队列Q初始化,置空
    EnQueue(Q, v); // v入队
    while (!QueueEmpty(Q)) { // 队列非空
        DeQueue(Q, u); // 队头元素出队并置为u
        for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) // 依次检查u的所有邻接点w
            if (!visited[w]) {
                cout << w;
                visited[w] = true;
                EnQueue(Q, w);
            }
    }
}

#### 3. 广度优先搜索的时间复杂度
广度优先搜索的时间复杂度与遍历所有顶点和边的次数有关,通常情况下,广度优先搜索的时间复杂度为\(O(V + E)\),其中V是顶点的数量,E是边的数量。

#### 笔记总结
广度优先搜索(BFS)是一种用于遍历或搜索树或图的算法。这个算法使用队列来记录要访问的顶点,并使用一个访问标志数组来避免重复访问。广度优先搜索的基本思想是“宽度优先”,即先访问起始顶点的所有邻接点,然后再访问这些邻接点的未访问邻接点。此算法通常用于找到无权图中的最短路径。

 我的理解:

当然可以。我们可以将广度优先搜索(BFS)比喻为在一座未知的城市中探索。

假设你现在站在一个城市的某个路口(初始顶点),你的目标是探索这座城市中的每个路口(每个顶点)。城市中的每条街道(边)都连接着两个路口(两个顶点)。

1. **开始探索:**
   - 当你来到一个新的路口,你会在地图上标记这个路口(访问顶点,并标记为已访问)。
   - 然后,你会查看与这个路口直接相连的所有其他路口(邻接点),并将它们加入到你的“待探索”名单中(入队)。

2. **探索邻居:**
   - 你会先探索与起始路口直接相连的所有路口,即在移动到下一层邻居之前,你会探索所有当前层的邻居(横向搜索)。
   - 在探索每个路口时,你会将所有未被探索的相邻路口加入到“待探索”名单中。
   - 你会重复这个过程,直到所有的路口都被探索过(队列为空)。

3. **记录与计划:**
   - 为了记住你需要探索哪些路口,你会使用一张清单(队列)。
   - 每次你探索一个新的路口,你会将与它直接相连的所有未探索的路口加入到这张清单中。
   - 同时,你会在地图上标记每个已探索的路口,以防重复探索(访问标志数组)。

这个过程就像是一场由内而外的探索。你始终优先探索离你最近的、还未探索过的路口,直到整座城市都被你探索过。在这个过程中,你使用了一个清单(队列)来保持探索的顺序,以及一张地图(访问标志数组)来避免重复探索。

 

 

 

例题: 

### 题目:岛屿数量

假设有一个二维数组,`0` 表示水域,`1` 表示陆地。相邻的陆地形成一个岛屿,求出岛屿的数量。

例如:

```plaintext
11110
11010
11000
00000
```

输出:`1`

```plaintext
11000
11000
00100
00011
```

输出:`3`

### 思考过程和分析过程:

#### 思考过程:

1. **深度优先遍历:**
   从一个陆地出发,尽可能深入,将与其相连的所有陆地都访问一遍,每找到一个陆地就将其标记为已访问,最终我们就能得到一个岛屿。
   
2. **广度优先遍历:**
   从一个陆地出发,访问其所有相邻的陆地,将未访问的陆地加入队列,再依次访问队列中的每个陆地及其相邻陆地,直到队列为空。

#### 分析过程:

1. 对于深度优先遍历和广度优先遍历,我们都需要遍历整个二维数组,当遇到一个`1`时,就从这个点出发,进行深度优先遍历或广度优先遍历,标记相连的所有陆地,岛屿数加`1`。
   
2. 时间复杂度:
   遍历整个二维数组,每个点都会被访问一次,所以时间复杂度为:\(O(m \times n)\),其中`m`和`n`分别为二维数组的行数和列数。

### C++ 实现:

#### 深度优先遍历

#include <vector>
using namespace std;

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty() || grid[0].empty()) return 0;
        
        int count = 0;
        for(int i = 0; i < grid.size(); ++i) {
            for(int j = 0; j < grid[0].size(); ++j) {
                if(grid[i][j] == '1') {
                    dfs(grid, i, j);
                    ++count;
                }
            }
        }
        
        return count;
    }
    
    void dfs(vector<vector<char>>& grid, int i, int j) {
        if(i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] == '0')
            return;
        
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
};

#### 广度优先遍历

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

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty() || grid[0].empty()) return 0;
        
        int count = 0;
        for(int i = 0; i < grid.size(); ++i) {
            for(int j = 0; j < grid[0].size(); ++j) {
                if(grid[i][j] == '1') {
                    bfs(grid, i, j);
                    ++count;
                }
            }
        }
        
        return count;
    }
    
    void bfs(vector<vector<char>>& grid, int i, int j) {
        queue<pair<int, int>> q;
        q.push({i, j});
        grid[i][j] = '0';
        
        while(!q.empty()) {
            auto [x, y] = q.front(); q.pop();
            
            if(x > 0 && grid[x - 1][y] == '1') {
                q.push({x - 1, y});
                grid[x - 1][y] = '0';
            }
            if(x < grid.size() - 1 && grid[x + 1][y] == '1') {
                q.push({x + 1, y});
                grid[x + 1][y] = '0';
            }
            if(y > 0 && grid[x][y - 1] == '1') {
                q.push({x, y - 1});
                grid[x][y - 1] = '0';
            }
            if(y < grid[0].size() - 1 && grid[x][y + 1] == '1') {
                q.push({x, y + 1});
                grid[x][y + 1] = '0';
            }
        }
    }
};

### Java 实现

#### 深度优先遍历

class Solution {
    public int numIslands(char[][] grid) {
        if(grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        
        int count = 0;
        for(int i = 0; i < grid.length; ++i) {
            for(int j = 0; j < grid[0].length; ++j) {
                if(grid[i][j] == '1') {
                    dfs(grid, i, j);
                    ++count;
                }
            }
        }
        
        return count;
    }
    
    void dfs(char[][] grid, int i, int j) {
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0')
            return;
        
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
}

#### 广度优先遍历

import java.util.Queue;
import java.util.LinkedList;

class Solution {
    public int numIslands(char[][] grid) {
        if(grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        
        int count = 0;
        for(int i = 0; i < grid.length; ++i) {
            for(int j = 0; j < grid[0].length; ++j) {
                if(grid[i][j] == '1') {
                    bfs(grid, i, j);
                    ++count;
                }
            }
        }
        
        return count;
    }
    
    void bfs(char[][] grid, int i, int j) {
        Queue<int[]> q = new

 LinkedList<>();
        q.add(new int[]{i, j});
        grid[i][j] = '0';
        
        while(!q.isEmpty()) {
            int[] cur = q.poll();
            int x = cur[0], y = cur[1];
            
            if(x > 0 && grid[x - 1][y] == '1') {
                q.add(new int[]{x - 1, y});
                grid[x - 1][y] = '0';
            }
            if(x < grid.length - 1 && grid[x + 1][y] == '1') {
                q.add(new int[]{x + 1, y});
                grid[x + 1][y] = '0';
            }
            if(y > 0 && grid[x][y - 1] == '1') {
                q.add(new int[]{x, y - 1});
                grid[x][y - 1] = '0';
            }
            if(y < grid[0].length - 1 && grid[x][y + 1] == '1') {
                q.add(new int[]{x, y + 1});
                grid[x][y + 1] = '0';
            }
        }
    }
}

### C实现

由于C语言编写会稍显复杂一些,这里简要描述一下步骤和关键实现:
1. 用一个二维字符数组表示地图,用两重循环遍历地图上的每一点。
2. 如果找到一个‘1’,就用深度优先遍历或者广度优先遍历将与它相连的所有‘1’都标记为‘0’,同时岛屿数量+1。
3. 对于深度优先遍历,可以用递归实现。
4. 对于广度优先遍历,可以用一个队列来保存待访问的点。

 

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

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

相关文章

JavaWeb开发-09-MyBatis

官网&#xff1a;https://mybatis.org/mybatis-3/zh/index.html 一.Mybatis入门 1.快速入门 2.JDBC介绍 3.数据库连接池 官方地址&#xff1a;https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter 4.lombok 二.Mybatis基础增删改查 1.准备 2.删除 3.新增…

AT24C02芯片

AT24C02简介&#xff1a; AT24C01/02/04/08/16...是一个 1K/2K/4K/8K/16K 位串行 CMOS内部有9个字节&#xff1b; 该器件通过 I2C 总线接口进行 操作&#xff0c;它有一个专门的写保护功能&#xff1b; 基于51 他有这个芯片操作 时序&#xff1a; AT24C02软件编程&#xff1a; …

darknet yolov3 模型训练步骤整理

1. 把需要训练的图片拷贝到 D:\模型训练\darknet-master\build\darknet\x64\data\VOCdevkit\VOC2012\JPEGImage 2. 执行imageName.py 文件&#xff0c;重命名JPEGImage下的图片。 D:\模型训练\darknet-master\build\darknet\x64\data\VOCdevkit\VOC2012\imageName.py 3. 用…

Termius 8 for Mac(多协议服务器连接软件)

Termius是一款远程访问和管理工具&#xff0c;旨在帮助用户轻松地远程连接到各种服务器和设备。它适用于多种操作系统&#xff0c;包括Windows、macOS、Linux和移动设备。 该软件提供了一个直观的界面&#xff0c;使用户可以通过SSH、Telnet和Mosh等协议连接到远程设备。它还支…

微表情识别API + c++并发服务器系统

微表情识别API系统 该项目只开源前后端程序&#xff0c;模型不开源 地址&#xff1a;https://github.com/lin-lai/-API- 更新功能 4.1版本 改用epoll实现IO多路复用并发服务器去除标志位设定—正在考虑用协议实现 项目介绍 本项目用于检测并识别视频中人脸的微表情 目标任…

【插件】页面引导库driver.js:

文章目录 一、效果图:二、实现思路:三、实现代码:【1】Driver.js 的技术特性【2】安装依赖【3】代码实现【4】 配置相关参数 一、效果图: 二、实现思路: 【官网】https://driverjs.com/docs/installation 【npm】https://www.npmjs.com/package/driver.js 【案例】改造driver.j…

用Python在XML和Excel表格之间实现互转

XML是一种超文本标记语言和文件格式&#xff0c;具有可自定义标签&#xff0c;易于扩展&#xff0c;便于编辑&#xff0c;传输便捷等优点。XML已成为应用数据交换的常用方式。虽然XML格式易于传输和开发者操作&#xff0c;但对于普通用户来说&#xff0c;数据以xls或xlsx的形式…

二维穿墙雷达CW112 的 优势

TFN CW112加固型二维定位穿墙雷达是一款综合UWB雷达和生物医学工程技术研制而成的人体目标探测装备。该产品可穿透建筑墙体等障碍物&#xff0c;实时获取其后方人体目标位置及数量等信息&#xff0c;具有穿透性强、轻质便携、可双手操控等特点&#xff0c;广泛应用于反恐处突、…

vue+element项目创建步骤

一、创建vue项目步骤 要创建一个Vue Element UI的项目&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1.确保你已经安装了Node.js和npm&#xff08;Node.js的包管理器&#xff09;。你可以在命令行中运行以下命令来检查它们是否已经安装&#xff1a; node -vnpm -v2.使…

视频超过1g怎么压缩?分享简单的视频压缩方法

随着科技的发展&#xff0c;视频已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;有时候我们会遇到视频文件过大&#xff0c;无法顺利传输或存储的问题。那么&#xff0c;如何解决视频超过1g压缩的问题呢&#xff1f; #长假读书清单# 首先我们可以借助专业的压缩…

华为云云耀云服务器L实例评测|运行python脚本

目录 一、往期回顾二、华为云云耀云服务器L实例使用流程1、账号注册2、购买并配置云耀云服务器L实例3、登录并使用云耀云服务器L实例 三、为什么Python越来越火&#xff1f;四、使用宝塔运行Python脚本1、下载Python项目管理器2、上传项目到服务器3、添加项目4、安装requests启…

一文读懂Llama 2(从原理到实战)

简介 Llama 2&#xff0c;是Meta AI正式发布的最新一代开源大模型。 Llama 2训练所用的token翻了一倍至2万亿&#xff0c;同时对于使用大模型最重要的上下文长度限制&#xff0c;Llama 2也翻了一倍。Llama 2包含了70亿、130亿和700亿参数的模型。Meta宣布将与微软Azure进行合…

便携式水质采样设备助力毒情监测

便携式水质采样设备助力毒情监测 污水涉毒采样检测工作是运用科技手段准确评估监测辖区内毒情形势的重要手段。期间&#xff0c;民警详细了解了生活和工业污水的处理、排放以及服务范围、人口数量等情况&#xff0c;并就污水涉毒采样检测工作达成共识。随后&#xff0c;民警严格…

【车载开发系列】ECU Application Software程序刷新步骤

【车载开发系列】ECU Application Software程序刷新步骤 ECU Application Software程序刷新步骤 【车载开发系列】ECU Application Software程序刷新步骤一. Boot Software&#xff08;引导软件&#xff09;1&#xff09;boot manager&#xff08;启动管理器&#xff09;2&…

Flink1.12.7 Standalone版本安装

官网下载版本&#xff1a;https://archive.apache.org/dist/flink/flink-1.12.7/flink-1.12.7-bin-scala_2.12.tgz 可以从首页找到Downloads | Apache Flink&#xff0c;一直往下拉 安装&#xff1a;下载后直接解压即可 添加全局参数&#xff1a; #vi /etc/profile FLINK_HO…

云服务器免费体检

三丰云提供了免费云服务器与免费虚拟主机服务&#xff0c; 个人学习、搭建个人网站或者微信小程序调试等可以申请一台。 免费申请网址为&#xff1a; https://www.sanfengyun.com/ 还是挺方便的&#xff0c;大家可以体验体验。

【校招VIP】前端操作系统之I/O调度算法

考点介绍 I/O 调度算法在各个进程竞争磁盘I/O的时候担当了裁判的角色。他要求请求的次序和时机做最优化的处理&#xff0c;以求得尽可能最好的整体I/O性能。 前端操作系统之I/O调度算法-相关题目及解析内容可点击文章末尾链接查看&#xff01; 一、考点题目 1. 某文件占10个…

Kafka的消息存储机制

前面咱们简单讲了K啊开发入门相关的概念、架构、特点以及安装启动。 今天咱们来说一下它的消息存储机制。 前言&#xff1a; Kafka通过将消息持久化到磁盘上的日志文件来实现高吞吐量的消息传递。 这种存储机制使得Kafka能够处理大量的消息&#xff0c;并保证消息的可靠性。 1…

西门子S7-1200使用LRCF通信库与安川机器人进行EthernetIP通信的具体方法示例

西门子S7-1200使用LRCF通信库与安川机器人进行EthernetIP通信的具体方法示例 准备条件: PLC:S7-1200 1214C DC/DC/DC 系统版本4.5及以上。 机器人控制柜:安川YRC1000。 软件:TIA V17 PLC做主站,机器人做从站。 具体方法可参考以下内容: 使用的库文件为西门子 1200系列…