深度搜索(DFS) 和 广度搜索(BFS)

本文详细介绍了深度优先搜索(DFS)的回溯+剪枝策略,包括经典例题如排列数字和n-皇后问题,以及广度优先搜索(BFS)的基本思想和走迷宫的应用。通过实例演示了这两种搜索算法在实际问题中的使用和优化技巧。
摘要由CSDN通过智能技术生成

深度搜索(DFS)

深度搜索思路:回溯 + 剪枝

深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.

举例说明之:下图是一个无向图,如果我们从A点发起深度优先搜索(以下的访问次序并不是唯一的,第二个点既可以是B也可以是C,D),则我们可能得到如下的一个访问过程:A->B->E(没有路了!回溯到A)->C->F->H->G->D(没有路,最终回溯到A,A也没有未访问的相邻节点,本次搜索结束).简要说明深度优先搜索的特点:每次深度优先搜索的结果必然是图的一个连通分量.深度优先搜索可以从多点发起.如果将每个节点在深度优先搜索过程中的"结束时间"排序(具体做法是创建一个list,然后在每个节点的相邻节点都已被访问的情况下,将该节点加入list结尾,然后逆转整个链表),则我们可以得到所谓的"拓扑排序",即topological
sort.
在这里插入图片描述

基本思路

深度优先遍历图的方法是,从图中某顶点v出发:
(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。 当然,当人们刚刚掌握深度优先搜索的时候常常用它来走迷宫.事实上我们还有别的方法,那就是广度优先搜索(BFS).

(1)对于下面的树而言,DFS方法首先从根节点1开始,其搜索节点顺序是1,2,3,4,5,6,7,8(假定左分枝和右分枝中优先选择左分枝)。
在这里插入图片描述

(2)从stack中访问栈顶的点;
在这里插入图片描述

(3)找出与此点邻接的且尚未遍历的点,进行标记,然后放入stack中,依次进行;
在这里插入图片描述

(4)如果此点没有尚未遍历的邻接点,则将此点从stack中弹出,再按照(3)依次进行;
在这里插入图片描述

在这里插入图片描述
(5)直到遍历完整个树,stack里的元素都将弹出,最后栈为空,DFS遍历完成。

在这里插入图片描述
在这里插入图片描述

深度搜索模板
int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
 
void dfs(int step)
{
    判断边界if
    {
        到达边界时的操作(输出等)
    }
    未到边界时尝试每一种可能else
    {
        满足check条件 if
        {
            标记
            继续下一步 : dfs(step+1)
            恢复初始状态
        }
    };
}
深度搜索经典例题:【排列数字】

原题链接

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

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

输入格式
共一行,包含一个整数 n。

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

数据范围
1 ≤ n ≤ 7 1≤n≤7 1n7
输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

题目答案:

#include<bits/stdc++.h>

using namespace std;
int path[N];
bool vis[N];
int n;
void dfs(int x){
    if(x > n)
    {
        for (int i = 1; i <= n; ++i) cout<<path[i]<<" ";
        puts("");
        return;
    }
    else
    {
        for (int i = 1; i <= n; ++i) 
        {
            if(!vis[i])
            {
                path[x] = i;
                vis[i] = true;
                dfs(x+1);
                vis[i] = false;
            }
        }
    }
}

int main() {
    cin>>n;
    dfs(1);
    return 0;
}
深度搜索经典例题:【n-皇后问题】

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

1_597ec77c49-8-queens.png
现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式
共一行,包含整数 n。

输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。每个方案输出完成后,输出一个空行。注意:行末不能有多余空格。输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围
1 ≤ n ≤ 9 1≤n≤9 1n9
输入样例:

4

输出样例:

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

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

(DFS按行枚举) 时间复杂度 O ( n ! ) O(n!) O(n!)
代码分析

对角线 d g [ u + i ] dg[u+i] dg[u+i],反对角线 u d g [ n − u + i ] udg[n−u+i] udg[nu+i]中的下标 u + i u+i u+i n − u + i n−u+i nu+i 表示的是截距

下面分析中的 ( x , y ) (x,y) (x,y) 相当于上面的 ( u , i ) (u,i) (u,i)
反对角线 y = x + b y=x+b y=x+b, 截距 b = y − x b=y−x b=yx,因为我们要把 b 当做数组下标来用,显然 b 不能是负的,所以我们加上 +n(实际上+n+4,+2n都行),来保证是结果是正的,即 y − x + n y - x + n yx+n
而对角线 y = − x + b y=−x+b y=x+b, 截距是 b = y + x b=y+x b=y+x,这里截距一定是正的,所以不需要加偏移量
核心目的:找一些合法的下标来表示 d g dg dg u d g udg udg 是否被标记过,所以如果你愿意,你取 u d g [ n + n − u + i ] udg[n+n−u+i] udg[n+nu+i] 也可以,只要所有 ( u , i ) (u,i) (u,i) 对可以映射过去就行

题目答案:

#include<bits/stdc++.h>

using namespace std;
const int N = 100;
char g[N][N];  // g[N][N]用来存路径
// bool数组用来判断搜索的下一个位置是否可行
bool col[N],dg[N],udg[N]; // g[N][N]用来存路径
int n;

void dfs(int x){
    // u == n 表示已经搜了n行,故输出这条路径
    if(x == n){
        for (int i = 0; i < n; ++i) puts(g[i]); // 等价于cout << g[i] << endl;
        puts(""); // 换行
        return;
    }else{
        for (int i = 0; i < n; ++i) {
            // 剪枝(对于不满足要求的点,不再继续往下搜索)
            // udg[n - u + i],+n是为了保证下标非负
            if(!col[i] && !dg[x+i] && !udg[n-x+i])
            {
                g[x][i] = 'Q';
                col[i] = dg[x+i] = udg[n-x+i] = true;
                dfs(x+1);
                g[x][i] = '.';
                col[i] = dg[x+i] = udg[n-x+i] = false;// 恢复现场 这步很关键
            }

        }
    }

}
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)

