代码随想录第50天 | 图论 基础介绍(新篇章

图论基础

图论理论基础

图的基础介绍

  1. 图分为有向图无向图,有向指连接俩个节点的边是有方向的,而无向图则是没有方向的

  2. 同时图还可以分成加权有向图加权无向图。意思是在有向图和无向图的基础上把边进行赋值(每条边上面都有一个数值代表其权值)

  3. 图的性质

  4. 度:对于无向图而言,每个节点所连接的边数就是度

  5. 入度和出度:对于有向图而言,从某节点出发的边的数量是出度,而指向该节点的边数量是入度

图的连通性

什么是连通图?------任意两个节点都是可以到达的(用于无向图连通性的描述)

什么是强连通图?------任意两个节点都是可以相互到达的(用于描述有向图)

连通分量

  1. 无向图中,极大连通 子图称之为该图的一个连通分量(判断是否是连通分量,重点在于判断1.是否是极大图 ,2. 是否是连通子图)

  2. 有向图中,极大****强连通 子图称之为该图的连通分量(则判断是否为强连通分量,重点在于判断1. 是否为极大图,2. 是否为强连通子图)

图的构造

  1. 朴素存储:存储边到数组中。存储所有边,一定是要通过节点来存储,两个节点一个边。最大的优点就是直观。

  2. 邻接矩阵:从节点的角度来表示图,存储边的两个节点到二维数组中,例如grid[2][3]=6表示节点2和节点3之间有个边,边的权值为6

优点:适合稠密图(节点和边数差不多),找任意两个节点之间是否存在边十分便利。

缺点:不适合稀疏图,会大大的浪费空间n*n(n表示节点数),遍历 边 的时候需要遍历整个n * n矩阵

  1. 邻接表:从边的数量为基准来创建链表和数组。邻接表是有一个数组,数组存储的是链表元素,该元素后面跟着的是与其连接的节点。

优点:适合稀疏图,只用存储边,空间利用率高。遍历边十分方便

缺点: 但是找任意两个节点间是否存在边十分麻烦。

图的遍历方式

  1. 广度优先遍历

广度的意思是:先把本节点连接的所有节点遍历一遍,再走到下一个节点,进行同样的操作

  1. 深度优先遍历

深度是:一直朝一个方向去搜索

这两个其实非常像二叉树的遍历方式,层序遍历和递归遍历(前中后序)

深搜

深搜理论基础

例如1 和 6 两条节点之间有这样4条路分别是:

  • 1-5-6
  • 1-5-4-6
  • 1-5-4-3-6
  • 1-4-6
  1. 假设我们先走第一条路,则到达目的地后往前回溯,取消5-6之间的路,去尝试走5-4,4再到6
  2. 现在又到达目的地了,则往前回溯,4不走6,而是先走3,3再到达6
  3. 到达目的地,回溯,不走1-5,而是走1-4,4再到达6
  4. 到达目的地,当然我们在回溯再选择路径时,会有碰壁的情况------则一碰壁就需要换方向

🆗观察上面过程发现,整个遍历过程是有规律的:

  • 搜索方向,是先认准一个方向搜,直到碰壁之后或者到达目的地之后再换方向(撤销原先的路径,改成该节点链接的下一个路径方向(回溯)
代码框架

回溯三部曲:

  1. 确立递归条件和参数

例如:

cpp 复制代码
vector<vector<int>> result; // 保存符合条件的所有路径
vector<int> path; // 起点到终点的路径
void dfs (图,目前搜索的节点)  
  1. 确立递归的结束条件
cpp 复制代码
if (终止条件) {
    存放结果;
    return;
}
  1. 处理目前搜索节点出发的路径
cpp 复制代码
for (选择:本节点所连接的其他节点) {
    处理节点;
    dfs(图,选择的节点); // 递归
    撤销处理结果;//回溯
}
题目1:98. 所有可达路径

98. 所有可达路径

图的存储方式:邻接表 和 邻接矩阵,用这题来进行完善存储方式的代码:
  1. 邻接矩阵:之前说过是根据节点的角度来存储的,则邻接矩阵申请空间也与节点数量息息相关:节点多大数量,矩阵的空间就多大(二维)
cpp 复制代码
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
cpp 复制代码
while (m--) {
    cin >> s >> t;
    graph[s][t] = 1; //1 表示节点s指向节点t
}
  1. 邻接表:一个数组,一个链表(存储在数组中)。大小和边数有关:
cpp 复制代码
vector<list<int>> graph(n + 1);//list是链表

⭐: 节点编号从1到n,所以申请 n+1 这么大的数组

cpp 复制代码
while (m--) {
    cin >> s >> t;
    graph[s].push_back(t);//表示s -> t
}

深度遍历三部曲分析:

  1. 返回值和参数:由于最终是要记录路径数,则存储到res即可,返回值void,参数为图(邻接矩阵)和当前遍历的节点,以及终点

  2. 结束条件:若最终当前遍历的节点==终点,(结束条件不用写无法到达终点的路径,在处理当前遍历的节点这种情况就已经被排查了,不可能到达终点也不可能是答案路径)

  3. 处理目前搜索到的节点:当前节点往下走,则需要先判断是否为1(groups邻接矩阵的值),x到i(i是从1遍历到n)

邻接矩阵代码:
cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;


vector<vector<int>> res;
vector<int> v;
void f(vector<vector<int>>& group,int x,int n){
    if(x==n){
        res.push_back(v);
        return;
    }

    for(int i=1;i<=n;i++){
        if(group[x][i]==1){
            v.push_back(i);
            f(group,i,n);
            v.pop_back();
        }
    }
}

int main(){
    int n,m;
    cin>>n>>m;
    vector<vector<int>> group(n+1,vector<int>(n+1,0));
    int s,t;
    while(m--){
        cin>>s>>t;
        group[s][t]=1;
    }
    v.push_back(1);
    f(group,1,n);
    
    if (res.size() == 0) cout<<-1<<endl;
    for(auto& it:res){
        for(int i=0;i<it.size()-1;i++){
            cout<<it[i]<<" ";
        } cout << it.back()<< endl;
    }
    return 0;
}
邻接表代码:
cpp 复制代码
#include<iostream>
#include<vector>
#include<list>
using namespace std;

vector<vector<int>> res;
vector<int> v;
void f(vector<list<int>>& graph,int x,int n){
    if(x==n){
        res.push_back(v);
        return;
    }

    for(int i : graph[x]){//遍历x连接的节点
            v.push_back(i);
            f(graph,i,n);
            v.pop_back();
    }
}

int main(){
    int n,m;
    cin>>n>>m;
    vector<list<int>> graph(n+1);
    int s,t;
    while(m--){
        cin>>s>>t;
        graph[s].push_back(t);
    }

    v.push_back(1);
    f(graph,1,n);
    
    if (res.size() == 0) cout<<-1<<endl;
    for(auto& it:res){
        for(int i=0;i<it.size()-1;i++){
            cout<<it[i]<<" ";
        } cout << it.back()<< endl;
    }
    return 0;
}

两个代码主要的区别在于1. 构建函数 2. 遍历连接x的节点

广搜

广搜理论基础

广搜------适合于解决两个点之间的最短路径问题

一圈一圈遍历,使用栈和队列都行。(常用队列)

cpp 复制代码
#include <vector>
#include <queue>
using namespace std;

// 定义四个方向:右、下、上、左
int directions[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

// BFS遍历网格
// grid: 二维网格地图
// visited: 访问标记数组
// start_x, start_y: 起始坐标
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int start_x, int start_y) {
    queue<pair<int, int>> q;  // 创建队列
    q.push({start_x, start_y});  // 起点入队
    visited[start_x][start_y] = true;  // 标记已访问
    
    while (!q.empty()) {
        // 取出队首元素
        auto current = q.front();
        q.pop();
        int current_x = current.first;
        int current_y = current.second;
        
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int next_x = current_x + directions[i][0];
            int next_y = current_y + directions[i][1];
            
            // 检查边界
            if (next_x < 0 || next_x >= grid.size() || 
                next_y < 0 || next_y >= grid[0].size()) {
                continue;
            }
            
            // 检查是否未访问
            if (!visited[next_x][next_y]) {
                q.push({next_x, next_y});
                visited[next_x][next_y] = true;
            }
        }
    }
}
相关推荐
草莓熊Lotso5 小时前
《算法闯关指南:优选算法--前缀和》--27.寻找数组的中心下标,28.除自身以外数组的乘积
开发语言·c++·算法·rpc
七夜zippoe5 小时前
仓颉语言核心特性详解:类型系统与内存安全
人工智能·算法·鸿蒙·仓颉·核心实践
星空露珠5 小时前
数独生成题目lua脚本
数据结构·数据库·算法·游戏·lua
hadage2336 小时前
--- 单源BFS权值为一算法 迷宫中离入口最近的出口 ---
算法·宽度优先
LDG_AGI6 小时前
【推荐系统】深度学习训练框架(一):深入剖析Spark集群计算中Master与Pytorch分布式计算Master的区别
人工智能·深度学习·算法·机器学习·spark
LDG_AGI6 小时前
【推荐系统】深度学习训练框架(二):深入剖析Spark Cluster模式下DDP网络配置解析
大数据·网络·人工智能·深度学习·算法·机器学习·spark
ai智能获客_狐狐6 小时前
电商零售行业外呼优势
人工智能·算法·自然语言处理·语音识别·零售
Giser探索家11 小时前
无人机桥梁巡检:以“空天地”智慧之力守护交通生命线
大数据·人工智能·算法·安全·架构·无人机
budingxiaomoli14 小时前
算法--滑动窗口(二)
算法