代码随想录Day43图:图论理论基础_深搜理论基础_98所有可达路径_广搜理论基础
图这章都用ACM模式
图论理论基础
有向图无向图,出度入度
强连通图:在有向图中,任何两个节点是可以相互到达的。
连通分量:在无向图中的极大连通子图。
强连通分量:在有向图中极大强连通子图。
图的存储:朴素存储(将所有边存下来),邻接矩阵,邻接表。
图的遍历:深度优先dfs,广度优先bfs
深搜理论基础
dfs关键就两点:搜索方向,是认准一个方向搜,直到碰壁之后再换方向。换方向是撤销原路径,改为节点链接的下一个路径,回溯的过程。
java
//dfs代码模板:
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
深度搜索三部曲:
确定递归函数;确认终止条件;处理目前搜索节点出发的路径。
98所有可达路径
题目:给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个程序,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。
输入描述:第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边。后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t 节点中有一条路径
输出描述:输出所有的可达路径,路径中所有节点的后面跟一个空格,每条路径独占一行,存在多条路径,路径输出的顺序可任意。如果不存在任何一条路径,则输出 -1。注意输出的序列中,最后一个节点后面没有空格。
链接:https://kamacoder.com/problempage.php?pid=1170
图的存储
用邻接矩阵存 :有n 个节点,节点标号从1开始,为了节点标号和下标对齐,申请 n + 1 * n + 1大的二维数组。
用邻接表存:构造一个数组,数组里的元素是一个链表。
dfs三部曲
1 确定递归函数参数
首先要存一个图用来遍历。其次是当前遍历的节点。最后还需要存一个n,表示终点,我们遍历的时候,用来判断当 x==n 时候表明找到了终点。
2 确认终止条件
当目前遍历的节点为最后一个节点 n 的时候 就找到了一条从出发点到终止点的路径。
3 处理目前搜索节点出发的路径
走当前遍历节点x的下一个节点:
java
for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
if (graph[x][i] == 1) { // 找到 x指向的节点,就是节点i
}
}
然后将x的下一节点加入到路径中来
path.push_back(i); // 遍历到的节点加入到路径中来
进入下一层递归
dfs(graph, i, n);
最后是回溯,撤销本次添加节点的操作。
用邻接矩阵存储写的:
java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main{
static List<List<Integer>> result=new ArrayList<>();
static List<Integer> path=new ArrayList<>();
public static void dfs(int[][] graph, int x, int n){
if(x==n){
result.add(new ArrayList<>(path));
return;
}
for(int i=1;i<=n;i++){
if(graph[x][i]==1){
path.add(i);
dfs(graph,i,n);
path.remove(path.size()-1);
}
}
}
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt(); //节点
int m=scanner.nextInt(); //边
int[][] graph=new int[n+1][n+1];
for(int i=0;i<m;i++){ //存储图,用邻接矩阵
int s=scanner.nextInt();
int t=scanner.nextInt();
graph[s][t]=1;
}
path.add(1); // 无论什么路径已经是从1节点出发
dfs(graph, 1, n); //开始dfs
if(result.isEmpty()) System.out.println(-1);
for(List<Integer> pa:result){
for(int i=0;i<pa.size()-1;i++){
System.out.print(pa.get(i)+" ");
}
System.out.println(pa.get(pa.size()-1)); //输出pa的最后一个节点
}
}
}
用邻接表存储写的:
java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
public class Main{
static List<List<Integer>> result=new ArrayList<>();
static List<Integer> path=new ArrayList<>();
public static void dfs(List<LinkedList<Integer>> graph, int x, int n){
if(x==n){
result.add(new ArrayList<>(path));
return;
}
for(int i:graph.get(x)){
path.add(i);
dfs(graph,i,n);
path.remove(path.size()-1);
}
}
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt(); //节点
int m=scanner.nextInt(); //边
List<LinkedList<Integer>> graph=new ArrayList<>();
for(int i=0;i<=n;i++){ //存储图,用邻接表,每个节点是链表
graph.add(new LinkedList<>());
}
while(m-->0){
int s=scanner.nextInt();
int t=scanner.nextInt();
graph.get(s).add(t);
}
path.add(1); // 无论什么路径已经是从1节点出发
dfs(graph, 1, n); //开始dfs
if(result.isEmpty()) System.out.println(-1);
for(List<Integer> pa:result){
for(int i=0;i<pa.size()-1;i++){
System.out.print(pa.get(i)+" ");
}
System.out.println(pa.get(pa.size()-1)); //输出pa的最后一个节点
}
}
}
广搜理论基础
使用场景?解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。
广度优先遍历怎么实现?
用队列的话,就是保证每一圈都是一个方向去转,例如统一顺时针或者逆时针。因为队列是先进先出,加入元素和弹出元素的顺序是没有改变的。
如果用栈的话,就是第一圈顺时针遍历,第二圈逆时针遍历,第三圈有顺时针遍历。因为栈是先进后出,加入元素和弹出元素的顺序改变了。
上述四方格的代码:

java
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 表示四个方向
// grid 是地图,也就是一个二维数组
// visited标记访问过的节点,不要重复访问
// x,y 表示开始搜索节点的下标
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
queue<pair<int, int>> que; // 定义队列
que.push({x, y}); // 起始节点加入队列
visited[x][y] = true; // 只要加入队列,立刻标记为访问过的节点
while(!que.empty()) { // 开始遍历队列里的元素
pair<int ,int> cur = que.front(); que.pop(); // 从队列取元素
int curx = cur.first;
int cury = cur.second; // 当前节点坐标
for (int i = 0; i < 4; i++) { // 开始想当前节点的四个方向左右上下去遍历
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1]; // 获取周边四个方向的坐标
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 坐标越界了,直接跳过
if (!visited[nextx][nexty]) { // 如果节点没被访问过
que.push({nextx, nexty}); // 队列添加该节点为下一轮要遍历的节点
visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
}
}
}
}