搜索算法dfs和bfs解析(附有例题)

简介: 搜索算法dfs和bfs解析(附有例题)

前言

本文我们主要来介绍dfs和bfs的基础知识在加以几个必要的习题说明,搜索算法dfs和bfs

dfs

深度优先搜索算法(简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。

是递归回溯的方法来进行搜索,dfs:一条路走到黑的方式来进行搜索,我们来看下面这张图

在这里插入图片描述
从每个节点一条路走下去,直到走不通为止

dfs全排列问题

以三为例,dfs的搜索顺序如下图所示
在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 10;
int path[N];//保存序列
int state[N];//数字是否被用过
int n;
void dfs(int u)
{
    if(u > n)//数字填完了,输出
    {
        for(int i = 1; i <= n; i++)//输出方案
            cout << path[i] << " ";
        cout << endl;
    }

    for(int i = 1; i <= n; i++)//空位上可以选择的数字为:1 ~ n
    {
        if(!state[i])//如果数字 i 没有被用过
        {
            path[u] = i;//放入空位
            state[i] = 1;//数字被用,修改状态
            dfs(u + 1);//填下一个位
            state[i] = 0;//回溯,取出 i
        }
    }
}

int main()
{

    cin >> n;
    dfs(1);
}

dfs N皇后问题

n皇后

「N 皇后问题」研究的是如何将 N 个皇后放置在 N×N 的棋盘上,并且使皇后彼此之间不能相互攻击。
皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。

直观的做法是暴力枚举将 N个皇后放置在 N×N 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。

对应今天的主题,我们就先用dfs深搜的方式来写这个n皇后问题

思路:
显然,每个皇后必须位于不同行和不同列,因此将 N 个皇后放置在 N ×N 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。

具体做法:
使用一个数组记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 N 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,将数组转换成表示棋盘状态的列表,并将该棋盘状态的列表加入返回列表。
在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 20; 

// bool数组用来判断搜索的下一个位置是否可行
// col列,dg对角线,udg反对角线
// g[N][N]用来存路径

int n;
char g[N][N];
bool col[N], dg[N], udg[N];

void dfs(int u) {
    //搜到
    if (u == n) {
        for (int i = 0; i < n; i ++ ) puts(g[i]);   
        puts(""); 
        return;
    }

    //对n个位置按行搜索
    for (int i = 0; i < n; i ++ )
        // 剪枝(对于不满足要求的点,不再继续往下搜索)  
        // udg[n - u + i],+n是为了保证下标非负
        if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
            g[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; // 恢复现场 
            g[u][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;
}   

最长快乐字符串

int used;
char GetNext(int *a, int *b, int *c)
{
    int m = *a, n = *b, p = *c;
    if (used == 1) { /* 哪个用过了,不参与比较 */
        m = 0;
    }
    if (used == 2) {
        n = 0;
    }
    if (used == 3) {
        p = 0;
    }
    if ((m >= n) && (m >= p) && (m > 0)) {
        (*a)--;
        used = 0;
        return 'a';
    }
    if ((n >= m) && (n >= p) && (n > 0)) {
        (*b)--;
        used = 0;
        return 'b';
    }
    if ((p >= m) && (p >= n) && (p > 0)) {
        (*c)--;
        used = 0;
        return 'c';
    }
    return '0';
}
char * longestDiverseString(int a, int b, int c)
{
    used = 0;
    char *res = calloc(a + b + c + 1, sizeof(char));
    char t;
    int cnt = 0;
    while (true) {
        t = GetNext(&a, &b, &c);
        if (t != '0') {
            res[cnt++] = t; 
        } else {
            break;
        }
        if ((cnt >= 2) && (res[cnt - 2] == 'a') && (res[cnt - 1] == 'a')) {
            used = 1;
        }
        if ((cnt >= 2) && (res[cnt - 2] == 'b') && (res[cnt - 1] == 'b')) {
            used = 2;
        }
        if ((cnt >= 2) && (res[cnt - 2] == 'c') && (res[cnt - 1] == 'c')) {
            used = 3;
        }
    }
    return res;
}

二叉树的最近祖先

dfs二叉树的最近祖先
值得注意:一个节点也可以是它自己的祖先,二叉搜索树,对于根节点来说,左边比根小,右边比根大,可以做截枝

暴搜

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //if (root == p || root == q) return root;
        if (p->val < root->val && q->val < root->val) return lowestCommonAncestor(root->left, p, q);
        if (p->val > root->val && q->val > root->val) return lowestCommonAncestor(root->right, p, q);
        return root;
    }
};
class Solution {
public:
    vector<TreeNode*> getPath(TreeNode* root, TreeNode* target) {
        vector<TreeNode*> path;
        TreeNode* node = root;
        while (node != target) {
            path.push_back(node);
            if (target->val < node->val) {
                node = node->left;
            }
            else {
                node = node->right;
            }
        }
        path.push_back(node);
        return path;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path_p = getPath(root, p);
        vector<TreeNode*> path_q = getPath(root, q);
        TreeNode* ancestor;
        for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
            if (path_p[i] == path_q[i]) {
                ancestor = path_p[i];
            }
            else {
                break;
            }
        }
        return ancestor;
    }
};

