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