【C++数据结构——图】图的遍历(头歌教学实验平台习题) 【合集】

简介: 本文介绍了图的遍历算法,包括深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先遍历通过递归方式从起始节点深入探索图,适用于寻找路径、拓扑排序等场景;广度优先遍历则按层次逐层访问节点,适合无权图最短路径和网络爬虫等应用。文中提供了C++代码示例,演示了如何实现这两种遍历方法,并附有测试用例及结果,帮助读者理解和实践图的遍历算法。

 

目录😋

任务描述

相关知识

一、深度优先遍历

1. 定义

2. 工作原理

(1)初始状态

(2)递归探索

(3)回溯机制

3. 示例代码

4. 优势和应用场景

(1)优势

(2)应用场景

二、广度优先遍历

1. 定义

2. 工作原理

(1)初始化

(2)迭代过程

3. 示例代码

4. 优势和应用场景

(1)优势

(2)应用场景

三、带权有向图

测试说明

通关代码

测试结果


任务描述

本关任务:实现图的遍历

相关知识

为了完成本关任务,你需要掌握:

  1. 深度优先遍历
  2. 广度优先遍历

一、深度优先遍历

1. 定义

  • 深度优先遍历(Depth - First Search,简称 DFS)是一种用于遍历或搜索图(包括树,树是一种特殊的图)的算法。采用递归算法的深度优先遍历是指在遍历图的过程中,通过递归调用函数自身来实现对图中节点的深度优先访问。
  • 其基本思想是从给定的起始节点开始,沿着一条路径尽可能深地探索图,直到无法继续或者达到目标节点,然后回溯到前一个节点,继续探索其他未访问的分支路径。

2. 工作原理

(1)初始状态

  • 选择一个起始节点,将其标记为已访问,以避免重复访问。
  • 通常会使用一个数据结构(如数组、布尔向量等)来记录节点是否被访问过。

(2)递归探索

  • 对于起始节点的每一个未访问的邻接节点,递归地调用深度优先遍历函数。这意味着每次找到一个新的邻接节点,就将其视为新的起始节点,继续深入探索它的邻接节点。
  • 例如,在一个简单的图中,如果从节点 A 开始,A 有邻接节点 B 和 C。先访问 B,然后对于 B 的未访问邻接节点(假设为 D 和 E),又会分别以 D 和 E 为新的起点进行递归探索,直到所有从 B 可达的节点都被访问,然后回溯到 A,再去访问 C 及其可达节点。

(3)回溯机制

  • 当一个节点的所有邻接节点都被访问后,递归函数会返回上一层调用,这就是回溯过程。回溯到上一层后,继续探索上一层节点的其他未访问邻接节点。
  • 例如,在上述例子中,当完成对以 B 为起点的所有可达节点的访问后,就会回溯到 A,然后开始访问 C 及其可达节点。

3. 示例代码(以简单图的邻接表表示为例)

以下是一个简单的 C++ 代码示例,用于对一个图进行深度优先遍历:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Graph {
private:
    int V;
    vector<vector<int>> adj;
public:
    Graph(int V) {
        this->V = V;
        adj.resize(V);
    }
    void addEdge(int u, int v) {
        adj[u].push_back(v);
    }
    void DFS(int v, vector<bool>& visited) {
        visited[v] = true;
        cout << v << " ";
        for (int i : adj[v]) {
            if (!visited[i]) {
                DFS(i, visited);
            }
        }
    }
};
int main() {
    int V = 5;
    Graph g(V);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 3);
    g.addEdge(1, 4);
    vector<bool> visited(V, false);
    g.DFS(0, visited);
    return 0;
}
image.gif

在这个示例中:

  • Graph 类表示图,V 是顶点数量,adj 是邻接表,用于存储每个顶点的邻接顶点。
  • addEdge 函数用于向图中添加边,将一个顶点的邻接顶点添加到其对应的邻接表中。
  • DFS 函数实现了深度优先遍历。它接受一个顶点索引 v 和一个用于记录访问状态的向量 visited。首先将当前顶点标记为已访问,然后输出当前顶点,接着遍历当前顶点的所有邻接顶点。对于未访问的邻接顶点,递归调用 DFS 函数进行深度优先遍历。
  • main 函数中,创建了一个有 5 个顶点的图,添加了一些边,初始化了访问向量,然后从顶点 0 开始进行深度优先遍历。

4. 优势和应用场景

