深度优先搜索(DFS)是一种基本的图算法,主要针对图和树,英文缩写:DFS。
深度优先搜索使用的策略:从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底。
理解深度优先搜索的关键在于:解决“当下该如何做”,同时“下一步如何做”则和“当下该如何做”是一样的。
通常的方法:把每一种可能都去尝试一遍(一般使用for循环来遍历),当前这一步解决后便进入下一步。下一步的解决方法和当前这步的解决方法是完全一样的。
- 在访问其中一个顶点时,将它标记为已访问
- 递归地访问它的所有没有被标记过的邻居顶点。
【深度优先搜索算法要点】
⑴定义状态。
即如何描述问题求解过程中每一步的状况。在n皇后问题中,将行l位置a[l]作为状态。如果扩展结点时参与运算的变量有多个,为了精简程序,增加可读性,我们一般将参与子结点扩展运算的变量组合成当前状态列入值参,以便回溯时能恢复递归前的状态,重新计算下一条路径。
⑵边界条件。
即在什么情况下程序不再递归下去。在n皇后问题中,将l=n+1(产生一种成功摆法)作为边界条件。如果是求满足某个特定条件的一条最佳路径,则当前状态到达边界时并不一定意味着此时就是最佳目标状态。因此还须增加判别最优目标状态的条件。
⑶搜索范围
在当前状态不满足条件的情况下,应如何设计搜索的范围。换句话说,如何设定for语句中循环变量的初值和终值;在n皇后问题中,l行的列位置i作为搜索范围,即l<=i<=n。
⑷约束条件和最优要求。
所谓约束条件是指,当前扩展出一个子结点后应满足什么条件方可继续递归下去;是求满足某个特定条件的一条最佳路径,那么在扩展出某个子状态后是否满足最优性要求。在n皇后问题中,将(l,i)置放皇后不产生攻击(att=false)作为约束条件。
⑸参与递归运算的参数。
将参与递归运算的参数设为递归子程序的值参或局部变量。若这些参数的存储量大(例如数组)且初始值需由主程序传入,为避免内存溢出,则必须将其设为全局变量,且回溯前需恢复其递归前的值,在n皇后问题中,将皇后的行位置l和列位置i作为参与递归运算的参数。
【走迷宫】
深度优先搜索的重要例子——走迷宫。
用迷宫代替图,通道代替边,路口代替定点,那么我们就可以将迷宫看成是一个图。
要探索迷宫中的所有通道,我需要这样做:(和DFS思想是一样的)
- 选择一条没有标记过的通道,在走过的路上铺一条绳子
- 标记所有第一次路过的路口和通道
- 当遇到一个标记过的路口时回退到上一个路口
- 当回退到的路口已经没有可走的通道时继续回退
图例:
【题目】
迷宫由n行m列的单元格组成,每个单元格要么是空地,要么是障碍物,需要找到一条从起点通往终点的最短路径。
注意:障碍物不能走,不能走到迷宫外面。
起点 | 障碍 | ||
障碍 | |||
障碍 | 终点 | ||
障碍 |
起点为(1,1),只能往下走或往右走,我们一个一个进行尝试。
- 先往右走,直到走不通的时候再往回回退,然后再去尝试另外一个方向。
- 起点(1,1)一步之内可以到达的点为(1,2)和(2,1),先往右走,到达(1,2)。
- 到达(1,2)后来判断又能到达哪些点?只有(2,2),然后继续往下走,直到无路可走或到达终点为止。
下图是一种可行的搜索路径:
1 | 2 | 障碍 | |
3 | 4 | 5 | |
障碍 | 6 | ||
障碍 | 终点8 | 7 | |
障碍 |
【DFS】
DFS函数的功能是解决当前应该怎么办。
在这道题目时,这一步需要解决的是:检查是否已经到了终点,如果没有到达终点则找出下一步可以走的地方。
因此,这里的DFS()需要维护3个参数,当前点的x坐标,y坐标,已经走过的步数step。如下
def dfs(x,y,step):
{
return 0
}
检查是否已经到达终点,只需要判断当前坐标和终点坐标是否相等就可以了,如果相等则表明已经到达终点的位置。
def dfs(x,y,step):
{
#判断是否到达终点位置
if x == p and y == q:
#更新最小值
if step < min
min = step
return
return 0
}
如果没有到达终点,则找出下一步可以走的地方。但是有四个方向可以走,我先定义一个方向数组
next_step = [[0,1], #向右走
[1,0], #向下走
[0,-1], #向左走
[-1,0] #向上走
]
通过这个方向数组,使用循环就可以得到下一步的坐标。
for i in range(len(next_step)):
next_x = start_x + next_step[i][0]
next_y = start_y + next_step[i][1]
接下来,要对下一个点进行判断,包括是否越界,是否为障碍物,是否这个点已经在路径中(使用book[next_x][next_y]来记录当前点是否已经在路径中)
如果这个点符合所有的要求,就对这个点进行下一步,dfs(next_x,next_y,step+1).
def dfs(start_x,start_y,end_x,end_y,migong_array,step):
'''
:param start_x: 起始横坐标
:param start_y: 起始纵坐标
:param end_x: 终点横坐标
:param end_y: 终点纵坐标
:param migong_array: 迷宫的数组
:return:
'''
next_step = [[0,1], #向右走
[1,0], #向下走
[0,-1], #向左走
[-1,0] #向上走
]
if (start_x == end_x and start_y == end_y):
global MIN
if(step < MIN):
MIN = step
return
for i in range(len(next_step)):
next_x = start_x + next_step[i][0]
next_y = start_y + next_step[i][1]
# 判断是否越界
if(next_x < 0 or next_y < 0 or next_x > len(migong_array) or next_y > len(migong_array[0])):
continue
# 判断是否为障碍物和已经在路中
if(a[next_x][next_y] == 0 and book[next_x][next_y] == 0):
book[next_x][next_y] = 1 #标记这个点已经被走过
dfs(next_x,next_y,end_x,end_y,migong_array,step+1)#尝试下一个点
book[next_x][next_y] = 0#尝试结束,取消这个点的标记
return