一、深度优先遍历DFS
1、定义
- 深度优先遍历(Depth-First Search,简称 DFS)是一种用于遍历或搜索图(graph)和树(tree)数据结构的算法。其基本思想是尽可能深入图的路径,然后回溯,直到找到所有连接的节点。在此过程中,DFS 递归地访问每个未访问的相邻节点。
- 适用场景
在图论中,DFS 用于搜索图的连接组件、检测环、构建生成树、路径查找等。
在树数据结构中,DFS 用于遍历整个树,通常用来实现先序、中序和后序遍历。 - 如何工作
DFS 的基本流程是:
从一个起点节点开始。
标记当前节点为已访问。
对于每个相邻节点,如果未被访问,则递归调用 DFS。
如果所有相邻节点都已访问,则回溯到上一个节点。
1、代码详解
#include <iostream>
#include <vector>
#include <list>
using namespace std;
// 图的类
class Graph {
public:
// 构造函数,numVertices 是顶点数
Graph(int numVertices);
// 向图中添加边
void addEdge(int src, int dest);
// 深度优先遍历
void DFS(int startVertex);
private:
int numVertices; // 顶点数
vector<list<int>> adjLists; // 邻接表
vector<bool> visited; // 标记已访问的顶点
// 深度优先遍历的辅助函数
void DFSUtil(int vertex);
};
// 构造函数,初始化顶点数和邻接表
Graph::Graph(int numVertices) {
this->numVertices = numVertices;
adjLists.resize(numVertices);
visited.resize(numVertices, false);
}
// 添加边
void Graph::addEdge(int src, int dest) {
adjLists[src].push_back(dest);
}
// 深度优先遍历
void Graph::DFS(int startVertex) {
// 重置访问标记
fill(visited.begin(), visited.end(), false);
// 从指定的起点开始遍历
DFSUtil(startVertex);
}
// 深度优先遍历的辅助函数
void Graph::DFSUtil(int vertex) {
// 标记当前顶点为已访问
visited[vertex] = true;
cout << vertex << " "; // 打印顶点
// 递归访问邻接表中的所有相邻顶点
for (int adjVertex : adjLists[vertex]) {
if (!visited[adjVertex]) {
DFSUtil(adjVertex);
}
}
}
int main() {
// 创建一个有 5 个顶点的图
Graph g(5);
// 添加一些边
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 3);
g.addEdge(1, 4);
// 从顶点 0 开始深度优先遍历
std::cout << "DFS starting from vertex 0: ";
g.DFS(0);
return 0;
}
这个 C++ 程序定义了一个用于表示图的类 Graph
,并提供了向图中添加边的功能,以及对图进行深度优先遍历 (DFS) 的功能。我们详细分析下这个程序的各个部分:
Graph 类
-
成员变量:
numVertices
: 表示图中的顶点数量。adjLists
: 使用向量 (vector) 存储邻接表,每个顶点对应一个list<int>
,表示该顶点的所有相邻顶点。visited
: 一个布尔向量,表示各顶点是否已经被访问。
-
构造函数
Graph(int numVertices)
:- 初始化图,设置顶点数量,并根据顶点数量调整
adjLists
和visited
的大小。
- 初始化图,设置顶点数量,并根据顶点数量调整
-
成员函数
addEdge(int src, int dest)
:- 在图中添加一条边,表示从
src
顶点到dest
顶点的连接。实现方式是将dest
添加到adjLists[src]
中。
- 在图中添加一条边,表示从
-
成员函数
DFS(int startVertex)
:- 从指定的起点进行深度优先遍历。它首先重置
visited
,然后调用辅助函数DFSUtil
从startVertex
开始遍历。
- 从指定的起点进行深度优先遍历。它首先重置
-
辅助函数
DFSUtil(int vertex)
:- 递归地访问顶点及其邻接顶点,完成深度优先遍历。它会标记
vertex
为已访问,然后递归遍历所有与vertex
相邻且尚未访问的顶点。
- 递归地访问顶点及其邻接顶点,完成深度优先遍历。它会标记
主函数 main
- 创建了一个有 5 个顶点的图
Graph g(5)
。 - 使用
addEdge
添加了几条边,构成一个简单的有向图结构。 - 然后通过
DFS(0)
从顶点 0 开始进行深度优先遍历,并输出遍历的结果。
程序输出
输出结果应该是一个深度优先遍历序列,从指定的起点顶点开始,按照递归方式遍历图。给定的添加边的顺序,将从顶点 0 开始,沿着 0 → 1 → 3 → 4 的路径进行遍历,之后可能会访问顶点 2。
总结
这个程序是一个基本的深度优先遍历实现,展示了如何使用邻接表来表示图,并实现了递归 DFS。它可以用作学习图数据结构和递归遍历算法的示例。
std::vector<std::list> adjLists
表示了 Graph
类中的一个成员变量 adjLists
,用于存储图的邻接表。让我们来解析这行代码的构成:
std::vector
:vector
是 C++ 标准库中一个动态数组类型,它可以自动调整大小。使用vector
允许根据顶点数量动态调整邻接表的大小。std::list
:list
是 C++ 标准库中的双向链表类型。用list
表示邻接表的每个元素,这样可以方便地在图中添加或删除边。std::vector<std::list<int>>
:这是一个由list<int>
组成的vector
。每个list<int>
表示一个顶点的邻接表,其中包含该顶点相邻的所有顶点的索引。adjLists
:这是这个vector
的变量名。通常,邻接表是用来存储每个顶点的邻接关系。
邻接表的作用
在图的表示中,邻接表是一种常见的方法。对于图的每个顶点,邻接表存储与该顶点相连的所有其他顶点。因此,邻接表的大小与顶点数相等,adjLists[i]
表示与顶点 i
相连的顶点列表。
这个成员变量的使用
在这个 Graph
类中,adjLists
用于存储图的边的连接关系。在 addEdge
函数中,当添加边时,将目标顶点添加到源顶点的邻接列表中,即 adjLists[src].push_back(dest)
。这代表在图中创建了一条从 src
到 dest
的有向边。
在深度优先遍历(DFS)中,邻接表也被用来遍历从某个起始顶点出发的所有相邻顶点。
总结
std::vector<std::list<int>> adjLists
是一种表示图邻接关系的结构,用于管理图中顶点之间的连接。这种结构适用于稀疏图,因为它的空间效率较高,并且允许动态增加或删除边。
this->numVertices = numVertices
这行代码中的 this->numVertices = numVertices;
主要用于在类的构造函数中初始化类的成员变量 numVertices
。具体解释如下:
this
:这是 C++ 中一个特殊的指针,指向当前实例化的对象。在类的构造函数中,this
指向正在初始化的对象。numVertices
:这是Graph
类的一个成员变量,用于存储图的顶点数量。numVertices = numVertices
:等号左边的numVertices
是Graph
类的成员变量,而右边的numVertices
是传递给构造函数的参数。通过this->
来明确左边的numVertices
是成员变量,而不是局部变量。
作用
在构造函数中,成员变量的初始化是非常重要的步骤。这行代码将传递给构造函数的 numVertices
参数值赋给 Graph
类的成员变量 numVertices
,以便在其他地方使用。
用途
在这个图的例子中,这行代码用来设置图的顶点数,然后使用这个数值来创建邻接表和访问标记。这个初始化过程确保类的内部状态在对象创建时处于一致的、正确的状态。
为什么需要 this
如果没有 this
,代码可能会因为变量名称冲突而产生歧义。在构造函数中,参数 numVertices
与类的成员变量 numVertices
具有相同的名称,通过使用 this->
,可以明确地指示要访问的成员变量,避免歧义。
总结
this->numVertices = numVertices;
是将构造函数的参数值赋给类的成员变量,这在构造函数中是常见的做法,用于确保对象在创建时初始化正确的内部状态。
这行代码 adjLists.resize(numVertices);
用于调整 adjLists
这个向量的大小,使其包含 numVertices
个元素。在当前上下文中,adjLists
是用于表示图的邻接表的数据结构。我们可以从几个方面解释这行代码的含义和用途。
什么是邻接表
邻接表是图的表示方法之一。在这种表示方式中,图的每个顶点都有一个列表,列表中存储与该顶点相连的其他顶点。邻接表适合用于表示稀疏图,其中大部分顶点之间没有直接连接。
向量 adjLists
adjLists
是 Graph
类中的一个成员变量,使用 std::vector<std::list<int>>
作为数据结构。这个向量中的每个元素都是一个 std::list<int>
, 代表一个顶点的邻接列表。
resize
方法
resize(numVertices)
: 这是std::vector
的方法,它将向量的大小调整为numVertices
个元素。- 如果当前向量的大小小于
numVertices
,则resize
会扩展向量,创建新元素。若大于numVertices
,则会移除多余的元素。 - 在本例中,
adjLists.resize(numVertices)
确保adjLists
有足够的空间来表示图的所有顶点。
为什么需要 resize
这行代码通常用于初始化阶段。在图的构造函数中,通过传递给构造函数的 numVertices
参数确定图的顶点数。因此,在构造函数中,必须确保 adjLists
的大小足够容纳所有顶点的邻接表。
总结
这行代码用于调整邻接表的大小,使其与图的顶点数相匹配。这是构造函数中常见的操作,确保数据结构在初始化时处于正确的状态。
adjLists[src].push_back(dest);
是一行用于在图结构中添加一条边的代码,它的具体含义和作用如下:
前提知识
在图的邻接表表示中,使用数据结构来表示图的边和顶点关系。std::vector<std::list<int>> adjLists
通常用于存储每个顶点的邻接关系。adjLists
是一个向量,其中每个元素都是一个 list<int>
,表示与某个顶点相邻的所有顶点。
这行代码的解释
src
:这是源顶点的索引,表示起点。dest
:这是目标顶点的索引,表示终点。adjLists[src]
:这是src
顶点的邻接表。它是一个list<int>
,包含与src
相邻的所有顶点。push_back(dest)
:这是向list<int>
中添加元素的方法。push_back
会将dest
添加到adjLists[src]
的末尾。
作用
这行代码用于在图中添加一条从 src
到 dest
的边。它会将 dest
添加到 src
顶点的邻接表中,表示 src
与 dest
之间有连接。
示例
// 添加一条从顶点 1 到顶点 3 的边
adjLists[1].push_back(3);
在这个示例中,adjLists[1].push_back(3)
表示在图中添加了一条从顶点 1
到顶点 3
的边。通过这种方式,邻接表反映了图的边和顶点的关系。
注意事项
- 有向图和无向图:在有向图中,这表示从
src
到dest
的连接。如果是无向图,则需要同时添加从dest
到src
的连接。 - 重复边:
list<int>
允许重复元素,因此可能会出现多条从src
到dest
的边。 - 图的遍历:在深度优先遍历、广度优先遍历等图算法中,邻接表是重要的输入数据结构。
总结
这行代码用于在图的邻接表中添加一条边,从源顶点到目标顶点。这种方式在图的构造、添加边、构建拓扑结构等操作中非常有用。通过邻接表,你可以快速查找与某个顶点相邻的所有顶点,从而实现各种图相关的算法和操作。