深入剖析广度优先搜索算法(BFS)和深度优先搜索算法(DFS)

本文将通过分析一个典型的搜索问题,阐述广度优先搜索算法(BFS)和深度优先搜索算法(DFS)的基本实现方法。

导言

BFS(广度优先搜索)和DFS(深度优先搜索)是图或树遍历中的两种核心算法,主要区别体现在以下方面:

BFS​​

  • 逐层访问节点,先访问根节点的所有相邻节点,再按层次扩展至下一层节点,确保按“近→远”顺序遍历。例如二叉树的BFS遍历顺序为根节点→左子节点→右子节点→孙节点。
  • 使用​​队列(FIFO)​​,按先进先出原则管理待访问节点。
  • 空间复杂度​​:需要存储当前层的所有节点,空间复杂度为 ​​O(V)​​(V为节点数)
  • 时间复杂度​​:两者时间复杂度均为 ​​O(V+E)​​(V为节点数,E为边数)
  • 应用场景​​:​​
    • 最短路径问题​​:如迷宫最短路径、社交网络中的最小关系链
    • 层级分析​​:如按层次遍历二叉树、网络路由

DFS

  • 沿一条路径尽可能深入,直到无法继续时回溯到上一节点,再探索其他分支。例如二叉树的DFS遍历可能先访问根节点→左子树的最深节点,再回溯访问右子树。
  • 使用​​栈(LIFO)​​或​​递归​​实现,后进先出的机制支持回溯操作。
  • 空间复杂度​​:递归深度取决于树的高度,空间复杂度为 ​​O(h)​​(h为树高)
  • 时间复杂度​​:两者时间复杂度均为 ​​O(V+E)​​(V为节点数,E为边数)
  • 应用场景​​:​​
    • ​​深层结构探索​​:如检测环路、拓扑排序、连通性分析。
    • 回溯问题​​:如排列组合生成、迷宫路径全解枚举。

下面将针对一道例题,同时使用 BFS 和 DFS算法,以探讨两者区别。

通过例题讲解BFS和DFS的区别

P1605 迷宫

题目描述

给定一个 N×MN \times MN×M 方格的迷宫,迷宫里有 TTT 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N,M,TN,M,TN,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 SX,SY,FX,FYSX,SY,FX,FYSX,SY,FX,FYSX,SYSX,SYSX,SY 代表起点坐标,FX,FYFX,FYFX,FY 代表终点坐标。

接下来 TTT 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

输入输出样例 #1

输入 #1

2 2 1
1 1 2 2
1 2

输出 #1

1

说明/提示

对于 100%100\%100% 的数据,1≤N,M≤51 \le N,M \le 51N,M51≤T≤101 \le T \le 101T101≤SX,FX≤n1 \le SX,FX \le n1SX,FXn1≤SY,FY≤m1 \le SY,FY \le m1SY,FYm

广度优先搜索(BFS)算法

伪代码

function BFS(graph, start_node):
    create a queue Q          // 创建一个队列 Q 来存储待访问的节点[^1]
    mark start_node as visited and enqueue it into Q      // 将起点标记为已访问,并加入队列[^2]

    while Q is not empty:         // 当队列不为空时循环执行
        node = dequeue(Q)         // 取出队首节点
        visit(node)               // 访问该节点
        
        for each neighbor of node in graph:       // 遍历当前节点的所有邻居节点
            if neighbor is not visited:           // 如果邻居节点未被访问过
                mark neighbor as visited          // 标记邻居节点为已访问
                enqueue(neighbor to Q)           // 将邻居节点加入队列
    
    return "BFS completed"

完整代码:使用队列

#include <iostream>
#include <queue>
using namespace std;

// State用于标记当前程序所在的点
struct State {
    int x, y;
    // 建立二维数组用于标记是否访问过
    vector<vector<bool>> visited;
};

