DFS & BFS 基础

DFS & BFS 基础

一、DFS

例1:排列数字

给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 nn。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include<iostream>
using namespace std;

const int N=10;
int n,path[N];//用来记录每一次的搜索路径
bool used[N];//用来标记某个数字是否被用过

void dfs(int x){//x指第几位数
    if(x==n){//已经填到了最后一位
        for(int i=0;i<n;i++) printf("%d ",path[i]);
        puts("");
        return ;
    }
    
    //依次枚举每一个备选的数字
    for(int i=1;i<=n;i++){
        if(used[i]==false){
            path[x]=i;//这个位置填上i
            used[i]=true;
            dfs(x+1);
            used[i]=false;
            //path[x]=0;
        }
    }
}

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

例2:n-皇后问题

n−n−皇后问题是指将 nn 个皇后放在 n×nn×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

1_597ec77c49-8-queens.png

现在给定整数 nn,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 nn。

输出格式

每个解决方案占 nn 行,每行输出一个长度为 nn 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤91≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..
#include<iostream>
using namespace std;

const int N=10;
char g[N][N];
int n;
bool col[N],dg[N],udg[N];//列、对角线、反对角线

//每一行有且仅有一个,所以可以按行搜索
void dfs(int x){
    if(x==n){//已经搜了n行,输出这条路径
        for(int i=0;i<n;i++) puts(g[i]);
        puts("");//换行
        return ;
    }

    //枚举x这一行,搜索合法的列
    for(int i=0;i<n;i++){
        //剪枝,对于不满足条件的点,不再继续搜索
        if(!col[i] && !dg[x+i] && !udg[n-x+i]){
            g[x][i]='Q';
            col[i]=dg[x+i]=udg[i-x+n]=true;
            //+n是防止下标小于0
            dfs(x+1);
            col[i]=dg[x+i]=udg[i-x+n]=false;
            g[x][i]='.';//恢复现场
        }
    }
}

int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)
            g[i][j]='.';
    }
    dfs(0);
    return 0;
}
二、BFS

例1:走迷宫

给定一个 n×mn×m 的二维整数数组,用来表示一个迷宫,数组中只包含 00 或 11,其中 00 表示可以走的路,11 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1)(1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m)(n,m) 处,至少需要移动多少次。

数据保证 (1,1)(1,1) 处和 (n,m)(n,m) 处的数字为 00,且一定至少存在一条通路。

输入格式

第一行包含两个整数 nn 和 mm。

接下来 nn 行,每行包含 mm 个整数(00 或 11),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤1001≤n,m≤100

输入样例:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

输出样例:

8
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

//所有边权相同的最短路问题才能用bfs
/*  queue<--初始状态
    while queue不变{
        t<--队头
        拓展t
    }
*/
const int N=110;
typedef pair<int,int> PII;
int n,m;
int g[N][N];//存地图
int d[N][N];//存每一个点到起点的距离

int bfs(){
    queue<PII> q;//队列储存路径
    
    memset(d,-1,sizeof d);//距离全部初始化为-1,表示没有走过
    
    //首先在起点,初始化d,并将这个点加入队列
    d[0][0]=0;
    q.push({0,0});
    
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};//向量表示上下左右四个方向
    
    while(q.size()){//队列不空
        auto t=q.front();//取出队头
        q.pop();//队头出队
        
        for(int i=0;i<4;i++){
            int x=t.first+dx[i],y=t.second+dy[i];//枚举向上下左右四个方向移动
            
            //且在边界范围内,且可以走是空地(g[][]==0),且之前没有走过(d[][]==-1)
            if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1){
                d[x][y]=d[t.first][t.second]+1;//更新这个点的距离
                q.push({x,y});//把这个位置记录在队列里
            }
        }
    }
    //这个循环也就是说,每一个在当时符合条件的位置都会被push到队列里,但是继续往下走时有些路径会无法走通,即无法继续延伸
    //而那个点本身被作为对头取出后,就会被pop掉,即被忽略掉
    //只有能走到终点的点最后会被留在队里,在循环中继续向下走,并更新着d[][]距离
    //而最后返回的答案,是右下角(终点)那个点距离起点的距离
    //由于bfs不走重复的路,走到那里只会剩下一条路,即d[n-1][m-1]只会被赋予最小值
    //即最短路
    
    return d[n-1][m-1];
}

int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++)
            cin>>g[i][j];
    }
    
    cout<<bfs()<<endl;
    
    return 0;
}

例2:八数码

在一个 3×33×3 的网格中,1∼81∼8 这 88 个数字和一个 x 恰好不重不漏地分布在这 3×33×3 的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将 3×33×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出 −1−1。

输入样例:

2  3  4  1  5  x  7  6  8

输出样例

19
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;

/*bfs 状态处理
如字符串 1234x5678 判断可以变成什么状态
    (1)先变成3x3的矩阵
    (2) x上下左右移动的情况进行枚举
    (3)再将矩阵恢复成字符串
*/

