chapter9-搜索
搜索是一种有目的地枚举问题的解空间中部分或全部情况,进而找到解的方法。它的定义是:
起始状态经过一系列的状态转移抵达目标状态,我们一般用搜索树(Search Tree)来表示状态转移
搜索一般包括4个部分:
-
1、状态空间,也叫解空间;
-
2、状态转移;
-
3、起始状态;
-
4、目标状态。
如何构思上面的四个部分呢,比如状态空间是什么,举个例子:
1.Search Trees
以寻找从点A到点E的路径问题为例,建立一颗搜索树,建好搜索树之后,还需要确定搜索策略,即先扩展树中的哪个结点,搜索策略有宽度优先搜索、深度优先搜索两种。
2.BFS
策略:每次优先处理当前所有未处理状态中深度最浅的一个状态,即处理最浅的结点。
采用这样的搜索策略,可以发现是一层一层从左到右的扩展,也就是先扩展的先处理,符合队列先进先出特性。
2.1 Catch That Cow
题目描述:
Farmer John has been. informed of the location of a fugitive cow and wants to catch her immediately.He starts at a point N(0≤N≤100000) on a number line and the cow is at a point K (0≤K≤100000)on the same number line. Farmer John has two modes of transportation: walking and teleporting.
Walking:Farmer John can move from any point X to the pointsX- 1 orX+ 1 in a single minute.Teleporting: Farmer John can move from any point xto the point 2X in a single minute.
输入:
Line 1: Two space-separated integers: N and K.
输出:
Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.
搜索类题目,首先写出搜索问题的4个部分:
-
1、状态空间 (位置n, 时间t)
-
2、状态转移 (n-1, t+1), (n+1, t+1), (2n, t+1),共3种移动方式
-
3、起始状态 (N, 0)
-
4、目标状态 (K, lowestTime)
然后建立一颗搜索树:
确定搜索策略:抓到牛并且花费时间最少,BFS逐层搜索符合题目要求。同时注意到,对重复位置的状态,为了提高搜索的效率,设置visit数组,不再对它进行扩展。
抓住那只牛
//2024-03-08 搜索 Catch that cow #include <iostream> #include <cstdio> #include <queue> using namespace std; const int MAXN = 1e5 + 10; bool visit[MAXN]; struct Status { int position; int time; Status(){} Status(int p, int t): position(p), time(t) {} }; int BFS(int n, int k) { queue<Status> myQueue; myQueue.push(Status(n, 0));//把搜索树的根结点压入队列 visit[n] = true; while(!myQueue.empty()) { //逐层扩展状态 Status current = myQueue.front(); if(current.position == k) { return current.time; } myQueue.pop(); for(int i = 0; i < 3; ++i) { Status next = current; if(0 == i) { next.position -= 1; } else if(1 == i) { next.position += 1; } else { next.position *= 2; } next.time += 1; if(next.position < 0 || next.position > MAXN || visit[next.position]) { continue; } myQueue.push(next); visit[next.position] = true; } } return -1; } int main() { int n, k; while(cin >> n >> k) { fill(visit, visit + MAXN, false); printf("%d\n",BFS(n, k)); } return 0; }
2.2 Find The Multiple
为了缩小状态空间,提高搜索效率,我们转换思路,搜索由0、1组成的数字中,能否被给定的数n整除
首先,写出搜索的4个部分
-
1、状态空间 0,1构成的数字number
-
2、状体转移 (number * 10),(number * 10 + 1)
-
3、初始状态 1
-
4、目标状态 number % n == 0
然后建立一颗搜索树:
Find the Multiple
//2024-03-12 Find the Mutiple 自写 #include <iostream> #include <cstdio> #include <queue> using namespace std; void BFS(int n) { queue<long long> myQueue; myQueue.push(1); while(!myQueue.empty()) { long long current = myQueue.front(); myQueue.pop(); if(current % n == 0) { cout << current << endl; return; } myQueue.push(current * 10); myQueue.push(current * 10 + 1); } } int main() { int n; while(cin >> n) { if(n == 0) break; BFS(n); } return 0; }
宽度优先搜索常被用来求解最优值问题,因为其搜索到的状态总是按照其某个关键字递增。因此,一旦问题中出现最少、最短、、最优等关键字,就要考虑是否是宽度优先搜索问题。
由于深度优先搜索并没有先入先出的特点,所以搜索到需要的状态时,该状态不再像是宽度优先搜索中的状态一样,具有某种最优的特性。因此,使用深度优先搜索策略时,常常是为了知道问题是否有解。
3.DFS
策略:优先处理最深的结点,可以发现先扩展的状态可能较后才处理,与栈的后入先出特性相符,但在实际操作中,往往使用递归的方式来实现DFS。
3.1 A Knight's Journey
像象棋一样,马这个棋字的行走规则是“日”字。
同样首先分析搜索问题的4个组成部分:
-
1、状态空间:当前游历到的点的坐标,以及总共走了多少格 (x, y, step)
-
2、状态转移:一共8种 (x-1, y-2, step+1)、(x+1, y-2, step+1)...
-
3、起始状态: (0, 0, 1) 如果存在解,一定是从A1开始搜索,使得游历的字典序最小
-
4、目标状态: (x, y, sizeof board) 在哪点终止不确定,但格子数确定
画出搜索树:
题目要求是找到一条路径能够遍历到棋盘的每一格,相当于要求搜索树的深度height与格子数量相同,并且注意要设置visit数组,之前已经访问过的格子不能再进行扩展或计入树高。
骑士的环球旅行
//2024-03-13 骑士的旅行 #include <iostream> #include <cstdio> #include <cstring> using namespace std; int direction[8][2] = { {-1, -2}, {1, -2}, {-2, -1}, {2, -1}, {-2, 1}, {2, 1}, {-1, 2}, {1, 2} }; const int MAXN = 30; //棋盘格数+5安全冗余 bool visit[MAXN][MAXN]; bool DFS(int x, int y, int step, string answer, int p, int q) { if(step == p * q) { cout << answer << endl << endl; return true; } //步数没到棋盘尺寸,就要进行扩展,有8种扩展方式 for(int i = 0; i < 8; ++i) { int nx = x + direction[i][0]; int ny = y + direction[i][1]; if(nx < 0 || nx >= p || ny < 0 || ny >= q || visit[nx][ny]) { continue; } visit[nx][ny] = true; char col = ny + 'A'; char row = nx + '1'; if(DFS(nx, ny, step + 1, answer + col + row, p, q)) { return true; } visit[nx][ny] = false; //不再访问这个状态,这个状态下的所有扩展状态均无解 } return false; } int main() { int n; scanf("%d", &n); int caseNumber = 0; while(n--) { int p, q; cin >> p >> q; memset(visit, false, sizeof(visit)); cout << "Scenario #" << ++caseNumber << ":" << endl; visit[0][0] = true; if(DFS(0, 0, 1, "A1", p, q)) {//压入初始状态进行处理 continue; } else { cout << "impossible" << endl << endl; } } return 0; }
3.2 Square
给出若干根长度不一的木棍,问它们能否拼成一个正方形,涉及搜索树减枝,这个例子先留个坑。
首先看搜索问题的4个部分:
-
1、状态空间: (sum, number) sum指当前已拼凑木棍的长度,第二个number,指已经拼凑成为正方形边长的个数
-
2、状态转移: (sum + stick[i], number),有可能发现状态变异-> (0, number + 1)
-
3、初始状态: (0, 0)
-
4、目标状态: (0, 4)
画出搜索树,由于扩展的状态数目由木棍数目决定,所以不定:
每往下搜索一层,就多用掉一根木根,所以可以判定木棍是否用完,以及最终拼凑出的边长个数是否等于4,所以应该用DFS搜索策略。
因为每个木棍只能使用一次,故要一个visit数组记录是否使用过这根木棍。
留个坑,DFS啊~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具