当前位置: 首页 > news >正文

图的存储、DFS、BFS

图的存储方式

一、邻接矩阵(适合稠密图)

结构:二维数组 matrix[V][V]matrix[i][j] 表示顶点 i 到 j 的边权(无边则为∞或 0)。
优点

  • 查询两点是否连通 O(1)(如判断 A→B 是否有边)。
  • 适合矩阵运算(如 Floyd 算法)。
    缺点
  • 空间复杂度 O(V²),顶点数 V>1e4 时内存爆炸(如 1e4 顶点需 100MB,1e5 顶点需 10GB)

二、邻接表(适合稀疏图,最常用)

结构:数组 / 字典存储每个顶点的邻接边,如 adj[i] = [(j, w), (k, w)]
优点

  • 空间复杂度 O(V+E),稀疏图(如 E<<V²)内存友好。
  • 支持动态增删边(如添加顶点 4→2,直接 append)。
    缺点
  • 查询两点是否连通需遍历邻接表,时间 O (度数)
1.邻接表存储单向边(使用vector便于排序)

2.邻接表存储双向边(使用vector便于排序)

tips:在存储树时,可用fa[i]来表示,树的每个节点只有一个父亲,只用给出每一个节点对应的父节点就可以了

DFS(深度优先搜索)

深度优先搜索(Depth - First Search,DFS)是一种用于遍历或搜索图或树的算法。以下是对深度优先搜索的详细介绍:

算法原理

  • 从起始顶点开始,沿着一条路径尽可能深地探索,直到无法继续或达到目标节点。
  • 然后回溯到前一步,继续探索其他未访问过的分支,直到所有顶点都被访问过或找到目标。

算法特点

  • 优点:对于某些问题,如寻找图中的连通分量、检测环等,深度优先搜索具有简洁高效的特点。它可以快速地深入探索图的结构,找到一条从起始节点到目标节点的路径,即使这条路径不是最短路径。
  • 缺点:由于它是深度优先探索,可能会陷入一条很长的路径而无法及时找到最优解,特别是在处理无权图或需要寻找最短路径的问题时。此外,递归实现可能会受到栈空间的限制,导致程序崩溃。

                                总结:深度优先搜索是一条路走到黑,不撞南墙不回头

BFS(广度优先搜索)

广度优先遍历(Breadth - First Search,BFS)是一种用于遍历图或树的算法,以下是其详细介绍:

算法原理

  • 从起始顶点开始,先访问起始顶点的所有邻接顶点,然后再依次访问这些邻接顶点的邻接顶点,以此类推,直到所有顶点都被访问过。
  • 就像在一层一层地向外扩展,先访问完一层的所有节点,再进入下一层。

算法特点

  • 优点:能保证找到从起始顶点到目标顶点的最短路径(在无权图中),因为它是按照层次依次访问节点的。
  • 缺点:需要更多的空间来存储队列中的节点,尤其是在处理大型图时,空间消耗可能较大。而且对于某些问题,可能会遍历大量不必要的节点,效率不如深度优先搜索。

总结:广度优先搜索是剥洋葱,一层一层地剥开你的心

题目

题目1:树的遍历

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=50;
int fa[N];//用于记录每个节点的父节点
vector<int>g[N];//邻接表
void dfs(int x)//深搜
{
    cout<<x<<' ';
    for(auto &y:g[x])dfs(y);//递归遍历树
}
void bfs(int rt)//广搜
{
    queue<int>q;//创建队列
    q.push(rt);//将根节点入队
    while(q.size())
    {
       int x=q.front();q.pop();用变量x接收对头元素,然后让队头元素出队
       cout<<x<<' ';//打印队头元素
       for(auto &y:g[x])q.push(y);//遍历每个根节点的子节点,让子节点入队
    }
}
int main()
{
    int n;cin>>n;
    for(int i=2;i<=n;i++)cin>>fa[i];//输入父节点
    for(int i=2;i<=n;i++)g[fa[i]].push_back(i);用邻接表存储父节点的每一个子节点
    for(int i=2;i<=n;i++)sort(g[fa[i]].begin(),g[fa[i]].end());//对每一个父节点下属的子节点排序
    dfs(1);
    cout<<"\n";
    bfs(1);
    return 0;
}

题目2:图的遍历

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+9;
vector<int>g[N];//邻接表
bitset<N>vis;//类似于bool数组,用于判断是否出现自环现象
void dfs(int x)
{
    vis[x]=true;//走过的点都记作true
    for(auto &y:g[x])
    {
        if(vis[y])continue;//如果这个点已经走过了,那么就跳过这个点进行下一个循环
        dfs(y);
    }
}
void bfs(int rt)
{
    queue<int>q;
    q.push(rt);
    while(q.size())
    {
        int x=q.front();q.pop();
        vis[x]=true;//走过的点记为true
        for(auto &y:g[x])
        {
            if(vis[y])continue;//如果这个点走过了,就跳过这个点进入下一个循环
            q.push(y);
        }
    }
}
int main()
{
    int n,m;cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v;cin>>u>>v;
        if(u!=v)g[u].push_back(v);//用邻接表存储边的关系
    }
    dfs(1);
    for(int i=1;i<=n;i++)
    {
        if(vis[i])cout<<i<<' ';//类似于桶排序,走过的点都会按顺序输出出来
    }
    return 0;
}

