本文仅供个人记录和复习,不用于其他用途
问题引入
小学的时候我们可能做过这样一个题目:给你1-9的自然数,将它们组成三个三位数,使得其中两个数相加等于另外一个数,自然数可以重复使用。如果现在让我们编程实现这个问题,我们应该怎么做呢?不要急,让我们一步步地解决这个问题。
深度优先搜索
我们先来看看这样一个问题:写出123的全排列。什么是全排列?其实就是将各个位置的数进行交换,得到的所有可能的数。比如123的全排列就是123、132、213、231、312、321。那么,让我们用代码来实现这个全排列:
|
|
至于1234的全排列,无非就是多加一层循环,再修改一下判断条件即可。不过呢,这样一来代码就显得较为臃肿。递归一章提到,所有的循环都可以用递归来表达,那么显然我们可以用递归来简化代码。
现在假设有3个盒子,代表着全排列中对应的百位、十位、个位,我们要做的就是将1、2、3放入三个盒子中。首先我们按照顺序,将1放入1号盒子,2放入2号盒子,3放入3号盒子,形成了123这个数。产生排列之后,我们将3取走,并且尝试着向3号盒子放入其他的数。当然,我们手中只有3,并没有其他的数,所以我们只能退回到2号盒子。来到2号盒子前,我们取回2,并且尝试着放入其他的数。很显然,我们可以放入3。放入3后,我们来到3号盒子,我们可以放入2,形成排列132。
按照刚刚的步骤进行模拟,就可以得到所有的排列,下面是这个过程的代码实现:
|
|
比较关键的是数组a
和b
,a
表示盒子中有没有数,b
表示1-9这9个数有没有被使用。当达到第十个盒子时,我们就形成了一种全排列。八皇后问题和这个过程类似,将一层层的循环写进递归之中,简化了代码量。深度优先搜索之所以冠以深度的名字,那是因为这个算法讲究的是先往下探索,一直深入到无法深入后,再回退一步,探索这个深度的其他可能。然后一直回退,最后回到第一层。
广度优先搜索
现在有一位探险家降落在一个海岛上,他携带了一张地图,地图上标识了他的位置为(6, 8)。但是呢,他并不知道这个海岛的面积有多大。那么,如果让你来引导这位探险家,你会用什么样的方式来探索出海岛的面积?
这里就不得不介绍一下广度优先搜索。所谓的广度优先,指的是将同一深度探索完成后,才进入下一深度,与深度优先那种直达最深层的方式有所区别。我们的探险家降落在(6, 8)的位置,他能够走的方向有上下左右四个方向,因此,(6, 9)、(6, 7)、(5, 8)、(7, 8)就是属于同一深度,我们就先探索这四个位置。然后呢,这四个位置同样可以往四个方向移动,不过(6, 8)已经走过了,所以我们将它标记一下,以免重复探索,所以我们下一步要探索的一共有8个位置(记住,探索过的位置一定要标记)。如下图:
红色的为起始位置,黄色的是第一深度,绿色的是第二深度。广度优先搜索就是像这样展开,一直到碰到海水时结束。这样,我们就能够将整个海岛的面积计算出来。
|
|
这里我们创建了一个结构体来存储坐标,另外呢,我们创建了一个结构体数组,代表一个队列,来存放需要搜索的位置。还需要注意的是,这里使用的next
数组用于指示所走的方向。那么呢,我们按照顺时针的探索方式,先从右开始搜索。
下面这组数据可以用于验证代码正确与否,0代表海洋,1-9代表陆地:
1 2 1 0 0 0 0 0 2 3
3 0 2 0 1 2 1 0 1 2
4 0 1 0 1 2 3 2 0 1
3 2 0 0 0 1 2 4 0 0
0 0 0 0 0 0 1 5 3 0
0 1 2 1 0 1 5 4 3 0
0 1 2 3 1 3 6 2 1 0
0 0 3 4 8 9 7 5 0 0
0 0 0 3 7 8 6 0 1 2
0 0 0 0 0 0 0 0 1 0
如果你的答案是38
,那么证明你的代码是正确的。
深度优先与广度优先的区别
看看上面这个图,如果是深度优先的话,探索的顺序应该是1-2-4-5-3-6
。如果是广度优先的话,探索的顺序应该是1-2-3-4-5-6
。注意,这里规定左边优先探索。深度优先时,我们优先探索纵深,而广度优先时应该优先探索同一层。示例图所表示的是一种名为“树”的数据结构,我们之后会学习到。