Bootstrap

【算法】深度优先搜索DFS(一)

深度优先搜索(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.  先往右走,直到走不通的时候再往回回退,然后再去尝试另外一个方向。
  2. 起点(1,1)一步之内可以到达的点为(1,2)和(2,1),先往右走,到达(1,2)。
  3. 到达(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

;