bfs

广度优先搜索,像下面这张图一样,一层一层去搜索,我们也不难看出,当边的权值都是一样的时候,第一次搜索就是最短路,后面的搜索都比第一次大,引出最短路问题,这个我们后面再说。
在这里插入图片描述
迷宫
dfs保证搜到终点,但是不能保证搜到的是最短的

#include<bits/stdc++.h>//万能头文件
using namespace std;
const int MAXSIZE_N=1000;
const int MAXSIZE_M=100000;
struct state{
    int x;
    int y;
};//养成好习惯~结构体有利于我们调整程序

bool IsUsed[MAXSIZE_N][MAXSIZE_N];//记录迷宫bfs的遍历状态
bool maze[MAXSIZE_N][MAXSIZE_N];//maze用于记录迷宫本身
int lookUpTable[MAXSIZE_N][MAXSIZE_N]={0};//建立查询表,记忆化搜索

int n,m;
int cnt=0;//记录可走的格子数

state dir[4]={{1,0},{-1,0},{0,1},{0,-1}};

void InitAll()
{
//    memcpy(maze,neverMaze,sizeof(neverMaze));
//    memcpy(IsUsed,neverUsed,sizeof(neverUsed));//速度过慢
    cnt=0;
}

bool IsSafe(state now,state next)
{
    return(next.x>=0&&next.x<n&&next.y>=0&&next.y<n)//在范围内
    &&(maze[next.x][next.y]!=maze[now.x][now.y])//题目定义的规则,只能0->1或1->0
    &&(IsUsed[next.x][next.y]!=true);//没有染过色or没走过
}


void bfs(state start)
{
    queue<state> Q;
    queue<state> memoryPos;//用于记忆化搜索,记录走过的点,提高速度
    IsUsed[start.x][start.y]=true;
    cnt++;
    Q.push(start);
    memoryPos.push(start);
    while(!Q.empty()){
        state now=Q.front();
        Q.pop();
        for(int i=0;i<4;i++){
            state next=now;
            next.x+=dir[i].x;
            next.y+=dir[i].y;
            if(IsSafe(now,next)){
                IsUsed[next.x][next.y]=true;
                memoryPos.push(next);
                cnt++;
                Q.push(next);
            }
        }
    }
    while(!memoryPos.empty())
    {
        state next=memoryPos.front();
        //把走过的点都记到表中
        memoryPos.pop();
        lookUpTable[next.x][next.y]=cnt;
    }
}