题目3:全排列

#include<bits/stdc++.h>
using namespace std;
const int N=15;
int n;
int a[N];
bitset<N>vis;
void dfs(int dep)
{
   if(dep==n+1)//当 dep 等于 n + 1 时,表示已经生成了一个完整的长度为 n 的排列。此时将排列输出,然后返回
   {
      for(int i=1;i<=n;i++)
      {
         cout<<a[i]<<' ';
      }
      cout<<'\n';
      return;
   }
   for(int i=1;i<=n;i++)
   {
     if(vis[i])continue;//如果这个数被标记过,则跳过这次循环
     vis[i]=true;//将这个数标记
     a[dep]=i;//将数字放到a数组中的第dep层
     dfs(dep+1);//递归调用dfs
     //恢复现场
     a[dep]=0;//将层数置为零
     vis[i]=false;//数字设为false未被标记,方便尝试其他排列
   }
}

int main()
{
   cin>>n;
   dfs(1);
   return 0;
}

题目4:迷宫问题

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e3+9;
int mp[N][N];//表示地图
int d[N][N];//表示花费时间
int dx[]={0,0,1,-1};//x轴偏移量
int dy[]={1,-1,0,0};//y轴偏移量
bitset<N>vis[N];//vis数组判断某个点是否已经走过
int n,m;
bool imp(int nx,int ny)//判断下一步走的是否合理(是否在地图里边)
{
    return nx>=1&&nx<=n&&ny>=1&&ny<=m;
}
void bfs(int sx,int sy)//广度优先搜索
{
    queue<pair<int,int>>q;
    q.push({sx,sy});
    while(q.size())
    {
        int x=q.front().first;int y=q.front().second;q.pop();
        for(int i=0;i<4;i++)//每秒可能往四个方向走
        {
           int nx=x+dx[i];int ny=y+dy[i];//坐标=当前位置+偏移量
           if(imp(nx,ny)&&!vis[nx][ny]&&!mp[nx][ny])//同时满足在地图里,这个点没被走过,地图还有容量
           {
            d[nx][ny]=d[x][y]+1;//让时间加一
            vis[nx][ny]=true;//走过的点标记为true
            q.push({nx,ny});//让点继续入队
           }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
       for(int j=1;j<=m;j++)cin>>mp[i][j];
    memset(d,0x3f,sizeof d);//把地图上没走过的点都设置为无穷大
    d[1][1]=0;//初始位置的时间是0
    bfs(1,1);//调用bfs
    if(!vis[n][m])cout<<-1<<'\n';//如果终点没被走,说明是死路一条,输出-1
    else cout<<d[n][m]<<'\n';//负责输出时间
    return 0;
}

相关文章:

  • 基于YOLO11深度学习的舌苔舌象检测识别与诊断系统【python源码+Pyqt5界面+数据集+训练代码】
  • Unity 和 Python 的连接(通过SocketIO)附源码
  • 【栈数据结构应用解析:常见算法题详细解答】—— Leetcode
  • 【春招笔试】2025.03.13-蚂蚁春招笔试题
  • 933. 最近的请求次数
  • 10个数据收集相关DeepSeek提示词
  • 机器学习神经网络中的损失函数表达的是什么意思
  • 基于SpringBoot + Vue 的房屋租赁系统
  • Java---JavaSpringMVC解析(1)
  • 【eNSP实战】配置NAPT(含动态NAT)
  • Java入职篇(2)——开发流程以及专业术语
  • 【C++项目】从零实现RPC框架「二」:项⽬设计
  • C++之文字修仙小游戏
  • 原理-事件循环
  • 数据炼丹与硬件互动:预测湿度的武学之道
  • 数据结构--图的基本操作
  • 系统稳定性建设
  • WVP前后端部署
  • WebSocket 使用教程:从原理到实践
  • 硬件驱动——51单片机:独立按键、中断、定时器/计数器
  • 十大券商看后市|A股指数有望进一步缓步推高,淡化短期波动
  • 宫崎骏的折返点
  • 央媒:设施老化、应急预案套模板,养老机构消防隐患亟待排查
  • 波兰总统选举投票开始,将是对亲欧路线的一次严峻考验
  • 中国旅马大熊猫“福娃”和“凤仪”启程回国
  • 美联储官员:美国经济增速可能放缓,现行关税政策仍将导致物价上涨