【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

目录
相关文章
|
15天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
132 77
|
15天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
56 19
|
15天前
|
存储 人工智能 算法
【C++数据结构——图】最短路径(头歌教学实验平台习题) 【合集】
任务描述 本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。 相关知识 为了完成本关任务,你需要掌握:Dijkst本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。为了完成本关任务,你需要掌握:Dijkstra算法。带权有向图:该图对应的二维数组如下所示:Dijkstra算法:Dijkstra算法是指给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径。Dijkstra算法的具体步骤如下:(1)初始时,S只包含源点,即S={v},v的距离为0。
46 15
|
15天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
38 10
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
291 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
45 1
|
15天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
37 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
15天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
38 9
|
15天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
30 7
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
90 5