1.图论基础
图论的基础概念:
- 图(Graph)
图 G=(V,E)G = (V, E)G=(V,E) 由两个元素组成:
- 顶点集 VVV:图中的节点,通常用字母或数字表示。
- 边集 EEE:表示顶点之间的连接关系,边通常由一对顶点表示,形式为 (u,v)(u, v)(u,v) 或 (u,v)(u, v)(u,v) (对于有向图)或者 {u,v}{u, v}{u,v} (对于无向图)。
- 有向图与无向图
- 无向图(Undirected Graph):边没有方向,边是两个顶点之间的连接。例如,边 (u,v)(u, v)(u,v) 和 (v,u)(v, u)(v,u) 是同一条边。
- 有向图(Directed Graph 或 Digraph):边具有方向,边从一个顶点指向另一个顶点。例如,边 (u,v)(u, v)(u,v) 表示从 uuu 到 vvv,而边 (v,u)(v, u)(v,u) 表示从 vvv 到 uuu 是不同的边。
- 图的类型
- 完全图(Complete Graph):一个无向图,其中任意两个不同的顶点都有一条边相连。完全图通常用 KnK_nKn 表示,其中 nnn 是顶点数。
- 树(Tree):一个无向图,且是连通的,没有环(即每对顶点之间只有一条简单路径)。
- 二分图(Bipartite Graph):图的顶点可以分为两个独立的集合 UUU 和 VVV,且每一条边都连接一个 UUU 中的顶点和一个 VVV 中的顶点。
- 度(Degree)
- 顶点的度(Degree of a Vertex):一个顶点的度是与该顶点相连的边的数量。在有向图中,顶点的度分为两类:
- 入度(In-degree):指向该顶点的边的数量。
- 出度(Out-degree):从该顶点出发的边的数量。
- 路径(Path)
- 简单路径(Simple Path):一条不包含重复顶点的路径。
- 环(Cycle):一个路径的起点和终点相同,且路径中包含至少一个边。
- 连通性
- 连通图(Connected Graph):一个图是连通的,如果任意两个顶点之间都有路径相连。
- 强连通(Strongly Connected):在有向图中,如果从任意顶点出发都可以到达其他任何顶点,则该图是强连通的。
- 图的遍历
- 深度优先遍历(DFS, Depth-First Search):从一个起始顶点出发,沿着一条路径走到底,直到没有未访问的邻接点,再回溯。
- 广度优先遍历(BFS, Breadth-First Search):从起始顶点开始,访问所有相邻的顶点,然后依次访问这些顶点的相邻顶点,直到所有顶点都被访问。
- 图的表示方法
- 邻接矩阵(Adjacency Matrix):一个二维数组,表示顶点之间的边。对于无向图,矩阵是对称的。
- 邻接表(Adjacency List):对于每个顶点,列出与之相邻的顶点的列表。
2.深度优先搜索理论基础
dfs是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯)。
void dfs(参数) {
处理节点
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
dfs的代码框架:
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
一般情况,深搜需要 二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局变量,避免让我们的函数参数过多。
3.所有可达路径
题目描述
给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个函数,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。
输入描述
第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边
后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t 节点中有一条路径
输出描述
输出所有的可达路径,路径中所有节点之间空格隔开,每条路径独占一行,存在多条路径,路径输出的顺序可任意。如果不存在任何一条路径,则输出 -1。
注意输出的序列中,最后一个节点后面没有空格! 例如正确的答案是 1 3 5
,而不是 1 3 5
, 5后面没有空格!
输入示例
5 5
1 3
3 5
1 2
2 4
4 5
输出示例
1 3 5
1 2 4 5
提示信息
数据范围:
图中不存在自环
图中不存在平行边
1 <= N <= 100
1 <= M <= 500
思路:
我们将图表示为一个邻接矩阵,矩阵中的 graph[i][j] = 1
表示节点 i
和节点 j
之间有一条有向边。然后从节点 1
开始,通过 DFS 遍历所有可能的路径,每次递归时我们将当前节点添加到路径中。如果到达目标节点 n
,则记录下这条路径。每条路径的记录都通过一个 path
数组来实现,这个数组动态存储当前路径的节点编号。每次递归返回时,我们将最后加入的节点从路径中移除,这样就完成了回溯。最终,当所有可能的路径都被探索完毕后,如果找到了路径,就输出每条路径。如果没有路径存在,则输出 -1
。
解答:
#include <stdio.h>
#include <stdlib.h>
#define MAX_N 100
int** result = NULL;
int rows = 0;
int* cols = NULL;
int* path = NULL;
int path_length = 0;
void dfs(int graph[MAX_N + 1][MAX_N + 1], int x, int n) {
if (x == n) {
int* new_path = (int*)malloc(path_length * sizeof(int));
for (int i = 0; i < path_length; i++) {
new_path[i] = path[i];
}
result[rows] = new_path;
cols[rows++] = path_length;
return;
}
for (int i = 1; i <= n; i++) {
if (graph[x][i] == 1) {
path[path_length++] = i;
dfs(graph, i, n);
path_length--;
}
}
}
int main() {
int n, m, s, t;
scanf("%d %d", &n, &m);
int graph[MAX_N + 1][MAX_N + 1] = { 0 };
cols = malloc(MAX_N * MAX_N * sizeof(int));
result = malloc(MAX_N * MAX_N * sizeof(int*));
for (int i = 0; i < m; i++) {
scanf("%d %d", &s, &t);
graph[s][t] = 1;
}
path = (int*)malloc((MAX_N + 1) * sizeof(int));
path_length = 0;
path[path_length++] = 1;
dfs(graph, 1, n);
if (rows == 0) {
printf("-1\n");
}
else {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols[i] - 1; j++) {
printf("%d ", result[i][j]);
}
printf("%d\n", result[i][cols[i] - 1]);
free(result[i]);
}
}
free(result);
free(path);
free(cols);
return 0;
}
4.广度优先搜索理论基础
广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。
用队列的话,就是保证每一圈都是一个方向去转,例如统一顺时针或者逆时针。
如果用栈的话,就是第一圈顺时针遍历,第二圈逆时针遍历,第三圈有顺时针遍历。