[HDOJ 1181]深度优先搜索 vs. 广度优先搜索 (二)

1181:给定一个无权有向图,求起点到终点是否可达.
题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1181

一.广度优先搜索

先用BFS解一下看看:

char map[26][26], letters[26];
int discover[26];

/*
 * 广度优先搜索,求B到M的最短路径
 *
 * @return 1:可以从b->m, 0:不可以从b->m
 *
 * @param x - 字母的序号,a:0, b:1, ..., z:25
 * 
 */
char bfs(void)
{
    /* 返回值,初始化为0 */
    char ret = 0;

    char *line, i, *lp;
    struct queue *q;

    /*
     * 图上最多26个顶点,每个顶点仅仅入队一次,
     * 因此队列大小只要26
     */
    q = queue_new(26);
    memset(discover, 0, sizeof(discover));

    /* 将B插入到队列中 */
    queue_append(letters + 1,  q);

    check:
    queue_append(NULL, q);
    while ((lp = (char*)queue_pop(q)) != NULL) {
        line = map[*lp];
        for (i = 0; i < 26; ++i) {
            if (line[i] && !discover[i]) {
                if (letters[i] == 'm' - 97) {
                    ret = 1;
                    goto exit;
                }
                discover[i] = 1;
                queue_append(letters + i, q);
            }
        }
    }
    /*
     * 队列变空是广度优先搜索基本的终止条件.
     * 当取出一个NULL后,如果队列空了,
     * 说明B到M不可达,因此直接退出并返回0
     */
    if (!queue_isempty(q))
        goto check; 
    exit:
    queue_destroy(q);
    return ret;
}

在上述过程中,每个节点最多入队一次,因此while循环最多执行25次(M不会入队).

二.深度优先搜索

再试试DFS.

char map[26][26], block[26];

/*
 * 深度优先搜索,
 * 路径从B开始,并且无论何时,只要M出现在从B开始的路径上,算法就结束
 * 否则,算法会遍历完所有路径
 *
 * @return - 当前字母到M的路径长度
 * INT_MAX: 不可达
 *
 * @param deepth - 当前深度
 * @param current - 当前字母
 *
 */
int dfs(int deepth, char current)
{
    int best = INT_MAX;

    int len, i, ret;

    /*
     * 将测试条件放在dfs()开始的地方还是
     * 将条件放在for循化内部?
     * 这是个让人头疼的问题.
     * 但是,对1180而言,后者明显好于前者(因为你不能进入到
     * 下一层递归以后才想起要实现"滑动").
     */
    if (current == 'm' - 97)
        return 0;
    if (block[current])
        return INT_MAX;
    /* 阻塞节点也在for循环外部执行 */
    block[current] = 1;
    for (i = 0; i < 26; ++i) {
        if (map[current][i] == 1 && !block[i]) {
            len = dfs(deepth + 1, i);
            if (len < best)
                best = len;
        }
    }
    block[current] = 0;

    ret = best + 1;
    /* 从Linux内核学来的判断溢出的方法 */
    return ret < best ? best : ret;
}

三.比较

对这个题目而言似乎BFS和DFS不分上下.
可是思考后发现,如果不存在起点到终点的路径,BFS只要遍历完所有节点就可以,但是DFS却要遍历完所有路径.从这个意义上看,似乎BFS还是优于DFS.

回过头再跟1181比较一下,在1180和1181中:

  • BFS表现出的最直观优势就是它绝不会将一个节点入队两次;
  • BFS都被用来寻找从起点到终点的最短路径;

经过一番思考,再联想到就连教科书常用BFS解决无权最短路径问题,那么,寻找最短路径应该正是BFS的长处所在.

在使用BFS寻找最短路径的过程中,节点第一次被发现的同时,其最短路径也被发现,因此每个节点只会入队一次,所以外层的while循环只会执行O(|V|)次(整个算法的复杂度是O(|V|*|E|)).

如果使用DFS寻找最短路径,那么DFS必须把图中的所有路径统统遍历一遍,虽然可以采用剪枝来优化这个过程,但是其最坏情形(两点不通)的复杂度依然与图中简单路径(无环的路径)的数目成正比,这意味着DFS寻找最短路径通常是指数级的算法.

再思考后,BFS和DFS的区别更加明确:

BFS的长处在于它可以很快找到最短路径;而DFS的长处在于它可以遍历图中所有简单路径.

BFS和DFS是不可交换的.如果用DFS寻找最短路径,会使算法从|V|*|E|级上升到指数级;反过来,如果强行用BFS遍历所有简单路径,估计我的内存和大脑都会马上冒烟.

这样,BFS和DFS的应用领域就很明确了:

  • 如果找到最短路径就能解决问题,那么选择BFS;
  • 如果找到最短路径不一定能解决问题, 那么选择DFS;

1181和1180都是找到最短路径就能解决问题的题目,那么有没有找到最短路径也不能解决的题目呢?当然有.

1175:给定一个地图和两个点,请问两点之间是否存在一条拐角数不超过2的路径?

原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=1175

对于下图:

1 0 0 0 0 0 0
0 2 2 2 2 2 0
0 0 0 2 2 2 0 
2 2 0 0 2 2 0 
2 2 2 1 0 0 0

1到1的最短路径显然不是问题的解.事实上,除了遍历所有路径,我不能想到更好的办法来寻找满足题设的路径.因此我只能选用DFS来解决这个问题.

附上DFS代码:

/* 地图 */
int map[1000][1000];
/* 方向 */
int dir[8] = {0, 0, -1, 1, -1, 1, 0, 0};
/* 当前地图的宽度和高度 */
int M, N;

/*
 * 深度优先搜索
 * 
 * @return 1:可以连在一起,0:不能连在一起
 *
 * @param deepth - 深度
 * @param conner - 路径上已有的拐角个数
 * @param direction - 当前的方向
 * @param bx, by, ex, ey - 起点和终点的坐标
 */
int dfs(int deepth, int conner, int direction,
        int bx, int by, int ex, int ey)
{
    int conner2, d, nx, ny;
    int ret, save;

    /* 
     * 由于会直接在地图上记录阻塞节点(写入51),
     * 所以把节点原来的值保存在sava变量中
     */
    save = map[bx][by];
    map[bx][by] = 51;
    /* 遍历四个方向 */
    for (d = 0; d < 4; ++d) {
        conner2 = conner;
        /* 
         * 如果当前方向与源方向不同,则拐角个数+1
         * 如果拐角个数多余2个,放弃当前方向
         */
        if (d != direction)
            if (++conner2 > 2)
                continue;
        nx = bx + dir[d];
        ny = by + dir[d + 4];

        /* 到达终点 */
        if (nx == ex && ny == ey) {
            ret = 1;
            goto exit;
        }

        /* 下一个节点合法性与合理性测试,这次,测试被放到了for循环内部 */
        if (nx >= 0 && nx < M && ny >= 0 && ny < N
                && !map[nx][ny]
                && dfs(deepth + 1, conner2, d,nx, ny, ex, ey)) {
            ret = 1;
            goto exit;
        }
    }
    ret = 0;
exit:
    map[bx][by] = save;
    return ret;
} 

注意:1175并非不能用BFS.
事实上,我感觉以拐角数作为度量的BFS优于上述DFS算法.
思路来自于1728,可惜我莫名其妙WA,至今没找到原因TT

终于,在DFS和BFS的选择问题上可以少一些纠结了 :-)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值