夏令营讲课内容整理 Day 4.

本日主要内容就是搜索(打暴力

搜索可以说是OIer必会的算法,同时也是OI系列赛事常考的算法之一。

有很多的题目都可以通过暴力搜索拿到部分分,而在暴力搜索的基础上再加一些剪枝优化, 就有可能会拿到更多的分数。

有句话说的好嘛,骗分过样例,暴力出奇迹。

真的可以出奇迹的,只要你用得好。

1.搜索的概念

在一个给定的空间内,运用一定的查找(遍历)方式,直到找到目标解(状态)的过程,我们称之为搜索。

搜素是尝试性的,搜索是无脑的,搜索是朴素的,搜索在很多时候是显然的,搜索应该总是暴力的。但搜索也是很常用的。

通常我们要把搜索的解空间划分成几个阶段(可能是抽象的),或者说是若干个状态,然后去按层或者按深度进行遍历,并尝试寻找可能的解。

搜索树。何为搜索树?我们想象一个搜索的阶段,首先,我们在初始位置作为起点开始我们的搜索过程,每到达下一个阶段,这棵树便会向下扩展。在同阶段扩展其他节点,这棵树便会拥有分叉,直到我们遍历完整个解空间,我们搜索的整个过程便记录在了搜索树里,而我们要找的解应该就在搜索树的某个叶子节点上,或者宣告无解。

2.常见的几类搜索问题

    • 排列问题:枚举1~n的排列
    • 组合问题:少年,你的这个物品,是要选呢?还是不选呢?
    • 路径问题:神奇海螺告诉我,下一步我该怎么走?

3.编写搜索算法时我们应该考虑到的问题

    • 我应该用什么搜索算法
    • 我这样写能不能保证一定出解
    • 我这样写找到的解是不是最优
    • 如果不是最优,那要如何才能找到最优解
    • 求解的效率如何(别想太多,搜索的时间复杂度一般都是指数级

我重点说一下深度优先搜索(DFS)和广度优先搜索(BFS)。

还有迭代加深算法和A*等略微丧病的搜索,我也会提一下

(今年没讲双向广搜啊。。

(当然也会有爬山法,模拟退火法,遗传算法等玄学搜索算法,仅供装逼

4.DFS

不撞南墙不回头,撞了就掉头,一冲到底不服输。

(好了DFS讲完了

我才没有那么不负责任。。

深度优先搜索,我们的思路就是只要能扩展,就一直向深处扩展,直到走不动,这时候要进行一步「回溯」操作。这个操作通常用递归来进行,当然也有非递归形式,不过不常用。

深度优先搜索的优点是代码量通常相对比较少,框架相对固定,而且比较容易理解,占用空间较小。在数据量比较少的时候可以很快出解。

但它的缺点也是明显的,在数据量大的时候遍历整个解答树会非常慢,递归层数过多也有可能引起爆栈。并且,找到的第一个解在某些时候并不一定是最优解,而只是可行解之一。

很多书上喜欢给一个DFS的大体框架,用来告诉初学者这种算法大概长什么样。那我也给出一个大体的DFS框架吧。。

 1 void dfs(int dep){
 2     if (dep==n+1){
 3         //执行输出操作
 4         return;
 5     }
 6     //可以在这里加一些特殊边界什么的,最后直接return就好
 7     for 枚举每个可能的位置或者决策或者方案{
 8         //可以在这里加一些剪枝什么的
 9
10         if (这个方案合法){
11             打标记
12             dfs(dep+1);
13             删除标记//这个时候已经回溯了
14         }
15
16     }
17 }

其实不难理解。递归进入下一层,判断是否已经到达边界,如果还不是边界就按照一定的规则,取遍所有可能的方案,作为当前状态。

同时我们可能需要「打标记」操作方便我们「回溯」。打标记,便是记录某个数或者某个点是否走过,当我们回溯的时候应该把这个标记擦掉,避免影响之后的搜索。

多思考思考。

举个例子吧。99%的OIer都会的DFS:生成全排列

我会我会!next_permutation!

。。。把那个用STL的拖出去毙了,这里是搜索专场!

 1 void dfs(int now) {
 2     if (now == n) {
 3         for (int i = 0; i < n; i++)
 4             cout << a[i] << " ";
 5         cout << endl;
 6         return;
 7     }
 8     for (int i = 1; i <= n; i++)
 9         if (!check[i]) {
10             a[now] = i;
11             check[i] = true;
12             dfs(now + 1);
13             check[i] = false;
14         }
15 }

当然,生成全排列也可以用栈,非递归地完成。不过这不常用。

思考:如果我要生成1~n的组合呢?

再来一个99%的OIer都见过的例题,八皇后。

下过国际象棋吧?没下过也没问题,这题不需要你了解多少国际象棋规则。

一个8*8的棋盘,摆放八个皇后,要求这八个皇后中的任意两个不能位于同行同列同对角线,求方案数。

答案是92.

如果你打算实现一下这个搜索解的过程,并且不加任何的优化,也就是说,把八个皇后在所有可能的位置都枚举一遍,并判断是否可行。我可以说的是,上述方法完全正确,但是。。这程序可能要跑个一两年吧。。。

考虑一下优化。既然要求任意两个皇后不能位于同行同列同对角线,那么我们在放第n+1个皇后时,肯定不能在第n个皇后的同一行和同一列,在搜索即将要扩展至此的时候肯定不可行,我们就应该「回溯」。这样只需要判断任意两个皇后是否处在同一对角线就好了。

给一个伪代码:

 1 void dfs(int dep){
 2     if (dep==n+1){
 3         //输出方案
 4         return;
 5     }
 6     for 枚举第dep行的皇后的可能位置{
 7
 8         if (这个位置合法){
 9             打标记
10             dfs(dep+1);
11             删除标记
12         }
13
14     }
15 }

5.Floodfill算法

又叫灌水法,填充法。

来一道例题:有一个n * m的点阵,有一些点是陆地,其他点是海洋,一共有多少块陆地?

这个算法其实类似于DFS,若要找到某个点所在的起点,就以此点开始DFS,遍历与它联通的所有点,如果枚举到的点没有被访问过并且是陆地,就可以打标记扩展。

6.BFS

按层搜索,广度优先,队列储存,首解最优。

其实可以这样想一下遍历解答树的过程:从根节点出发,找到所有与之相连的第一层节点,依次遍历,遍历的同时找到扩展到的下一层节点,储存起来方便下一步搜索,这样就是一个正常向的BFS过程。

一层一层地慢慢展开,每个节点到根节点的距离是慢慢增加的,而不是像DFS那样一直走到最深处再考虑往回走,所以BFS找到的第一个解一般就是最优解。

缺点的话,占用空间是比DFS大一些的,而且框架比DFS要长一些。。

之前说到的拓扑排序,便是一个BFS的过程。

  大致的框架:

  

 1 void bfs(int s){
 2     queue<int> q;
 3     q.push(s);
 4     vis[s] = true;
 5     while (!q.empty()){
 6         int u = q.front();
 7         q.pop();
 8         for (遍历所有与u相邻的节点){
 9             if (当前找到的扩展节点为解){
10                 执行输出操作
11             }
12
13             if (!vis[u]){
14                 q.push(u);
15                 vis[u] = true;
16             }
17         }
18     }
19     return ;
20 }

  

然后就是有一些对DFS和BFS进行优化的算法,不过实际应用当中用的比较少,我稍微写一下思想。

7.迭代加深搜索(就是那个所谓的A*

其实可以把它理解为集DFS占空间小和BFS首解最优两种特性于一体的一种搜索算法。先给DFS一个比较小的深度限制,然后逐渐增加深度限制,直到找到解或找遍所有分支为止。

8.启发式搜素

有效摒弃了DFS与BFS的无脑搜索缺点。利用一个“预判”引导搜索方向,就好像人走迷宫预先判断哪里很显然是死路那样,减小搜索范围。

启发式搜索的强度取决于我们“预判”的程度。

如果预判程度太高,虽然能大大减小工作量,但会有可能把本来应该是能找到最优解的道路剪掉,导致我们找不到最优解。

如果预判程度太低,会导致事倍功半,性能上与BFS差不了多少但是写起来可比BFS麻烦多了。所以我们要尽量合理地使用启发方式。

其实这里有一个评价函数f,用来评估我们的行走决策。这里只给出思想,如果有想深入了解此算法的同学可以自行查阅相关资料。

9.(据说可以拿来装逼的算法

爬山法,模拟退火,遗传算法

我当时是懵逼的。这我真不会。

概念什么的写的很少就是了。。搜索这东西得拿题来多写才行。。

也有可能是我太弱了吧。。

时间: 2024-10-13 10:25:35

夏令营讲课内容整理 Day 4.的相关文章

夏令营讲课内容整理Day 0.

今年没有发纸质讲义是最气的.还好我留了点课件. 第一次用这个估计也不怎么会用,但尝试一下新事物总是好的. 前四天gty哥哥讲的内容和去年差不多,后三天zhn大佬讲的内容有点难,努力去理解吧. 毕竟知识还是需要消化的. 这里我只整理知识点,每天上午评测的题目我会单独处理. 嗯大概就是这样了. 写完后我就会考虑发到博客园里.

夏令营讲课内容整理 Day 3.

本日主要内容是树与图. 1.树 树的性质 树的遍历 树的LCA 树上前缀和 树的基本性质: 对于一棵有n个节点的树,必定有n-1条边.任意两个点之间的路径是唯一确定的. 回到题目上,如果题目读入的是树上所有的边,则我们应该想到: 每个点的父亲是谁 每个点的深度 每个点距离根节点的距离 其他的附加信息(例如:子树和,子树最大值..) 遍历整个树的代码如下: 1 void dfs(int now) 2 { 3 deep[now]=deep[fa[now]]+1; 4 sum[now]=value[n

夏令营讲课内容整理 Day 5.

DP专场.. 动态规划是运筹学的一个分支, 求解决策过程最优化的数学方法. 我们一般把动态规划简称为DP(Dynamic Programming) 1.动态规划的背包问题 有一个容量为m的背包,有n个物品,每一个物品i的重量为w[i],价值为v[i]. 要求选择一些物品放入背包中,每种物品只能最多使用一次,使得在不超重的情况下让背包中所有物品价值总和最大. 正常向解法:设状态数组f[i][j]为把前i个物品放入一个容量为j的背包中所能获得的最大价值(以下同设),则状态转移方程为: f[i][j]

夏令营讲课内容整理Day 1.

主要内容是栈和队列. 1.  栈 运算受到限制的线性表.只允许从一端进行插入和删除等操作.这一端便是栈顶,另一端便是栈底. 其实可以把栈想象层任何有底无盖的柱状的容器...毕竟栈满足后进先出的特性.计算机当中调用函数时,中间结果便会保存到「系统栈」中.递归过程也需要栈的协助 . 实现:STL or 手写(请参照一本通 or 课件) 一般操作:判断栈空/满.入栈.出栈,判断栈的大小(请参照一本通 or 课件) 1.1 单调栈 顾名思义,保证内部元素单调(单增或单减)的栈.我们只要在插入新元素的时候

夏令营讲课内容整理 Day 6 Part 1.

Day6讲了三个大部分的内容. 1.STL 2.初等数论 3.倍增 Part1主要与STL有关. 1.概述 STL的英文全名叫Standard Template Library,翻译成中文就叫标准模板库. 它有点类似于一个大型的工具箱,里面包含许多实用工具,可以拿过来直接用而大部分情况下无需去深入探究其内部原理. 不知道从什么时候开始,CCF不再限制选手使用STL,所以在OI赛事中STL被广泛应用. 它分为六个大部分: 1)容器 containers 2)迭代器 iterators 3)空间配置

夏令营讲课内容整理 Day 6 Part 2.

Day 6的第二部分,数论 数论是纯粹数学的分支之一,主要研究整数的性质 1.一些符号: a mod b 代表a除以b得到的余数 a|b a是b的约数 floor(x) 代表x的下取整,即小于等于x的最大整数,也可以认为是直接舍去小数部分 (这个应该是一个符号,但我不知道怎么打出来..下面那个ceil也是) ceil(x) 代表x的上取整,即大于等于x的最小整数,也可以认为是直接舍去小数部分再+1. gcd(a,b) 表示a与b的最大公约数 lcm(a,b) 表示a与b的最小公倍数 累加符号∑

夏令营讲课内容整理 Day 2.

本日主要内容是并查集和堆. 并查集 并查集是一种树型的数据结构,通常用来处理不同集合间的元素之间的合并与查找问题.一个并查集支持三个基本功能:合并.查找和判断.举一个通俗的例子,我和lhz认识,lhz和hzc认识,那么也就可以断定我和hzc认识. 依照并查集的思想,我们把所有要待处理的元素a1,a2,a3....an这n个元素都看作是一个单独的集合,初始状态每个集合都只有一个元素.我们就可以把并查集的合并操作理解为集合之间的取并集操作. 作为一个树形结构,在一个由许多这样的集合构成的森林中,每个

Google C++ 风格指南内容整理

之前一直没有全面的看过Google C++风格指南,现在很多公司进行C++开发都要求按照Google C++风格.在这个网站 http://zh-google-styleguide.readthedocs.org/en/latest/contents/  有人已经把其翻译成中文.为了便于以后查看,下面的内容完全是来自于这个网站,只是把多个网页的内容整理放在了一起. 1.      头文件: 通常每一个.cc文件都有一个对应的.h文件.也有一些常见例外,如单元测试代码和只包含main()函数的.c

网页格式化排版代码,专用信息采集后的内容整理

public static string ClearHtml(string content) { Regex regex = new Regex(""); //首先把p标签的属性去掉,只留<p> regex = new Regex(@"<p.*?>", RegexOptions.IgnoreCase | RegexOptions.Singleline); content = regex.Replace(content, "<p