(1)优势

  • 代码简洁直观:对于熟悉递归思想的开发者来说,递归实现的深度优先遍历代码结构相对简单,易于理解和实现。
  • 自然地反映深度优先的思想:递归调用的过程很好地模拟了不断深入图的过程,符合深度优先遍历的逻辑。

(2)应用场景

  • 图的连通性判断:可以判断一个图是否是连通图,或者找出图中的连通分量。
  • 拓扑排序(对于有向无环图):在对有向无环图进行拓扑排序时,深度优先遍历是一种常用的基础算法。
  • 寻找路径:用于在图中寻找从一个节点到另一个节点的路径,如在迷宫求解等问题中,将迷宫看作一个图,使用深度优先遍历寻找从起点到终点的路径。

二、广度优先遍历

1. 定义

  • 广度优先遍历(Breadth - First Search,简称 BFS)是一种图的遍历算法。它从给定的起始顶点开始,按照与起始顶点的距离远近,一层一层地向外扩展遍历图中的顶点,直到图中的所有顶点都被访问。在遍历过程中,先访问距离起始顶点距离为 1 的顶点,再访问距离为 2 的顶点,以此类推。

2. 工作原理

(1)初始化

  • 首先,将起始顶点标记为已访问,并且将其放入一个队列(Queue)中。队列是一种先进先出(FIFO)的数据结构,这保证了在遍历过程中先访问先加入的顶点的邻接顶点。

(2)迭代过程

  • 当队列不为空时,取出队首顶点,访问该顶点,然后遍历这个顶点的所有邻接顶点。对于每个未被访问的邻接顶点,将其标记为已访问,并放入队列中。
  • 这样,每次从队列中取出的顶点都是按照距离起始顶点的层次顺序进行的,先处理离起始顶点近的顶点,再逐渐处理更远的顶点。
  • 例如,在一个简单的图中,从顶点 A 开始进行广度优先遍历。A 被放入队列,然后取出 A,访问 A 并将 A 的邻接顶点 B、C 放入队列;接着取出 B,访问 B 并将 B 的邻接顶点 D、E 放入队列(如果 D、E 未被访问),以此类推,直到队列为空。

3. 示例代码(以简单图的邻接表表示为例)

以下是一个简单的 C++ 代码示例,用于对一个图进行广度优先遍历:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Graph {
private:
    int V;
    vector<vector<int>> adj;
public:
    Graph(int V) {
        this->V = V;
        adj.resize(V);
    }
    void addEdge(int u, int v) {
        adj[u].push_back(v);
    }
    void BFS(int start) {
        vector<bool> visited(V, false);
        queue<int> q;
        visited[start] = true;
        q.push(start);
        while (!q.empty()) {
            int v = q.front();
            q.pop();
            cout << v << " ";
            for (int i : adj[v]) {
                if (!visited[i]) {
                    visited[i] = true;
                    q.push(i);
                }
            }
        }
    }
};
int main() {
    int V = 6;
    Graph g(V);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 3);
    g.addEdge(1, 4);
    g.addEdge(2, 4);
    g.addEdge(3, 5);
    g.addEdge(4, 5);
    g.BFS(0);
    return 0;
}
image.gif

在这个示例中:

  • Graph 类表示图,V 是顶点数量,adj 是邻接表,用于存储每个顶点的邻接顶点。
  • addEdge 函数用于向图中添加边,将一个顶点的邻接顶点添加到其对应的邻接表中。
  • BFS 函数实现了广度优先遍历。它接受一个起始顶点索引 start。首先创建一个用于记录访问状态的向量 visited 和一个队列 q,将起始顶点标记为已访问并放入队列。在循环中,只要队列不为空,就取出队首顶点,访问该顶点,然后遍历它的邻接顶点。对于未被访问的邻接顶点,标记为已访问并放入队列。
  • main 函数中,创建了一个有 6 个顶点的图,添加了一些边,然后从顶点 0 开始进行广度优先遍历。

4. 优势和应用场景

(1)优势

  • 找到最短路径:在无权图(边没有权重的图)中,广度优先遍历可以找到从起始顶点到其他顶点的最短路径长度。因为它是按照距离层次进行遍历的,第一次访问到某个顶点时的路径长度就是最短路径长度。
  • 均匀地搜索图:相比于深度优先遍历可能会沿着一条路径深入而忽略其他部分,广度优先遍历可以比较均匀地搜索图,对图的整体结构有更好的探索效果。