int bfs(string start){
    string end="12345678x";
    
    queue<string> q;//队列里存的是字符串,是一串字符,是以上字符的一种排列方式
    unordered_map<string,int> d;//d是某种排列方式距离原始状态移动的次数(即可看作距离)
    
    q.push(start);//初始状态入队
    d[start]=0;//初始状态的距离为0
    
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
    
    //宽搜模板
    while(q.size()){
        auto t=q.front();//取出队头,t是字符串,是以上字符的一种排列方式
        q.pop();
        
        int distance=d[t];//t状态下跟原始状态的距离
        
        if(t==end) return d[t];//如果已经到最终状态,直接返回所用的步数,即答案
        
        //状态转移
        int k=t.find('x');//先找到x的位置(返回x的下标)  (string中的函数 s.find('a') 返回字符a的下标)
        int x=k/3,y=k%3;//一维数组和二维数组下标的转化
        
        for(int i=0;i<4;i++){//枚举四种状态
            int a=x+dx[i],b=y+dy[i];
            if(a>=0 && a<3 && b>=0 && b<3){//a,b都没有出界
                swap(t[k],t[a*3+b]);//交换一维数组中该点与x的下标
                
                if(!d.count(t)){//这个状态没有经历过,可向这个方向交换
                    d[t]=distance+1;//在之前那个状态下的步数+1
                    q.push(t);//作为一种情况入队
                }
                swap(t[k],t[a*3+b]);//恢复状态,继续循环向其他方向的情况
            }
        }
    }
    return -1;
}

int main(){
    string start;//存初始状态
    for(int i=0;i<9;i++){
        char c;
        cin>>c;
        start+=c;
    }
    
    cout<<bfs(start)<<endl;//bfs输出最短距离
    
    return 0;
}
### 深度优先搜索(DFS) 和 广度优先搜索(BFS) 算法入门 #### 什么是深度优先搜索(DFS)? 深度优先搜索(Depth First Search, DFS)是一种用于遍历或搜索树或图的算法。其核心思想是从起始节点开始,沿着某一条路径尽可能深入地探索下去,直到无法继续为止。如果到达了一个死胡同,则回溯至上一个有未访问邻接点的节点并重复此过程[^1]。 以下是C++实现的一个简单例子: ```cpp #include &lt;iostream&gt; #include &lt;vector&gt; using namespace std; void dfs(int node, vector&lt;vector&lt;int&gt;&gt;&amp; adjList, vector&lt;bool&gt;&amp; visited){ cout &lt;&lt; node &lt;&lt; &quot; &quot;; // 访问当前节点 visited[node] = true; // 标记已访问 for(auto neighbor : adjList[node]){ if(!visited[neighbor]){ // 如果邻居节点尚未被访问 dfs(neighbor, adjList, visited); // 继续递归调用dfs函数 } } } int main(){ int n = 5; vector&lt;vector&lt;int&gt;&gt; adjList(n+1); adjList[0].push_back(1); adjList[0].push_back(2); adjList[1].push_back(3); adjList[1].push_back(4); vector&lt;bool&gt; visited(n+1, false); dfs(0, adjList, visited); } ``` #### 什么是广度优先搜索(BFS)? 广度优先搜索(Breadth First Search, BFS)也是一种常用的图遍历方法。不同于DFS的是,BFS会一层层地扩展节点,即先访问离起点最近的所有节点后再去更远的地方。这种策略通常借助队列来辅助完成操作[^2]。 下面给出Python版本的BFS代码片段作为示范: ```python from collections import deque def bfs(start_node, graph): queue = deque([start_node]) # 初始化队列为只含有初始节点 visited = set() # 创建集合存储已经访问过的节点 while queue: current = queue.popleft() if current not in visited: print(current) visited.add(current) # 将所有未访问的邻居加入队列 for neighbour in graph[current]: if neighbour not in visited: queue.append(neighbour) # 测试数据结构 graph = { &#39;A&#39;: [&#39;B&#39;, &#39;C&#39;], &#39;B&#39;: [&#39;D&#39;, &#39;E&#39;], &#39;C&#39;: [&#39;F&#39;], &#39;D&#39;: [], &#39;E&#39;: [&#39;F&#39;], &#39;F&#39;: [] } bfs(&#39;A&#39;, graph) ``` #### 总结比较两者特点 - **时间复杂度**: 对于具有V个顶点和E条边的无向连通图来说,无论是DFS还是BFS的时间复杂度均为O(V+E)[^3]。 - **空间需求**: 在最坏情况下,由于需要保存整个路径或者层次关系的信息,因此它们的空间消耗也大致相同。 - **应用场景差异**: - 当目标是最短距离时倾向于采用BFS; - 而当只需要找到任意解而不在乎长度的时候可以考虑使用DFS因为其实现起来更加简洁直观一些。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值