前言
搜索是一种适用性非常广泛的算法。很多我们实际遇到的问题,数据量可能不会太大,都可以暴力搜索一把来解决。
其实这个算法网上也很多但是都讲的比较复杂很多人也只能看一个前面的东西后面的其实看不懂。也有很多文章讲的不是太精炼,讲不出个所以然来。所以小编今天就利用这篇文章来带大家先入门搜索算法。最简单的 DFS 相当于一个暴力搜索算法。
基本上遇到的很多题目暴力都能解决,毕竟 CPU 处理的速度这么快。
正文
搜索算法是属于一种比较基础的算法,相当于万丈高楼的第一层,也是后期学习的一些高级算法的基础部分,搜索算法分为深度优先搜索(Depth First Search,DFS)和广度优先搜索(Breadth First Search,BFS)这两种。DFS 相对简单一点那就从 DFS 开始入门吧。
说到这个搜索算法就要讲到图的问题,有学过离散数学或者数据结构的都知道,图是一个点集合加上一个边的集合一个边对应两个定点,属于一种二元关系。
如果你没学过的话上面那句话省略,直接看这里:举个例子讲,微信朋友圈可以看成一个很大的图,每个人都是一个顶点,彼此是好友彼此的朋友圈就能看到,相当于彼此的可达的。这个问题一抽象化就能算出只要你的朋友圈连接着多少人了。假如你发张你的自拍,你的每个好友都转发,你好友的好友也都转发……然后全世界都知道你长什么样子了。 所以小编的文章能怎么样还是需要你们这些第一批种子用户的。
直接形象化的上图
了解什么是图了,那么我们从图的遍历开始讲,给定一个包含 N N N个顶点的图,以及图上的 M M M条边。让你遍历图中的每一个顶点恰好一次。图的遍历有很多用途,比如判断一个图是不是连通的。和判断你的微信能不能连接全世界是一样的。
我们说说到一个算法首先考虑的是他的实现,要实现的话就得要存储,看问题我们是有两个集合(点集和边集)集合一般的思想就是通过一个数组集合来存储,通过对点集的标号,找到与数组下标的关系,我们就能实现存储。
其实我们有两种方法来存储边集。一种叫做邻接矩阵表示法,另一种叫邻接表表示法。
邻接矩阵是说我们用一个二维矩阵A来表示边集。
A
i
,
j
=
0
A_{i,j}=0
Ai,j=0表示顶点
i
i
i和顶点
j
j
j之间不存在边,
A
i
,
j
=
1
A_{i,j}=1
Ai,j=1表示顶点i和顶点j之间存在边。如果我们用邻接矩阵表示上图,是这个样子:
在程序中,我们一般用一个二维数组来表示邻接矩阵:
i
n
t
int
int
a
[
N
+
1
]
[
N
+
1
]
a[N+1][N+1]
a[N+1][N+1]。
a
i
,
j
=
=
1
a_{i,j}==1
ai,j==1表示
i
i
i和
j
j
j之间有边相连,
a
i
,
j
=
=
0
a_{i,j}==0
ai,j==0表示
i
i
i和
j
j
j之间没有边相连。
邻接表是图中的每一个顶点i都有一个线性表,保存与i相连的顶点编号。
如果我们用邻接矩阵表示上图,是这个样子:
在程序中,我们一般用一个数组嵌套vector的方法来表示邻接表:
v
e
c
t
o
r
<
i
n
t
>
vector<int>
vector<int>
g
[
N
+
1
]
g[N+1]
g[N+1],里面保存着每一个与i相邻的顶点编号。
上面讲了什么是搜索,如何存储一个图。那么重头戏来了我们不妨设从1号顶点起始。在搜索过程中,我们维护一个布尔数组 b o o l bool bool v i s i t e d [ N + 1 ] visited[N+1] visited[N+1],这个数组用来表示每个顶点是不是已经遍历过了。 v i s i t e d i = = t r u e visited_i==true visitedi==true表示顶点 i i i已经遍历过了, v i s i t e d i = = f a l s e visited_i==false visitedi==false表示i还没有遍历过。DFS一般我们可以用递归实现,如果采用邻接矩阵,伪代码如下:
Visited[] = {FALSE, FALSE, ...FALSE}
DFS(x):
Visited[x] = TRUE
For i = 1 .. N:
If !Visited[i] AND A[x][i]:
DFS(i)
当我们执行 D F S ( 1 ) DFS(1) DFS(1)的时候,程序就会开始从1号节点开始遍历,每一次都对搜索过的书标记一直到全部被标记完为止。而每一次DFS执行中都要i循环从 1 1 1到 N N N遍历一遍。所以整个复杂度是 O ( N 2 ) O(N^2) O(N2)的。