搜索算法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;
}
相关文章
|
8月前
|
算法 数据可视化 测试技术
HNSW算法实战:用分层图索引替换k-NN暴力搜索
HNSW是一种高效向量检索算法,通过分层图结构实现近似最近邻的对数时间搜索,显著降低查询延迟。相比暴力搜索,它在保持高召回率的同时,将性能提升数十倍,广泛应用于大规模RAG系统。
703 10
HNSW算法实战:用分层图索引替换k-NN暴力搜索
|
8月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
8月前
|
机器学习/深度学习 算法 C++
【DFS/回溯算法】2016年蓝桥杯真题之路径之谜详解
题目要求根据城堡北墙和西墙箭靶上的箭数,推断骑士从西北角到东南角的唯一路径。每步移动时向正北和正西各射一箭,同一格不重复经过。通过DFS回溯模拟“拔箭”过程,验证路径合法性。已知箭数约束路径唯一,最终按编号输出行走顺序。
|
9月前
|
机器学习/深度学习 人工智能 搜索推荐
从零构建短视频推荐系统:双塔算法架构解析与代码实现
短视频推荐看似“读心”,实则依赖双塔推荐系统:用户塔与物品塔分别将行为与内容编码为向量,通过相似度匹配实现精准推送。本文解析其架构原理、技术实现与工程挑战,揭秘抖音等平台如何用AI抓住你的注意力。
2285 7
从零构建短视频推荐系统:双塔算法架构解析与代码实现
|
9月前
|
机器学习/深度学习 算法 安全
【无人机三维路径规划】基于非支配排序的鲸鱼优化算法NSWOA与多目标螳螂搜索算法MOMSA求解无人机三维路径规划研究(Matlab代码实现)
【无人机三维路径规划】基于非支配排序的鲸鱼优化算法NSWOA与多目标螳螂搜索算法MOMSA求解无人机三维路径规划研究(Matlab代码实现)
407 5
|
9月前
|
机器学习/深度学习 算法 安全
【无人机三维路径规划】多目标螳螂搜索算法MOMSA与非支配排序的鲸鱼优化算法NSWOA求解无人机三维路径规划研究(Matlab代码实现)
【无人机三维路径规划】多目标螳螂搜索算法MOMSA与非支配排序的鲸鱼优化算法NSWOA求解无人机三维路径规划研究(Matlab代码实现)
314 0
|
9月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
920 1
|
9月前
|
算法 搜索推荐 Java
贪心算法:部分背包问题深度解析
该Java代码基于贪心算法求解分数背包问题,通过按单位价值降序排序,优先装入高价值物品,并支持部分装入。核心包括冒泡排序优化、分阶段装入策略及精度控制,体现贪心选择性质,适用于可分割资源的最优化场景。
491 1
贪心算法:部分背包问题深度解析
|
9月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
9月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率

热门文章

最新文章

推荐镜像

更多
  • DNS