搜索:暴力法算法思想的具体实现
搜索:通用的方法,一个问题如果比较难,那么先尝试一下搜索,或许能启发出更好的算法
技巧:竞赛时遇到不会的难题,用搜索提交一下,说不定部分判题数据很弱,得分了
暴力法
把所有的可能性都列举出来,一一验证,简单直接
利用计算机强大的计算能力和存储能力
依赖的是递归
深度优先搜索
Depth First Search 即 DFS,意为深度优先搜索,是所有的搜索手段之一。它是从某个状态开始,不断进行状态转移,直到不能转移后,向后回退,一直到遍历完所有的状态。
深度优先搜索基本概念
作为搜索算法的一种,DFS 主要是用于解决 NP 完全问题。但是,深度优先搜索算法的时间复杂度较高,深度优先搜索是 O ( n ! ) O(n!) O(n!) 的阶乘级算法,它的效率非常低,在数据规模变大时,此算法就难以解决当前的问题了。
所以搜索算法使用于状态节点较小规模的问题。
DFS 的设计步骤
按照定义设计:
- 确定该题目的状态(包括边界)
- 找到状态转移方式
- 找到问题的出口,计数或者某个状态
- 设计搜索
DFS基础:递归和记忆化搜索
形式上,递归函数是自己调用自己,是一个不断重复的过程
递归的思想是把大问题逐步缩小,直到变成最小的同类问题的过程,而最后的小问题的解是已知的,一般是给定的初始条件
到达最小问题之后,再回溯,把小问题的解逐个带回给更大的问题,最终大问题也得到了解决
递归有两个过程:递归前进、递归返回
在递归过程中,由于大问题和小问题的解决方法完全一样,那么大问题的代码和小问题的代码可以写成一样
一个递归函数,直接调用自己,实现了程序的复用
DFS的代码框架
ans //答案,用全局变量表示
def dfs (层数 (状态), 其他参数):
if (条件判断) //到达最底层(达到最终状态),或者满足条件退出
更新答案 //答案一般用全局变量表示
return //返回到上一层
剪枝 //在进一步DFS之前剪枝
for (枚举下一层可能的情况):
//对每一个情况继续DFS
if (used[i] == 0):
//如果状态i没有用过,就可以进入下一层
used[i] = 1 //称为保存现场,占有现场
//标记状态i,表示已经用过,在更底层不能再使用
dfs(层数+1, 其他参数)
//下一层
used[i] = 0 //称为恢复现场,释放现场
//恢复状态,回溯时,不影响上一层对这个状态的使用
return //返回到上一层
剪枝
题目中给了要求是y<30
当扩展到y=29这个点以后,就不需要继续往后扩展了
伪代码:
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
bool pd(参数){
相应操作
}
void dfs(int step)
{
判断边界pd()
{
不在边界内,即回溯
}
尝试每一种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
DFS:剪枝
剪枝:把不会产生答案的,或不必要的枝条剪掉
剪枝的关键:剪什么枝、在哪里剪
剪枝是搜索常用的优化手段,常常能把指数级的复杂度,优化到近似多项式的复杂度
可行性剪枝:对当前状态进行检查,如果当前条件不合法就不再继续,直接返回
搜索顺序剪枝:搜索树有多个层次和分支,不同的搜索顺序会产生不同的搜索树形态
最优性剪枝:在最优化问题的搜索过程中,如果当前花费的代价已超过前面搜索到的最优解,那么本次搜索已经没有继续进行下去的意义,停止对当前分支的搜索
排除等效冗余:搜索不同的分支,最后的结果是一样的,那么只搜一个分支就够了
记忆化搜索:在递归的过程中,有许多分支被反复计算,会大大降低算法执行的效率。将已经计算出来的结果保存起来,以后需要用到的时候直接取出结果,避免重复运算,从而提高了算法的效率
DFS 题目讲解
1. 状态搜索代表: N 皇后问题
题目链接
难度: 简单
标签: DFS
题目描述:
在N×N的方格棋盘放置了N 个皇后,使得它们不相互攻击(即任意 22 个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成 45 角的斜线上。你的任务是,对于给定的N,求出有多少种合法的放置方法。
输入描述:
输入中有一个正整数 N≤10,表示棋盘和皇后的数量
输出描述:
为一个正整数,表示对应输入行的皇后的不同放置数量。
输入输出样例:
示例:
输入:
5
输出:
10
运行限制:
最大运行时间:1s
最大运行内存: 256M
解题思路:
二维搜索问题,有一个x坐标一个y坐标
下面是用递归的深度优先搜索求解 n 皇后问题的算法描述:
这里用一个 N×N 的矩阵来表示棋盘,但是我们不需要定义这样的数组,只要心中有 N×N 的棋盘即可。
- 算法开始:
当前行设为第一行,当前列设为第一列,从第一行第一列开始搜索,即只能让皇后从第一行放到第 n 行。
这样在每次判断是否满足情况时我们不用去判断是否皇后在相同行。
我们只用判断之前的 1 到 a−1 个皇后的位置和当前第 a 个皇后的位置是否属于同一列或者斜线,判断是否同一列。 - 判断边界:
在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第 5 步,即不符合边界条件。
首先说一下,什么叫不符合边界条件,不只是跳出了搜索范围,剪枝也可以从这里开始,比如这里不满足条件,向下继续搜索也不会再有结果。
这可以理解为超出边界的剪枝,我们的边界只得可能存在解的范围,这里已经超出有解的范围,必然要被踢出。
判断条件:
我们用数组 x [ a ] = i x[a]=i x[a]=i 来表示第 a 个皇后的位置在第 a 行第 i 列,我们不用考虑是否在同一行的问题你,我们只用判断之前的 1 到 a−1 个皇后的位置和当前第 a 个皇后的位置是否属于同一列或者斜线。
判断是否属于同一列: 就判断 x [ a ] x[a] x[a] 是否等于 x [ i ] x[i] x[i]; 判断是否属于同一斜线:等同于判断行之差是否等于列之差也,即 a b s ( x [ k ] − x [ i ] ) ≠ a b s ( k − i ) x [ k ] ≠ x [ i ] \begin{array}{} abs(x[k]−x[i])\ne abs(k−i)\\ x[k]\ne x[i] \end{array} abs(x[k]−x[i])=abs(k−i)x[k]=x[i] - 搜索过程:
调用Check函数。
如果 边界条件,就继续调用放下一个皇后的位置 Check函数:
如果当搜索到第 N+1 行的时候,即代表前 N 行已经搜索完了,所以这个时候正好求出了一个解,记录加一。- 在当前位置上不满足条件的情形,进行回溯
搜索过程
![![[Pasted image 20240310171129.png]]](https://i-blog.csdnimg.cn/blog_migrate/2e917d7b200a49495ec5587c34d59e73.png)
回溯,把放过的收起来
不是直接回溯到最开始,而是从第五行开始往第一行走,一层一层回溯,回到之前的每一行,继续往下一列走,判断有没有新的解,没有的话,就继续往前回溯
![![[Pasted image 20240310171558.png]]](https://i-blog.csdnimg.cn/blog_migrate/af38e165cc7fe903c2902e1655792232.png)
回溯到第二行,找到新的解,停止回溯,往后搜索,寻找新的解
![![[Pasted image 20240310171716.png]]](https://i-blog.csdnimg.cn/blog_migrate/98d6f28fb1b3776df4c205ae88fddc0a.png)
找到新的解,ans+1,第五行没有别的解了,往前回溯
代码
C++ 语言描述:
占用的代码
#include <iostream>

最低0.47元/天 解锁文章

238

被折叠的 条评论
为什么被折叠?



