【搜索与图论】DFS算法(深度优先搜索)

一、定义

DFS 是一种用于遍历或搜索树/图的算法。核心思想是"一条路走到黑,撞了南墙就回头"。

二、相关概念

**回溯:**走不通就回头,恢复原来的样子,继续尝试别的可能。

**剪枝:**提前发现这条路行不通,直接不走了,节省时间。= 砍掉无效分支

三、核心原理

DFS的工作流程,本质是利用"栈"的数据结构(先进后出)来记录访问路径,确保我们能"回溯"到上一个节点。这里要注意两个点:

访问与回溯的逻辑

  1. 从起始节点出发,标记该节点为"已访问"(避免重复访问,陷入死循环);
  2. 选择该节点的一个未访问邻居节点,继续深入访问(递归/栈推进);
  3. 当某个节点没有未访问的邻居时,说明这条路走到头了,回溯到上一个节点,继续寻找其他未访问的邻居;
  4. 重复步骤2-3,直到所有节点都被访问完毕。

隐式栈 vs 显式栈

很多人第一次学DFS会困惑:"栈在哪里?"------其实有两种实现方式,对应两种栈

  1. 隐式栈:用递归实现时,系统会自动创建"调用栈",递归的每一层就是栈的一个元素,回溯就是栈的弹出操作(递归返回);
  2. 显式栈:用迭代实现时,我们自己定义一个栈结构,手动将节点压入、弹出,模拟递归的过程。

本篇博客先使用递归实现

递归写法的三要素

  1. 终止条件:什么时候算找到答案了?什么时候是死路(越界、已访问)?

  2. 当前层操作 :标记已访问(visited)、记录路径。

  3. 下潜与回溯 :进入下一层递归,回来后撤销操作(状态重置)。

四、例题

排列数字

题目描述(题目来源于AcWing)

思路

  1. 使用path数组存储每次的路径。visited数组存储该数组是否被访问过。
  2. 对于1~n的数进行深度优先搜索。
    1. 判断当前是否已经填完了全部的数,如果是,则直接数组path
    2. 对于每个数判断当前数是否已经被填过了。
      1. 如果没有被填过,则把当前数加入在path中,同时把该数的状态设置为已经被访问。进入下一个要填数的位置,重复步骤1,2。如果该条路径到底就回溯。
      2. 若该数被填过了,则判断下一个数是否被填过。

代码

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 10;
int n;
//path数组用于存储每次排列的组合,visited数组用于存储当前数字是否被填过
int path[N];
bool visited[N];

//深度优先搜索---u表示当前正在填第几个数
void dfs(int u){
    //如果已经填完了所有数---输出该组合
    if(u == n){
        for(int i = 0; i < n; i++){
            cout<<path[i]<<" ";
        }
        //每种组合换行
        cout<<endl;
    }
    
    //对于每个数,需要判断当前的数有没有填过
    for(int i = 0; i < n; i++){
        if(visited[i] == false){
            //如果当前的数没有访问过--则把当前数字填入path中,同时设置该数字已经被访问过
            path[u] = i + 1; 
            visited[i] = true;
            
            //走到下一个要填的位置
            dfs(u + 1);
            //恢复为未访问
            visited[i] = false;
            
        }
    }
   
    
}
int main(){
    cin>>n;
    
    //调用dfs函数
    dfs(0);
    
    return 0;
}

n皇后问题

题目描述(题目来源于AcWing)

方法一:一行放一个皇后,dfs按行递归

思路

  1. 一行放一个皇后,用 DFS 按行递归
  2. 放之前检查列、对角线是否冲突
  3. 不冲突就放皇后 + 标记占用
  4. 递归到下一行
  5. 回溯:撤销标记,继续尝试其他列
  6. 放完 n 行 → 输出方案

代码

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 10;
int n;
//row存储每行是否出现皇后,col存储列,dg存储对角线,udg存储反对角线
int row[N], col[N], dg[2 * N], udg[2 * N];
//path存储当前方案
char path[N][N];