int main()
{
    std::ios::sync_with_stdio(false);//加快输入流的速度
    cin>>n>>m;
    string in[n];
    state start[m];
    for(int i=0;i<n;i++)    cin>>in[i];
    for(int i=0;i<m;i++){
        cin>>start[i].x>>start[i].y;
        start[i].x-=1;//调整输入的坐标到从零开始的坐标
        start[i].y-=1;
    }

    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(in[i][j]=='0')
                maze[i][j]=false;//输入的0记为false
            else
                maze[i][j]=true;//1记为true
        }
    }

    for(int i=0;i<m;i++)
    {
        if(lookUpTable[start[i].x][start[i].y]!=0){
            cout<<lookUpTable[start[i].x][start[i].y]<<'\n';//建立查询表
            continue;
        }

        InitAll();//开始是用memcpy初始化IsUsed的..后来发现染色法其实根本不需要重新初始化,但还是保留了函数
        bfs(start[i]);//对每个输入的点进行bfs
        cout<<cnt<<'\n';
    }
    return 0;
}
相关文章
|
20天前
|
机器学习/深度学习 数据采集 自然语言处理
理解并应用机器学习算法:神经网络深度解析
【5月更文挑战第15天】本文深入解析了神经网络的基本原理和关键组成,包括神经元、层、权重、偏置及损失函数。介绍了神经网络在图像识别、NLP等领域的应用,并涵盖了从数据预处理、选择网络结构到训练与评估的实践流程。理解并掌握这些知识,有助于更好地运用神经网络解决实际问题。随着技术发展,神经网络未来潜力无限。
|
3天前
|
机器学习/深度学习 数据采集 存储
【机器学习】K-近邻算法(KNN)全面解析
K-近邻算法(K-Nearest Neighbors, KNN)是一种基于实例的学习方法,属于监督学习范畴。它的工作原理简单直观:给定一个训练数据集,对新的输入实例,KNN算法通过计算其与训练集中每个实例的距离,找出距离最近的K个邻居,然后根据这些邻居的类别(对于分类任务)或值(对于回归任务)来预测新实例的类别或值。KNN因其简单高效和无需训练过程的特点,在众多领域中得到广泛应用,如模式识别、推荐系统、图像分类等。
17 0
|
4天前
|
存储 搜索推荐 算法
归并排序算法深入解析
归并排序算法深入解析
|
7天前
|
存储 算法 搜索推荐
深度解析:Python中的高效数据结构与算法实现
深度解析:Python中的高效数据结构与算法实现
24 0
|
10天前
|
索引
浅谈两个重要的搜索算法
【5月更文挑战第15天】线性搜索从数组一端按顺序遍历,直到找到目标元素,平均和最坏情况的时间复杂度均为O(N)。二分查找适用于排序数组,通过比较中间元素快速定位目标,最佳、平均和最坏情况的时间复杂度都是O(logN)。
17 6
|
11天前
|
存储 算法 C++
c++算法学习笔记 (7) BFS
c++算法学习笔记 (7) BFS
|
11天前
|
算法 C++
c++算法学习笔记 (6) DFS
c++算法学习笔记 (6) DFS
|
13天前
|
算法
【软件设计师】常见的算法设计方法——穷举搜索法
【软件设计师】常见的算法设计方法——穷举搜索法
|
15天前
|
机器学习/深度学习 编解码 算法
算法工程师面试问题总结 | YOLOv5面试考点原理全解析
本文给大家带来的百面算法工程师是深度学习目标检测YOLOv5面试总结,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,我们还将介绍一些常见的深度学习目标检测面试问题,并提供参考的回答及其理论基础,以帮助求职者更好地准备面试。通过对这些问题的理解和回答,求职者可以展现出自己的深度学习目标检测领域的专业知识、解决问题的能力以及对实际应用场景的理解。同时,这也是为了帮助求职者更好地应对深度学习目标检测岗位的面试挑战,提升面试的成功率和竞争力。
|
18天前
|
缓存 算法 Java
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析
数据结构~缓存淘汰算法--LRU算法(Java的俩种实现方式,万字解析

推荐镜像

更多