深度搜索简介

广度优先搜索(也称宽度优先搜索,缩写BFS,以下采用广度来描述)是连通图的一种遍历算法这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。基本过程,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。一般用队列数据结构来辅助实现BFS算法。

基本思路

(1)给出一连通图,如图,初始化全是白色(未访问);
在这里插入图片描述

(2)搜索起点V1(灰色);
在这里插入图片描述

(3)已搜索V1(黑色),即将搜索V2,V3,V4(标灰);
在这里插入图片描述

(4)对V2,V3,V4重复以上操作;
在这里插入图片描述

(5)直到终点V7被染灰,终止;

在这里插入图片描述

(6)最短路径为V1,V4,V7.

深度搜索经典例题:【走迷宫——边的权值相同】

给定一个 n ∗ m n*m nm 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 ( 1 , 1 ) (1, 1) (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 ( n , m ) (n, m) (n,m) 处,至少需要移动多少次。
据保证 ( 1 , 1 ) (1, 1) (1,1) 处和 ( n , m ) (n, m) (n,m) 处的数字为 0,且一定至少存在一条通路。

输入格式

第一行包含两个整数n和m。
接下来n行,每行包含m个整数(0或1),表示完整的二维数组迷宫。

输出格式

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

数据范围

1 ≤ n , m ≤ 100 1≤n,m≤100 1n,m100

输入样例:

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<bits/stdc++.h>

using namespace std;
const int N = 110;
int n,m;
int g[N][N],d[N][N];  // g记录迷宫,d记录该位置与起始位置的距离
typedef pair<int,int> PII;
queue<PII> q;

int bfs(){
    int dx[] = {-1,0,1,0},dy[] = {0,1,0,-1};

    q.push({0,0}); //从第一个元素开始访问
    d[0][0] = 0;

    while(!q.empty()){
        auto t = q.front();  q.pop();
        
        for(int i = 0;i < 4;i++){
            int x = t.first+dx[i] , y = t.second+dy[i];
            
            if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && !d[x][y]){
                d[x][y] = d[t.first][t.second]+1;
                q.push({x,y});
            }
        }
    }
    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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值