//u表示当前放皇后的行数
void dfs(int u){
    
    
    //判断是否把皇后都放完了
    if(u == n){
        //输出方案
        for(int i = 0; i < n; i++){
            cout<<path[i]<<endl;
        }
        cout<<endl;
    }
    
    //遍历每一列
    for(int i = 0; i < n; i++){
        //如果满足行,列,对角线,反对角线都没有放皇后---则可以放
        if(!col[i] && !dg[u + i] && !udg[n - u + i]){
            path[u][i] = 'Q';
            //同时设置该行,列,对角线,反对角线都已经放了皇后
            col[i] = dg[u + i] = udg[n - u + i] = true;
            
            //进入下一个位置
            dfs(u + 1);
            
            //回溯时修改状态
            col[i] = dg[u + i] = udg[n - u + i] = false;
            path[u][i] = '.';
            
        }
    }
}

int main(){
    cin>>n;
    
    //输出化棋盘全为.
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            path[i][j] = '.';
        }
    }
    
    //调用深度优先搜索函数
    dfs(0);
    
    return 0;
}

方法二:遍历每一个格子:每个格子有放和不放两种状态

思路

一个格子一个格子遍历,每个格子只有两种选择:不放皇后 或 放皇后。先试 "不放",走不通再回头试 "放",放满 n 个就输出答案。

  1. 从 (0,0) 开始一格一格走
  2. 每个格子:
    • 先不放 → 去下一格
    • 回来后 → 能放就放
  3. 放皇后必须满足:行列斜线都没皇后
  4. 放满 n 个 → 输出方案
  5. 走不通 → 回溯(撤销操作)
  6. 直到搜索完所有可能

代码

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 10;
int n;
//row存储每行是否出现皇后,col存储列,dg存储对角线,udg存储反对角线
int row[N], col[N], dg[2 * N], udg[2 * N];
//path存储当前方案
char path[N][N];

//x表示当前行数,y表示列数,s表述皇后的总数
void dfs(int x, int y, int s){
    //如果皇后大于需要的数,直接结束
    if(s > n){
        return ;
    }
    //如果格子走到越界,则切换到下一行
    if(y == n){
        y = 0;
        x++;
    }
    
    //走到最后一行时,判断
    if(x == n){
        //判断是否把皇后都放完了
        if(s == n){
            //输出方案
            for(int i = 0; i < n; i++){
                cout<<path[i]<<endl;
            }
            cout<<endl;
        }
        return;
    }
    
    //没有放皇后就是.,进入下一个格子
    path[x][y] = '.';
    dfs(x, y + 1, s);

    //判断是否满足条件
    if(!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]){
        path[x][y] = 'Q';
        //同时设置该行,列,对角线,反对角线都已经放了皇后
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
        
        //进入下一个位置
        dfs(x,y + 1,s + 1);
        
        //回溯时修改状态
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
        path[x][y] = '.';
            
    }
}

int main(){
    cin>>n;
    
    //输出化棋盘全为.
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            path[i][j] = '.';
        }
    }
    
    //调用深度优先搜索函数
    dfs(0,0,0);
    
    return 0;
}
相关推荐
美好的事情能不能发生在我身上2 小时前
Hot100中的:图论专题
图论
I_LPL2 小时前
hot100 栈专题
算法·
2401_879503412 小时前
C++中的观察者模式变体
开发语言·c++·算法
阿贵---2 小时前
C++中的备忘录模式
开发语言·c++·算法
setmoon2143 小时前
C++中的观察者模式实战
开发语言·c++·算法
山峰哥3 小时前
查询优化案例:从慢查询到闪电般的查询速度
数据库·sql·性能优化·编辑器·深度优先
2403_835568473 小时前
C++代码规范化工具
开发语言·c++·算法
tankeven3 小时前
HJ138 在树上游玩
c++·算法
lihihi3 小时前
P1209 [USACO1.3] 修理牛棚 Barn Repair
算法