[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的選擇問題上可以少一些糾結了 :-)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章