深度优先搜索

来源:互联网 发布:微云同步盘mac版 编辑:程序博客网 时间:2024/05/18 12:31

深度优先,顾名思义,只要有路,则一条路走到底,然后回溯,看之前的叉路口是否还有路可选。


一、基本概念

深度优先搜索 是一种图的遍历算法,可用于求单源最短路径。

算法思路


这里写图片描述

看图说话!如上是一个有向图。

  • 假设我们需要找到从1到4结点的路径。那么从结点1开始遍历,然后递归的遍历所有与1相邻的结点。所以,第一,我们需要构建图的邻接矩阵(建模很重要),并初始化。

  • 为了避免遍历完1结点后,在跳到2结点时,又往回遍历1结点。所以,第二,我们需要用一个数组visited来标记 已遍历过 的结点(简单的hash表)。

  • 前面讲过,深度优先其实就是先一条路走到黑。那么,在从1到2后,就应该是在2的邻接结点中选择一个结点(此前已将1和2结点标记为了已遍历),假如选择3,那么接下来就是从3的邻接结点中选择一个结点,如上只能选择4(visited[1] != 0),因此1-2-3-4便是1到4的一条路径。

  • 如此,便完成了一次深度搜索。 如果任务重新修改为找到从1到4节点的最短路径,虽然通过上述过程,我们已经找到了一条路径。但是此时,并不能确定此为最短。因为,在之前的叉路口,还有其他路径可选时,比如在节点2的时候,除了3这个选择,我们还可以选择5,因此,我们还需要重新回到2节点,然后看看如果接下来我们选择5,会不会有可能也可以到达4,并且路程缩短。 基于这个想法,进入下一个步骤。

  • 首先是在4结点的基础上回溯到3结点,由于3只有一个往外地指向,所以继续回溯到2结点。(注意,回溯的时候需要将此前标记过的结点去除标记,即将4和3去除标记)。在回溯到2结点时,发现还有一条可走的路径,即跳转到5,然后接下来的过程便是重复上述步骤。最后发现新的路径: 1-2-5-3-4 。在此,基于结点2的所有可能路径便已全部遍历完。于是再从2回溯到1(并将2标记为未遍历),将1其余的邻接结点按照上述步骤重复。


二、举例说明

1. 求最短路径

假设有5个城市,城市之间的道路情况如下图所示,1和2之间的单向箭头表示只能从1到2,箭头上面的数字表示城市之间的距离。要求计算从1到5之间的最短距离。


这里写图片描述

  1. 构建邻接矩阵:


    这里写图片描述

  2. 初始一个数组用于记录每个节点是否被遍历。visited[]

  3. 在当前结点处,判断与其他结点是否有通路(-1表示没有)。例如,假设当前在结点1,判断与其他结点之间的关系,只需要遍历一个一维数组就可以了(第一行)。

  4. 发现 [1,2] 位置 > 0 时,则表示1和2之间有通路,如果 visited[2] = 0 则表示节点2没有被访问过,那么也就代表着此时站在节点1时,可以从节点1过渡到节点2, 然后再执行 visited[2] = 1 以标识节点2被访问了。

  5. 通过第4步,到达了节点2。此时,由于节点2不是目的节点,则需要站在节点2的位置,继续“选路”,也就是遍历第二行,从第二行中找到可选节点。剩下的过程就是重复步骤4和步骤5,直到没有路可选了 或者 已到达目的节点。然后就是回溯,也就是从最深一层的递归中退出来时,将之前访问过的节点重新标记为未访问,然后就是再重复之前过程。。。(表述有点不清晰,代码说话= =)

代码如下:

/*    深度优先搜索算法     作者:Zoo    时间:2016年4月16日12:45:19 */ #include <iostream> // for cout#include <string.h> // for memset#define N 100using namespace std;int city_n ;        //城市的数量int road_n ;        //道路的数量int minDis = -1;    //最短路程 -1 表示此数值无效 int visited[N];     //已经路过的城市 int edge[N][N];     //邻接矩阵 : -1 表示此路不通 int city_tar;       //目标城市 /*    深度优先搜索算法    cur  int  当前结点    dis  int  已走过的路程 */void dfs(int cur, int dis); int main(){           cout <<  "输入城市数量 和 路的总数【eg :5 8 总共5个城市 8条单向通道】" <<endl;    cin >> city_n >> road_n;    // 初始化邻接矩阵    memset(*edge,-1,4*N*(city_n+1));    for(int i = 0 ; i <= city_n ; i++)    {        edge[i][i] = 0; // 自己到自己的距离是0     }     cout <<  "输入城市之间的道路情况【eg : 1 2 8 城市1到2的路程为8】" <<endl;       int city_s,city_d;  //路的起始城市,和 目的城市     int dis;    //距离     for(int i = 0; i < road_n; i++)    {        cin >> city_s >> city_d >> dis;        edge[city_s][city_d] = dis;     }    /* 输入出发点 和 目标点 */     int city_cur;    cout <<  "输入出发点 和 目标点【eg : 1 5 从城市1出发到城市5】" <<endl;    cin >> city_cur >> city_tar ;    visited[city_cur] = 1;    dfs(city_cur,0);    cout << minDis << endl;    return 0;}void dfs(int cur, int dis){       //如果当前走过的路程已经大于之前找到的最短路径,则返回     if( minDis >= 0 && dis > minDis )           return;    if( cur == city_tar ) // 如果当前结点为目标结点,则判断是否需要更新最短路程     {        if( (minDis >= 0 && dis < minDis) || ( minDis == -1) )            minDis = dis;        return;     }     // 此处就相当于对一个一维数组的遍历    for( int i = 1 ; i <= city_n; i++)    {        if(edge[cur][i] >= 0 && visited[i] == 0)        {            /* 标记为已走过的结点 */            visited[i] = 1;            dfs(i,dis+edge[cur][i]);             /* 回溯时将结点状态重置 */            visited[i] = 0;        }           }    return; }

