搜索
搜索,就是查找解空间,它是“暴力法”算法思想的具体实现。
文章目录:
01 搜索简介
02 搜索算法的基本思路
03 BFS的性质和代码实现
04 DFS的常见操作和代码实现
DFS的常见操作
DFS代码框架
05 BFS和DFS的复杂度
06 BFS和DFS基本题目
01
搜索简介
暴力法(Brute force,又译为蛮力法):把所有可能的情况都罗列出来,然后逐一检查,从中找到答案。这种方法简单、直接,不玩花样,利用了计算机强大的计算能力。
搜索是“通用”的方法。一个问题,如果比较难,那么先尝试一下搜索,或许能启发出更好的算法。竞赛的时候,遇到不会的难题,如果有时间,就用搜索提交一下,说不定判题数据很弱,就通过了。
搜索的思路很简单,但是操作起来也并不容易。一般有以下操作:
(1)找到所有可能的数据,并且用数据结构表示和存储。常用的搜索算法是BFS和DFS。
(2)优化。尽量多地排除不符合条件的数据,以减少搜索的空间。
(3)用某个算法快速检索这些数据。
02
搜索算法的基本思路
搜索的基本算法是:深度优先搜索(DFS, Depth-First Search)、宽度优先搜索(BFS, Breadth-First Search,或称为广度优先搜索)。
这两个算法的思想,用老鼠走迷宫的例子来说明,又形象又透彻。
迷宫内部的路错综复杂,老鼠从入口进去后,怎么才能找到出口?有两种不同的方法:
(1)一只老鼠走迷宫。它在每个路口,都选择先走右边(当然,选择先走左边也可以),能走多远就走多远;直到碰壁无法再继续往前走,然后往回退一步,这一次走左边,然后继续往下走。用这个办法,能走遍所有的路,而且不会重复(回退不算重复走)。这个思路,就是DFS。
(2)一群老鼠走迷宫。假设老鼠是无限多的,这群老鼠进去后,在每个路口,都派出部分老鼠探索所有没走过的路。走某条路的老鼠,如果碰壁无法前行,就停下;如果到达的路口已经有别的老鼠探索过了,也停下。很显然,所有的道路都会走到,而且不会重复。这个思路,就是BFS。BFS看起来像“并行计算”,不过,由于程序是单机顺序运行的,所以,可以把BFS看成是并行计算的模拟。
简洁地说:BFS是“逐层扩散”,DFS是“一路到底、逐层回退”。
下面用一棵二叉树为例子,演示BFS和DFS的访问顺序。
图1 一棵二叉树
(1)BFS的访问顺序是:{E B G A D F I C H},即“第1层E–第2层BG–第3层ADFI–第4层CH”。
(2)DFS的访问顺序,设先访问左节点,后访问右节点,那么访问顺序是:{E B A D C G F I H}。需要注意的是,访问顺序不是输出顺序。例如上面的二叉树,它的中序遍历、先序遍历、后序遍历都不同,但是对节点的访问顺序是一样的(实际上就是先序遍历)。具体操作,见下一节的代码。
03
BFS的性质和代码实现
BFS和DFS的实现:“BFS=队列”,“DFS=递归”。
为什么“BFS=队列”呢?以老鼠走迷宫为例,从起点s开始,一层一层地扩散出去。处理完离s近的第i层之后,再处理第i+1层。这一操作用队列最方便,处理第i层的节点a时,把a的第i+1层的邻居,放到队列尾部即可。
队列内的节点有2个特征:
(1)处理完第i层后,才会处理第i+1层;
(2)队列中最多有2层节点,其中第i层节点都在第i+1层前面。
下面给出BFS遍历图1二叉树的代码。分别给出了静态版和指针版二叉树的代码,竞赛中一般用静态版二叉树,不易出错。两个代码都使用STL的queue队列。
两个代码的输出都是:E B G A D F I C H。
BFS(静态版二叉树)
#include
using namespace std;
const int maxn = 100005;
struct Node{
//静态二叉树
char value;
int lchild, rchild;
}node[maxn];
int index = 0; //记录节点
int newNode(char val){
node[index].value = val;
node[index].lchild = -1; //-1表示空
node[index].rchild = -1;
return index ++;
}
void insert(int &father, int child, int l_r){ //插入孩子
if(l_r == 0) //左孩子
node[father].lchild = child;
else //右孩子
node[father].rchild = child;
}
int buildtree(){ //建一棵二叉树
int A = newNode('A');int B = newNode('