欢迎来到算法学习的精彩世界!本篇博客将带你深入探索两种基础但强大的算法思想------枚举搜索与素数判断。无论你是刚踏入算法大门的新手,还是想要巩固基础的进阶者,这些知识点都将为你的编程之路打下坚实基础。
枚举搜索做题知识点补充
试除法判断素数
我们用试除法判断素数的时候,我们用for循环遍历2到√x,具体代码实现我们可以这样写
cpp// x:要判断的数 bool isprime(int x) { // 若x小于等于1,则不满足素数条件 if(x <= 1) return false; // 让 x 对 2 到 √x 之间的数取模 for(int i = 2;i <= x /i;i++) { // x 能被小于等于√x的数整除,x不为素数 if(x % i == 0) return false; } // x为素数 return true; }其中for循环条件我们可以改为以下两种,但是没有第一种判断方式的循环条件方便
cpp// ① i^2 <= x for(int i = 2;i*i <= x;i++) //缺点:如果i过大,会导致溢出 // ① i <= sqrt(x) for(int i = 2; i <= sqrt(x);i++) //缺点:需要再单独展开<cmath>头文件(实际算不上绝对的缺点,只是相对第一种而言)
对于素数判断这一问题,我们也可以利用线性筛这个问题去预处理我们要判断的数据,大大降低逐个判断的时间复杂度,有兴趣的可以私下去了解。
搜索算法基本介绍
搜索,是⼀种枚举,通过穷举所有的情况 来找到最优解 ,或者统计合法解的个数 。因此,搜索有时候 也叫作暴力搜索(简称暴搜)。
搜索⼀般分为深度优先搜索(DFS) 与宽度优先搜索(BFS)。
关于应该称呼这一类的算法为优先遍历 还是优先搜索 ,我们不必细究:遍历是形式,搜索是⽬的。
不过,在⼀般情况下,我们不会去纠结概念的差异,两者可以等同。
回溯与剪枝
- 回溯:当在搜索的过程中,遇到走不通 或者走到底的情况时,就回头。
- 剪枝:剪掉在搜索过程中,剪掉重复出现 或者不是最优解的分⽀。
dfs(深度优先搜索)
深度搜索的基本步骤如下:
- 先画枚举树,然后根据枚举树去写搜索代码。
- 数据范围如果特别小,就可以考虑枚举搜索。
- 在枚举情况时候,要视情况剪枝。
决策树
清空现场
我们在做每次决策时,都只是一种尝试,并不代表这个决策就一定符合我们的需求,所以在做了某一个决策的时候,我们应当清空现场(撤销当前决策),然后更换为下一种决策再做尝试。
可以参考下一题的示例代码参考清空现场操作。
例题
代码
cpp
#include<iostream>
#include<string>
using namespace std;
string path;
int n;
void dfs(int pos)
{
if(pos > n)
{
cout << path << endl;// 打印最终决策
return;
}
path += 'N';// 做决策
dfs(pos+1);
path.pop_back();// 清空现场(撤销决策)
path += 'Y';// 做决策
dfs(pos+1);
path.pop_back();// 情况现场(撤销决策)
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
dfs(1);
return 0;
}
剪枝与优化
剪枝,形象地看,就是剪掉决策树的分支,减少我们执行的情况,通过减少搜索重复情况的方式,降低我们算法的时间复杂度。
剪枝的情况有很多种,我们可以大致分为以下五种:
1. 排除等效冗余
当我们在进行不同决策情况的遍历时,发现出现了已经出现过了的决策(经过处理之后可以视作等效的决策),我们就可以进行剪枝,不再继续往下执行。
2. 可行性剪枝
在遍历决策树的过程中,我们可以提前对我们要遍历的决策进行判断,如果我们即将执行的决策已经从可行性角度来看被否决,我们就没有再执行下去的必要了。
3. 最优性剪枝
在找最优化结果的过程中,我们发现某一个决策的最优性还不如或者等于之前已经遍历搜索过的最优解,我们也就没有必要遍历下去了,因为再遍历下去也不会达到我们想要的最优结果了。
4. 优化搜索顺序
在遍历搜索的过程中,搜索顺序并不会影响我们最终的结果,但是时间复杂度会受搜索顺序的影响,为此,我们应当通过调整我们做不同决策的顺序,区分出不同决策的优先级来优化搜索顺序,先快速的找到一个最优解用于剪去接下来遍历搜索中多余的分支。
5. 记忆化搜索
记录每一种状态的搜索结果,当下一次再次搜索到这个状态时,直接找到之前记录过的搜索结果。记忆化搜索在某些情况下也叫做动态规划。
bfs(广度优先搜索)
宽度优先搜索的过程中,每次都会从当前点向外扩展⼀层,所以会具有⼀个最短路 的特性。因此,宽搜不仅能搜到所有的状态,⽽且还能找出起始状态距离某个状态的最小步数。
但是,前提条件是每次扩展的代价都是 1,或者都是相同的数。
宽搜常常被用于解决边权为1的最短路径问题。
bfs图示
多源bfs
• 当问题中只存在**⼀个起点时,这时的最短路问题就是单源最短路问题。
• 当问题中存在多个起点而不是单⼀起点时,这时的最短路问题就是多源最短路问题。
多源最短路问题的边权都为** 1 时,此时就可以用多源 BFS 来解决。
实现方式
把这些源点汇聚在⼀起,当成⼀个"超级源点"。然后从这个"超级源点"开始,处理最短路问题。
- 初始化的时候,把所有的源点都加⼊到队列⾥⾯;
- 然后正常执⾏ bfs 的逻辑即可。
主要的区别就是初始化的时候,比普通的 bfs 多加入几个起点。
01 bfs
01 bfs和一般的bfs区别主要就是,01 bfs的问题中,你路径的边权是有可能为 0 的。
而且在01 bfs问题中,你到达某一点的路径值是有可能发生变化的,也就是说我们之前抵达某一个节点的路径值可能会在后面发生变化,这里接涉及到了松弛操作。
01bfs问题
松弛操作
松弛操作就是当我们在对后面的节点进行bfs找最短路径时,发现了一条更短的路径到达之前遍历过的节点,我们就要对路径结果进行更新,这就是松弛操作。
floodfill 问题
关于floodfill问题,主要就是处理具有统一性质的连通块问题。
小补充
- 二分的特性就是能够解决最大值中找最小的问题
算法学习如同登山,每一步都算数。今天的基础知识积累,将为明天的难题突破提供坚实支撑。记住:理解比记忆更重要,实践比理论更有效。
决策树
bfs图示
01bfs问题