图的遍历是指从图中的某个顶点出发,按照一定的规则访问图中所有顶点,并使每个顶点仅被访问一次。图的遍历包括深度优先搜索(DFS)和广度优先搜索(BFS)两种主要方法,它们在算法设计、路径搜索、网络分析等方面有广泛的应用。
深度优先搜索(DFS):
类似于树的先序遍历,采用递归或栈的方式实现。其基本思想是从一个起始顶点开始,访问一个顶点后,继续访问它的未访问过的邻接顶点,直到所有邻接顶点都被访问过为止,然后回溯到上一个顶点,继续这一过程,直到所有顶点都被访问过。
递归算法的步骤如下:
1. 访问起始顶点,并标记为已访问。
2. 从该顶点出发,依次访问每个未被访问的邻接顶点,重复步骤 1。
3. 若当前顶点的所有邻接顶点都被访问过,则回溯到上一个顶点,继续访问其他未被访问的邻接顶点。
其递归代码示例如下:
c示例
boolean visited[max_vertex_num]; // 访问标志数组
status (*visitfunc)(int v); // visitfunc 是访问函数,对图的每个顶点调用该函数
void dfstraverse(graph g, status (*visit)(int v)) {
visitfunc = visit;
for (v = 0; v < g.vexnum; ++v)
visited[v] = false; // 访问标志数组初始化
for (v = 0; v < g.vexnum; ++v)
if (!visited[v])
dfs(g, v); // 对尚未访问的顶点调用 dfs
}
void dfs(graph g, int v) { // 从第 v 个顶点出发递归地深度优先遍历图 g
visited[v] = true;
visitfunc(v); // 访问第 v 个顶点
for (w = firstadjvex(g, v); w >= 0; w = nextadjvex(g, v, w)) // firstadjvex 返回 v 的第一个邻接顶点,若顶点在 g 中没有邻接顶点,则返回空(0)。若 w 是 v 的邻接顶点,nextadjvex 返回 v 的(相对于 w 的)下一个邻接顶点。若 w 是 v 的最后一个邻接点,则返回空(0)。
if (!visited[w])
dfs(g, w); // 对 v 的尚未访问的邻接顶点 w 调用 dfs
}
广度优先搜索(BFS):
类似于树的层次遍历,采用队列的方式实现。其基本思想是从一个起始顶点开始,访问一个顶点后,将其所有未被访问的邻接顶点依次入队,访问完当前顶点后,出队下一个顶点,继续这一过程,直到所有顶点都被访问过为止。
非递归算法的步骤如下:
1. 访问起始顶点,并标记为已访问,将该顶点入队。
2. 当队列不为空时,出队一个顶点,访问它的所有未被访问的邻接顶点,并将这些邻接顶点依次入队。
3. 重复步骤 2,直到队列为空。
c示例
visited[v] = false;
initqueue(q); // 置空辅助队列 q
for (v = 0; v < g.vexnum; ++v)
if (!visited[v]) {
visited[v] = true;
visitfunc(v);
enqueue(q, v); // v 入队列
while (!queueempty(q)) {
dequeue(q, u); // 队头元素出队并置为 u
for (w = firstadjvex(g, u); w >= 0; w = nextadjvex(g, u, w))
if (!visited[w]) { // w 为 u 的尚未访问的邻接顶点
visited[w] = true;
visitfunc(w);
enqueue(q, w);
}
}
}
C++实现
图的深度优先遍历(DFS)和广度优先遍历(BFS):
cpp示例
#include <iostream>
#include <vector>
#include <queue>
// 邻接表表示的图
class Graph {
int numVertices;
std::vector<std::vector<int>> adjacencyList;
public:
Graph(int vertices) : numVertices(vertices), adjacencyList(vertices) {}
// 添加边
void addEdge(int src, int dest) {
adjacencyList[src].push_back(dest);
}
// 深度优先遍历的辅助函数
void dfsHelper(int vertex, std::vector<bool>& visited) {
visited[vertex] = true;
std::cout << vertex << " ";
for (int neighbor : adjacencyList[vertex]) {
if (!visited[neighbor]) {
dfsHelper(neighbor, visited);
}
}
}
// 深度优先遍历
void dfs(int startVertex) {
std::vector<bool> visited(numVertices, false);
dfsHelper(startVertex, visited);
}
// 广度优先遍历
void bfs(int startVertex) {
std::vector<bool> visited(numVertices, false);
std::queue<int> queue;
visited[startVertex] = true;
queue.push(startVertex);
while (!queue.empty()) {
int currentVertex = queue.front();
queue.pop();
std::cout << currentVertex << " ";
for (int neighbor : adjacencyList[currentVertex]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.push(neighbor);
}
}
}
}
};
int main() {
Graph g(5); // 5 个顶点的图
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
std::cout << "深度优先遍历: ";
g.dfs(2);
std::cout << std::endl;
std::cout << "广度优先遍历: ";
g.bfs(2);
return 0;
}
在上述代码中, Graph 类表示图。 dfsHelper 函数用于深度优先遍历的递归, dfs 函数从指定顶点开始进行深度优先遍历, bfs 函数进行广度优先遍历。
图的遍历操作具有复杂性,主要体现在以下几个方面:
1. 图中没有一个“自然”的首结点,任意一个顶点都可作为第一个被访问的结点。
2. 在非连通图中,从一个顶点出发只能访问它所在的连通分量上的所有顶点,还需考虑选取下一个出发点以访问其余的连通分量。
3. 若图中有回路,一个顶点被访问之后,有可能沿回路又回到该顶点。
4. 一个顶点可以和其他多个顶点相连,访问过后存在选取下一个要访问顶点的问题。
图的遍历还有一些具体的分类,例如:
1. 遍历完所有的边而不能有重复,即所谓“一笔画问题”或“欧拉 路径”。
2. 遍历完所有的顶点而没有重复,即所谓“哈密尔顿问题”。
3. 遍历完所有的边而可以有重复,即所谓“中国邮递员问题”。
4. 遍历完所有的顶点而可以重复,即所谓“旅行推销员问题”。
其中,第一和第三类问题已经得到了完满的解决,而第二和第四类问题则只得到了部分解决。第一类问题就是研究所谓的欧拉图的性质,而第二类问题则是研究所谓的哈密尔顿图的性质。