代码随想录算法训练营第五十五天|图论理论基础、深搜理论基础、98. 所有可达路径、广搜理论基础

图论理论基础

完整内容可参考:代码随想录|图论基础

这里关于图的基本概念就不说了,主要记录一下关于如何表示图,主要有两种方式,一种是邻接矩阵,一种是邻接表。邻接矩阵 使用 二维数组来表示图结构。 邻接矩阵是从节点的角度来表示图,有多少节点就申请多大的二维数组。

邻接矩阵的优点:

  • 表达方式简单,易于理解
  • 检查任意两个顶点间是否存在边的操作非常快
  • 适合稠密图,在边数接近顶点数平方的图中,邻接矩阵是一种空间效率较高的表示方法。

缺点:

  • 遇到稀疏图,会导致申请过大的二维数组造成空间浪费 且遍历 边 的时候需要遍历整个n * n矩阵,造成时间浪费

例如: grid[2][5] = 6,表示 节点 2 连接 节点5 为有向图,节点2 指向 节点5,边的权值为6。

邻接表使用数组+链表的方式来表示。邻接表是从边的数量来表示图。有多少边才会申请对应大小的链表。

邻接表的优点:

  • 对于稀疏图的存储,只需要存储边,空间利用率高
  • 遍历节点连接情况相对容易

缺点:

  • 检查任意两个节点间是否存在边,效率相对低,需要 O(V)时间,V表示某节点连接其他节点的数量。
  • 实现相对复杂,不易理解。

深搜理论基础

完整内容可参考**:代码随想录|深度优先搜索理论基础**

dfs关键就两点:

  • 搜索方向,是认准一个方向搜,直到碰壁之后再换方向
  • 换方向是撤销原路径,改为节点链接的下一个路径,回溯的过程。

dfs的代码框架:

cpp 复制代码
void dfs(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本节点所连接的其他节点) {
        处理节点;
        dfs(图,选择的节点); // 递归
        回溯,撤销处理结果
    }
}

跟回溯板块差不多,深搜也有三部曲:确认递归函数、确认终止条件、处理目前搜索节点出发的路径。

98.可达路径

思路:主要难点在判断终止条件和递归思路,递归函数定义为:

复制代码
void dfs (const vector<vector<int>>& graph, int x, int n)

当x==n时,说明已经找到了一条路径,此时需要将path加入到结果数组。

每层的递归思路就是先判断当前节点是不是可连通的,如果可以就说明可以从这个节点接着往下搜索,用dfs函数递归继续搜索下去,最后记得回溯回去。这题是acm模式,所以是有打印数组的要求的,这里要注意最后一个数的后面是没有空格的,在写代码的时候需要特殊处理最后一个结果数组的元素。

我的代码:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result;
vector<int> path;
void dfs(const vector<vector<int>> &graph,int x,int n) {
    if(x==n) {
        result.push_back(path);
        return ;
    }
    for(int i=1;i<=n;i++) {
        if(graph[x][i]==1) {
            path.push_back(i);
            dfs(graph,i,n);
            path.pop_back();
        }
    }
}

int main() {
    int n,m,s,t;
    cin>>n>>m;
    vector<vector<int>> graph(n+1,vector<int>(n+1,0));
    while(m--) {
        cin>>s>>t;
        graph[s][t]=1;
    }
    path.push_back(1);
    dfs(graph,1,n);
    if(result.size()==0) cout<<-1<<endl;
    for(auto pa:result) {
        for(int i=0;i<pa.size()-1;i++) {
            cout<<pa[i]<<" ";
        }
        cout<<pa[pa.size()-1]<<endl;
    }
    return 0;
}

广度优先搜索理论基础

完整版可参考:代码随想录|广度优先搜索理论基础

广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。当然,也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,**这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行。**对于广度优先搜索,用队列,还是用栈,甚至用数组,都是可以的。用队列的话,就是保证每一圈都是一个方向去转,例如统一顺时针或者逆时针。

因为队列是先进先出,加入元素和弹出元素的顺序是没有改变的。如果用栈的话,就是第一圈顺时针遍历,第二圈逆时针遍历,第三圈有顺时针遍历。因为栈是先进后出,加入元素和弹出元素的顺序改变了。而在这里顺序是无关紧要的。

下面给出广搜代码模板:

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

} //(by 代码随想录)

 

今日总结

今天的图论基础在学过回溯下比较好接受,不过图论也是一个大板块,好好理解今天的理论内容才能在后面做难题更的心顺手,特别要注意邻接矩阵和邻接表两种表示图的方式,链表的方式相对来说不好理解一点,但是看了示例代码其实感觉在写法上貌似没有很大差别。继续加油吧,再坚持十几天就结束了!

相关推荐
努力学习的小廉2 小时前
我爱学算法之——floodfill算法(下)
学习·算法
2401_851272992 小时前
编译器内建函数使用
开发语言·c++·算法
Book思议-2 小时前
【数据结构实战】C 语言实现静态顺序队列:从原理到完整可运行代码
c语言·数据结构·算法·队列
hanlin032 小时前
刷题笔记:力扣第6题-Z字形变换
笔记·算法·leetcode
努力学习的小廉2 小时前
我爱学算法之——记忆化搜索
算法
m0_730115112 小时前
C++与Python混合编程实战
开发语言·c++·算法
郝学胜-神的一滴4 小时前
Leetcode 969 煎饼排序✨:翻转间的数组排序艺术
数据结构·c++·算法·leetcode·面试
炸膛坦客11 小时前
单片机/C/C++八股:(二十)指针常量和常量指针
c语言·开发语言·c++
I_LPL11 小时前
hot100贪心专题
数据结构·算法·leetcode·贪心