int main(){

    // 输入第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。
    int n, m, t;
    cin >> n >> m >> t;

    //第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX,FY 代表终点坐标。
    int sx, sy, fx, fy;
    cin >> sx >> sy >> fx >> fy;

    // 建立maze二维数组用于存储地图,其中true代表障碍
    vector<vector<bool>> maze(n+1, vector<bool>(m+1, false));

    // 用于初始化visited数组
    vector<vector<bool>> init_visited(n+1, vector<bool>(m+1, false));

    while(t--){
        // 读取障碍坐标并保存到 maze
        int x, y;
        cin >> x >> y;
        maze[x][y] = true;
    }

    // 建立方向数组,用于定义四个移动方向   
    int dir[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};

    // 初始化队列,将起点入队
    queue<State> q;
    init_visited[sx][sy] = true; // 标记起点已访问
    q.push({sx, sy, init_visited});

    //用于存储路径的总数
    int count = 0;

    while(!q.empty()){ // 当队列不为空时,继续循环
        State current = q.front(); // 取出队首元素
        q.pop(); // 移除队首元素

        // 到达终点,路径数+1
        if(current.x == fx && current.y == fy){
            count++;
            continue; // 不继续扩展终点节点
        }

        // 遍历四个移动方向
        for(int i=0; i<4; i++){
            int nx = current.x + dir[i][0]; // 计算新坐标
            int ny = current.y + dir[i][1]; // 计算新坐标

            // 检查新坐标是否越界
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue; // 越界跳过

            // 检查新坐标是否为障碍或已访问过
            if(maze[nx][ny] || current.visited[nx][ny]) continue; // 是则跳过

            // 深度拷贝访问状态:创建新访问矩阵
            vector<vector<bool>> new_visited = current.visited;
            new_visited[nx][ny] = true; // 标记新坐标已访问

            // 将新坐标入队
            q.push({nx, ny, new_visited});
        }
    }

    cout << count << endl;
    return 0;
}

深度优先搜索算法(DFS)

伪代码 (使用递归)

function DFS(node):
    if node is null:  // 如果节点为空,则直接返回
        return
    
    visit(node)       // 访问当前节点 (例如打印节点值或其他操作)
    
    mark node as visited  // 将当前节点标记为已访问
    
    for each neighbor of node:  // 遍历当前节点的所有邻居节点
        if neighbor is not visited:  // 如果邻居节点未被访问
            DFS(neighbor)           // 对该邻居节点递归执行DFS函数

伪代码 (使用栈)

function IterativeDFS(start_node):
    stack = new Stack()         // 创建一个新的栈
    stack.push(start_node)      // 将起始节点压入栈中
    
    while stack is not empty:   // 当栈不为空时循环
        node = stack.pop()      // 取出栈顶元素
        
        if node has not been visited:  // 如果取出的节点尚未访问
            visit(node)               // 访问该节点
            
            mark node as visited     // 标记该节点为已访问
            
            for each neighbor of node in reverse order:  // 遍历当前节点的所有邻居节点(逆序)
                stack.push(neighbor)       

完整代码: 使用栈

#include <iostream>
#include <queue>
#include <stack>  // 添加栈头文件
using namespace std;

// State用于标记当前程序所在的点
struct State {
    int x, y;
    // 建立二维数组用于标记是否访问过
    vector<vector<bool>> visited;
};

int main(){

    // 输入第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。
    int n, m, t;
    cin >> n >> m >> t;

    //第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX,FY 代表终点坐标。
    int sx, sy, fx, fy;
    cin >> sx >> sy >> fx >> fy;

    // 建立maze二维数组用于存储地图,其中true代表障碍
    vector<vector<bool>> maze(n+1, vector<bool>(m+1, false));

    // 用于初始化visited数组
    vector<vector<bool>> init_visited(n+1, vector<bool>(m+1, false));

    while(t--){
        // 读取障碍坐标并保存到 maze
        int x, y;
        cin >> x >> y;
        maze[x][y] = true;
    }

    // 建立方向数组,用于定义四个移动方向   
    int dir[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};

    // 初始化队列,将起点入队
    // 将队列改为栈
    stack<State> s;
    init_visited[sx][sy] = true;
    s.push({sx, sy, init_visited});

    int count = 0;

    while(!s.empty()) {
        State current = s.top();  // 取栈顶元素
        s.pop();  // 移除栈顶元素

        if(current.x == fx && current.y == fy) {
            count++;
            continue; // 不继续扩展终点节点
        }

        // 遍历四个移动方向
        for(int i=0; i<4; i++){
            int nx = current.x + dir[i][0]; // 计算新坐标
            int ny = current.y + dir[i][1]; // 计算新坐标

            // 检查新坐标是否越界
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue; // 越界跳过

            // 检查新坐标是否为障碍或已访问过
            if(maze[nx][ny] || current.visited[nx][ny]) continue; // 是则跳过

            // 深度拷贝访问状态:创建新访问矩阵
            vector<vector<bool>> new_visited = current.visited;
            new_visited[nx][ny] = true; // 标记新坐标已访问

            // 将新坐标入队
            s.push({nx, ny, new_visited});
        }
    }

    cout << count << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值