测试数据:

5 81 2 21 5 102 3 32 5 73 1 43 4 44 5 55 3 31 5

结果如下:

这里写图片描述


2. 求两个节点之间的所有路径

接着上面的题目,此时假设要求任意两个结点之间的路径。

/*深度优先搜索算法作者:Zoo时间:2016年4月16日12:45:19*/#include <iostream> // for cout#include <string.h> // for memset#include <string>#define N 100using namespace std;int city_n;     //城市的数量int road_n;     //道路的数量int minDis = -1;    //最短路程 -1 表示此数值无效 int visited[N];     //已经路过的城市 int edge[N][N];     //邻接矩阵 : -1 表示此路不通 int city_tar;           //目标城市 string road[N];     // 所有可选路径int    road_i = 0;  // 当前路径 string road_tmp;/*找出 起始结点 到 目的结点 的所有路径    cur int 当前结点 */void dfs_road(int cur);int main(){    cout << "输入城市数量 和 路的总数【eg :5 8 总共5个城市 8条单向通道】" << endl;    cin >> city_n >> road_n;    // 初始化邻接矩阵    memset(*edge, -1, 4 * N*(city_n+1));    for (int i = 0; i <= city_n; i++)    {        edge[i][i] = 0; // 自己到自己的距离是0     }    cout << "输入城市之间的道路情况【eg : 1 2 8 城市1到2的路程为8】" << endl;    int city_s, city_d; //路的起始城市,和 目的城市     int dis;    //距离     for (int i = 0; i < road_n; i++)    {        cin >> city_s >> city_d >> dis;        edge[city_s][city_d] = dis;    }    /* 输入出发点 和 目标点 */    int city_cur;    cout << "输入出发点 和 目标点【eg : 1 5 从城市1出发到城市5】" << endl;    cin >> city_cur >> city_tar;    visited[city_cur] = 1;    road_tmp.append(1,city_cur + '0');    dfs_road(city_cur);    for (int i = 0; i < N; i++)    {        if (road[i].length() == 0)            continue;        cout << road[i] << endl;    }    return 0;}void dfs_road(int cur){    if (cur == city_tar) // 如果当前结点为目标结点,则判断是否需要更新最短路程     {        road[road_i++] = road_tmp;        return;    }    for (int i = 1; i <= city_n; i++)    {        if (edge[cur][i] <= 0 || visited[i] == 1)            continue;        visited[i] = 1;        road_tmp.append(1,'0' + i); //进栈         dfs_road(i);        road_tmp.erase(road_tmp.size()-1,1); //出栈         visited[i] = 0;    }    return;}

测试数据改为求1和4结点之间的所有路径。

结果如下:

这里写图片描述


3. 迷宫

以下是一个迷宫的示意图,标有1的空格表示为障碍物,求从起始位置到目的位置的最短步数。注意:每次只能在当前位置的上下左右四个方向移动一格,要求只能在迷宫内移动,并且遇到障碍物只能绕道而行。(此题是针对 广度优先搜索 中问题的深度优先写法。)


这里写图片描述

代码如下:

#include <iostream>using namespace std;int direct[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};int book[2501][2501];int minDist = 99999;int tarx,tary;  //目标点坐标int row,col;    //地图的行列 int map[51][51];    //地图 void maze(int x,int y,int step){    if(x == tarx && y == tary)    {        if(step < minDist)            minDist = step;        return;     }    for(int i = 0; i < 4; i++)    {        int tx = x + direct[i][0];        int ty = y + direct[i][1];          if( tx < 1 || tx > row || ty < 1 || ty > col)            continue;        if( map[tx][ty] == 0 && book[tx][ty] == 0 )        {            book[tx][ty] = 1;            maze(tx,ty,step+1);            book[tx][ty] = 0;        }    }}int main(){    cout << "input row & col as map's size [eg: 5 4 -> 5 rows and 4 cols ]" <<endl;    cin >> row >> col;    cout << "input the map's info " <<endl;    for(int i = 1; i <= row ; i++)    {        for(int j = 1; j <= col ; j++)        {            cin >> map[i][j];        }    }    cout << "input start pos and target pos" <<endl;    int sx,sy;    cin >> sx >> sy >> tarx >> tary ;    maze(sx,sy,0);    cout << minDist << endl;    return 0;}

测试代码:

5 40 0 1 00 0 0 00 0 1 00 1 0 00 0 0 11 1 4 3

测试结果:

这里写图片描述

三、说明

if(x == tarx && y == tary){    if(step < minDist)        minDist = step;    return; }

上述这段代码中,return 不加上本来也没有错,但是理论上会增加递归次数。因为进入到这个 if 语句也就代表着这次深度搜索达到了目的,按要求应该是回溯至上一节点,去其他分支看看有没有另外一条路能够使得最短路径减小。而如果不加上return , 那么就会再一次进入到下面的for循环中,那么只要当前结点的上下左右四个方向中,存在任何一个之前没被访问过的结点(book[i][j] == 0), 那么就会再一次进入递归,而显然这是无意义的。

2 0
原创粉丝点击