(2)应用场景

  • 最短路径问题(无权图):如在社交网络中寻找两个人之间的最短关系链,或者在迷宫中寻找从起点到终点的最短路径(将迷宫看作一个图)。
  • 网络爬虫:广度优先遍历可以用于网络爬虫中,从一个起始网页开始,一层一层地爬取链接页面,先爬取离起始网页近的链接,再逐渐向外扩展。
  • 连通分量计算:和深度优先遍历一样,也可以用于计算图的连通分量,不过广度优先遍历是按照层次来划分的。

三、带权有向图

带权有向图如下图所示,要求指定初始点,从初始点出发进行图的遍历。

image.gif


测试说明

平台会对你编写的代码进行测试:

测试输入:

0
image.gif

预期输出:

从顶点0开始的DFS:
  0  1  2  5  4  3
从顶点0开始的BFS:
  0  1  3  2  5  4
image.gif

【提示:输出遍历顶点的格式为 printf("%3d",v);】

开始你的任务吧,祝你成功!


通关代码

#include <iostream>  
#include <vector>  
#include <queue>  
#include <stack>  
#include <algorithm> // 用于sort  
using namespace std;  
class Graph {  
private:  
    int V; // 顶点数  
    vector<vector<int>> adj; // 邻接表  
public:  
    // 构造函数  
    Graph(int V) {  
        this->V = V;  
        adj.resize(V);  
    }  
    // 添加有向边  
    void addEdge(int u, int v) {  
        adj[u].push_back(v);  
    }  
    // 深度优先遍历  
    void DFS(int start) {  
        vector<bool> visited(V, false); // 访问标记  
        stack<int> s; // 使用栈实现DFS  
        s.push(start);  
        cout << "从顶点" << start << "开始的DFS:\n";  
        while (!s.empty()) {  
            int v = s.top();  
            s.pop();  
            if (!visited[v]) {  
                visited[v] = true;  
                printf("%3d", v); // 输出顶点  
            }  
            // 遍历邻接节点,逆序入栈  
            for (int i = adj[v].size() - 1; i >= 0; --i) {  
                int neighbor = adj[v][i];  
                if (!visited[neighbor]) {  
                    s.push(neighbor);  
                }  
            }  
        }  
        cout << endl;  
    }  
    // 广度优先遍历  
    void BFS(int start) {  
        vector<bool> visited(V, false); // 访问标记  
        queue<int> q; // 使用队列实现BFS  
        q.push(start);  
        visited[start] = true;  
        cout << "从顶点" << start << "开始的BFS:\n";  
        while (!q.empty()) {  
            int v = q.front();  
            q.pop();  
            printf("%3d", v); // 输出顶点  
            // 遍历邻接节点  
            for (int neighbor : adj[v]) {  
                if (!visited[neighbor]) {  
                    visited[neighbor] = true;  
                    q.push(neighbor);  
                }  
            }  
        }  
        cout << endl;  
    }  
    // 函数:排序邻接表  
    void sortAdjacencyList() {  
        for (int i = 0; i < V; ++i) {  
            sort(adj[i].begin(), adj[i].end()); // 对每个邻接链表进行排序  
        }  
    }  
};  
int main() {  
    int V = 6; // 顶点数量  
    Graph g(V);  
    // 添加边 (u, v)  
    g.addEdge(5, 4);
    g.addEdge(4, 3);
    g.addEdge(3, 2);
    g.addEdge(2, 0);    
    g.addEdge(0, 1);  
    g.addEdge(0, 3);  
    g.addEdge(1, 2);  
    g.addEdge(3, 5);  
    g.addEdge(2, 5);  
    g.addEdge(5, 0);  
    
    // 手动控制邻接表的顺序  
    g.sortAdjacencyList();  
    int startVertex;  
    cin >> startVertex;  
    // 进行深度优先遍历和广度优先遍历  
    g.DFS(startVertex);  
    g.BFS(startVertex);  
    return 0;  
}

image.gif


测试结果

image.gif

image.gif

目录
相关文章
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
158 77
|
2月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
74 19
|
2月前
|
存储 人工智能 算法
【C++数据结构——图】最短路径(头歌教学实验平台习题) 【合集】
任务描述 本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。 相关知识 为了完成本关任务,你需要掌握:Dijkst本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。为了完成本关任务,你需要掌握:Dijkstra算法。带权有向图:该图对应的二维数组如下所示:Dijkstra算法:Dijkstra算法是指给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径。Dijkstra算法的具体步骤如下:(1)初始时,S只包含源点,即S={v},v的距离为0。
67 15
|
17天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
20天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
17天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
17天前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
2月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
64 13
|
2月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
62 5
|
2月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
48 5