第四章 搜索基础
1、dfs
⭐️深度优先搜索
深度优先搜索(DepthFirst Search,DFs),在图论中是一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。
在搜素算法中,DFS是指利用递归函数方便地实现暴力枚举的算法,与图论中的DFS算法有一定相似之处,但并不完全相同。
⭐️回溯法
回溯法(Backtracking)是深度优先搜索(dfs)的一种。它系统地搜索所有可能的解,常用于解决组合问题、排列问题、子集问题以及图的遍历等。其基本思想是通过递归的方式尝试构建解,并在发现当前路径无法形成有效解时,及时“回溯”到上一个步骤,尝试其他可能的路径。
回溯法的基本思路
选择:在每一步选择一个可能的选项(例如,选择一个数字、字符或状态)。
约束:在选择后,检查当前状态是否满足约束条件(例如,是否满足题目要求)。
目标:如果当前状态满足目标条件,记录结果(即找到一个解)。
回溯:如果当前状态无法继续前进,撤回上一步选择,尝试其他可能的选项。
📖
📚
int func(int i){
if(i == 1 || i == 2) return 1;
return func(i-1) + func(i-2);
}
在用dfs实现代码时,新手最好对于每一题都画出递归树,了解dfs的运行顺序。从而更好掌握dfs。
例如对于斐波拉契数列,我们有递归公式f(n)=f(n-1)+f(n-2),我们可以画出递归树。
标记出递归树各函数执行完毕的顺序。
注意 执行完毕 与 执行的顺序 区分开来。
📖dfs实现指数型枚举
选择:在每一步选择一个可能的选项(例如,选择一个数字、字符或状态)。
对于每一个数字,我有选和不选两种方式。对于一个数字判定完后,我们需要判定下一个数字是否选和不选。
约束:在选择后,检查当前状态是否满足约束条件(例如,是否满足题目要求)。
选择后,判断当前数字选择了几位。
目标:如果当前状态满足目标条件,记录结果(即找到一个解)。
当我进入到第n+1位,代表前n位已经选择好了,则输出结果。
回溯:如果当前状态无法继续前进,撤回上一步选择,尝试其他可能的选项。
即我们完成了选择,或者该层递归代码实现完成,我们进行回溯到上一步,并恢复进入该层递归时的状态。
📚
static void dfs(int u){
if(u>n)[
for(int i=1;i<=n;i++){
if(st[i]) System.out.print(i+" ");
}
System.out.println();
return;
}
dfs(u+1);
st[u]=true;
dfs(u+1);
st[u]=false;
}
2、二进制搜索
什么是二进制搜索?
⭐️二进制搜索
首先一个二进制数中只包含0,1,在二进制枚举中0代表不选当前元素,1代表选当前元素。
例如对于一个长度为5的集合{1,2,3,4,5},我们选择1,3,5这3个元素:
那么当前选择的状态就可以用二进制10101来表示,也就是十进制中的21。
什么是二进制搜索?
一种搜索所有可能组合的技巧,通常用于解决一些组合问题、子集问题或状态空间搜索问题。它利用二进制数对集合的子集进行标记,从而有效地生成所有可能的组合。比如:
- 数字0的二进制是000,表示所有物品都不选。
- 数字5的二进制是101,表示选第0和第2个物品,不选第1个。
这样可以枚举出所有可能的选择组合。
3、bfs
什么是BFS?
BFS(广度优先搜索,Breadth-First Search)是一种图或树的遍历算法。其一般用于求解边权相等的最短路以及最小操作数问题。该算法时间复杂度为O(点数+边数)
从一个节点开始,探索所有相邻的节点,然后以此向外扩展至更远的节点,逐层进行。
BFS算法我们用队列进行实现,一般情况下需要完成以下四步:
- 初始化队列:将起始节点放入队列中。
- 从队列中取出一个节点,访问该节点,并将其所有未访问的邻居节点加入队列。
- 确保每个节点只被访问一次,以避免死循环(特别是在处理有环的图时)。
- 重复上述步骤,直到队列为空。
总体的复杂度时O(n+m),n是访问点的复杂度,m是访问边的复杂度。
BFS(graph, start):
queue =空队列
visited=空集合
queue.add(start)
visited[start]=true;#初始化
#开始遍历
while queue 不为空:
#从队列中取出当前节点
current = queue.poll()
if(current=end){ #如果答案找到,直接退出函数
return current;
}
for each neighbor in graph[current]:#处理current节点可以是打印、搜集信息等
if neighbor 不在visited:
queue.add(neighbor) #将邻居加入队列
visited.add(neighbor) #标记为已访问
return false #遍历结束,所有节点都已经访问过,均没有找到答案
📖走迷宫
第一个问题:能否看成一张图?将这个01迷宫看成是一张图,每个格子都能走到他上下左右四个格子,每个结点相连,最后形成一张图。
第二个问题,这张图是否为一张边权为1的图呢?从一个格子走到另一个格子,所花费步数是1,因此这是一张边权为1的图。
综上,启发我们用bfs解决问题。
但是我们还要解决几个问题:
1、怎么样得到当前结点的相邻结点?
2、怎么判断相邻的结点是合法的?
📚走迷宫
📖最少操作数
📚最少操作数
搜索基础代码模板
⭐️dfs
static void dfs(){
if(递归终止条件){
return;//结束递归
}
//更新状态进行下一次递归
dfs();
//回溯状态进行下一次递归
dfs();
}
复杂度取决于递归树。
⭐️二进制搜索
复杂度为 O(2n),枚举只有两种状态(选/不选)的全部方案。
for(int i = 0 ; i <= (1<<n)-1 ; i ++){
for(int j = 0 ; j < n; j++){ // 枚举0 ~ n-1 位
if((i>>j)%2== 1){ // 判断i的第j 位是否是1,即是否被选择
//demo
}
}
}
⭐️bfs
等权图最值问题最短路。
static void bfs(){
Queue<自定义类型> q=new ArryaDeque<>();
//初始化状态
q.offer(起点);
st[起点]=true;
while(!q.isEmpty){
自定义类型 t=q.poll();
if(t==终点){
结束bfs;
return;
}
遍历当前点所能到达的其余点;
for(int v:剩余点){
if(!st[v]){//剩余点没访问过的入队
q.offer(v);
st[v]=true;
}
}
}
没有找到答案且题有需求输出-1
}