板子:DFS 与 BFS(邻接矩阵 + 邻接表)
两种遍历都靠
visited[]保证每个顶点只访问一次。**DFS 用递归(相当于一个隐式的栈),BFS 用队列。**换一种存储结构,只需要换「怎么找邻接点」那一行。
邻接矩阵版
bool visited[MAXV]; // 访问标记:全局数组,每次遍历前要清零
void DFS_M(MGraph &G, int v) { // 从 v 出发深度优先
printf("%d ", v); // 访问 v(这里输出顶点下标)
visited[v] = true; // 访问完立刻标记
for (int w = 0; w < G.numVertices; w++) // 按下标从小到大找 v 的邻接点
if (G.Edge[v][w] != 0 && !visited[w]) // 有边,而且还没访问过
DFS_M(G, w);
}
void BFS_M(MGraph &G, int v) { // 从 v 出发广度优先
int q[MAXV], front = 0, rear = 0; // 每个顶点只入队一次,队列开 MAXV 就够
printf("%d ", v);
visited[v] = true; // 入队时就标记
q[rear++] = v;
while (front < rear) {
int u = q[front++];
for (int w = 0; w < G.numVertices; w++)
if (G.Edge[u][w] != 0 && !visited[w]) {
printf("%d ", w); // 访问、标记、入队,三件事一起做
visited[w] = true;
q[rear++] = w;
}
}
}
void DFSTraverse(MGraph &G) { // 非连通图:每个连通分量都要出发一次
for (int i = 0; i < G.numVertices; i++) visited[i] = false;
for (int i = 0; i < G.numVertices; i++)
if (!visited[i]) DFS_M(G, i); // 无向图里,这一行执行几次就有几个连通分量
}邻接表版
void DFS_L(ALGraph &G, int v) {
printf("%d ", v);
visited[v] = true;
for (ArcNode *p = G.vertices[v].firstarc; p != NULL; p = p->nextarc) // 沿边表逐条看 v 的边
if (!visited[p->adjvex]) DFS_L(G, p->adjvex);
}
void BFS_L(ALGraph &G, int v) {
int q[MAXV], front = 0, rear = 0;
printf("%d ", v);
visited[v] = true;
q[rear++] = v;
while (front < rear) {
int u = q[front++];
for (ArcNode *p = G.vertices[u].firstarc; p != NULL; p = p->nextarc) {
int w = p->adjvex; // 这条边指向的顶点
if (!visited[w]) {
printf("%d ", w);
visited[w] = true;
q[rear++] = w;
}
}
}
}复杂度(
| 邻接矩阵 | 邻接表 | |
|---|---|---|
| 时间 | ||
| 空间 | visited 加上递归栈或队列 |
关键边界
| 位置 | 为什么 |
|---|---|
| BFS 在入队时标记 | 如果出队时才标记,一个顶点可能被好几个邻居重复入队 |
每次遍历前把 visited 清零 | 它是全局数组,不清零的话第二次遍历什么都访问不到 |
DFSTraverse 的外层循环 | 非连通图从一个顶点出发走不完所有顶点 |
| 遍历序列是否唯一 | 邻接矩阵版按下标顺序找邻接点,序列唯一;邻接表版取决于边表里的顺序,所以教材说「基于邻接表的遍历序列不唯一」 |
易错点
- 这里的矩阵是 0/1 矩阵,有边写成
G.Edge[v][w] != 0。换成带权矩阵(没有边存INF,对角线为 0)时,要改成w != v && G.Edge[v][w] < INF。 - 教材的 BFS / DFS 伪代码用
FirstNeighbor/NextNeighbor找邻接点,以此和存储结构解耦。考场上直接写成上面的循环,更清楚。