代码随想录Day43图:图论理论基础_深搜理论基础_98所有可达路径_广搜理论基础

代码随想录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; // 只要加入队列立刻标记,避免重复访问
            }
        }
    }

}
相关推荐
WW_千谷山4_sch1 天前
MYOJ_7789:(洛谷P3388)【模板】割点(割顶)(tarjan算法)
c++·算法·深度优先·图论
WW_千谷山4_sch1 天前
MYOJ_11705:(洛谷P1137)旅行计划(经典拓扑排序)
c++·算法·动态规划·图论
yyy(十一月限定版)2 天前
图论——最小生成树Kruskal算法
算法·图论
yyy(十一月限定版)2 天前
图论——最短路Dijkstra算法
算法·图论
-海绵东东-2 天前
图论——代码篇
算法·深度优先·图论
十八岁讨厌编程2 天前
【算法训练营 · 二刷总结篇】贪心算法、图论部分
算法·贪心算法·图论
WW_千谷山4_sch2 天前
MYOJ_7788:(洛谷P3387)【模板】缩点(有关强连通分量)
c++·算法·深度优先·动态规划·图论·拓扑学
yyjtx3 天前
DHU上机打卡D27
c++·算法·图论
漂流瓶jz3 天前
UVA-12569 树上的机器人规划(简单版) 题解答案代码 算法竞赛入门经典第二版
算法·图论·dfs·bfs·uva·算法竞赛入门经典第二版·11214