1. 程式人生 > >DFS(深度優先搜尋)的非遞迴實現

DFS(深度優先搜尋)的非遞迴實現

突然想起來搜尋是屬於圖論的,那麼在解決圖論的過程中把搜尋解決掉。沒想到非遞迴實現一下就搞了兩天。雖然有些疑問還沒有解決,但感覺已經可以寫總結了,與其說總結,不如說一次嘗試的記錄(因為有個題最後還是TLE嘛)。(求規範程式碼!!!@嘯爺)

眾所周知,DFS的常規寫法是遞迴實現,但遞迴普遍慢所以可以用模擬遞迴棧來迴圈實現。這樣做有兩個好處,一個是迴圈實現要比遞迴實現更快,第二個是因為模擬的棧是在程式中手動開的儲存空間而不是通過遞迴用的計算機本身的棧,相當於外掛棧,在數量巨大的DFS的過程中不會爆棧。

當然,這是傳統這麼認為的,第二點沒什麼好說的,全域性變數開的陣列的確可以比遞迴用的棧大,所以更能有效的防止爆棧。但是關於第一點,我覺得還是有待考慮的。原因是我寫的兩種DFS非遞迴實現一個超時,一個和遞迴實現用時一樣= =。我現在還不認為是我的程式碼有問題,如果大家發現問題請留言,感激不盡。

先說那個和遞迴實現一樣的那個。

因為DFS基本上就分兩種,一種是以遍歷全圖為目的的,需要邊走邊標記,而回退之後不用清除標記。目的是遍歷全圖嘛,標記正記錄了走過的位置而方便找沒有走過的位置。一種以搜尋路徑為目的,最短路也好,恰好到達也好,找的是一條路徑問題,而最大的區別就是如果發生退棧,要進行清除標記,因為退棧後,可能存在別的路徑依然會訪問到這個結點。

先看效果,我第一次提交是用DFS的遞迴實現,第二次是用非遞迴實現,雖然兩個用時一樣,但是我們還是能確定非遞迴實現不一定要比遞迴慢(我只能精確到這了TAT,我目前還沒遇到遞迴TLE,非遞迴過的題目,但的確遇到了非遞迴TLE,遞迴過的題目= =)。

遞迴程式碼:

#include <iostream>
#include <cstring>

using namespace std;

char mp[150][150];
int chx[] = {1,0,-1,0,1,-1,1,-1};
int chy[] = {0,1,0,-1,1,-1,-1,1};
bool bj[150][150];

void dfs(int x,int y,int n,int m)
{
    for(int i = 0;i < 8;i++)
    {
        int tx = x + chx[i];
        int ty = y + chy[i];

        if(tx >= 0 && ty >= 0 && tx < n && ty < m)
        {
            if(bj[tx][ty] == 0)
            {
                bj[tx][ty] = 1;
                if(mp[tx][ty] == '@')
                {
                    dfs(tx,ty,n,m);
                }
            }
        }
    }
}

int main()
{
    int n,m;

    while(cin >> n >> m,n)
    {
        memset(bj,0,sizeof(bj));
        for(int i = 0;i < n;i++)
            cin >> mp[i];

        int ans = 0;
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j < m;j++)
            {
                if(bj[i][j] == 0 && mp[i][j] == '@')
                {
                    dfs(i,j,n,m);
                    ans++;
                }
            }
        }

        cout << ans << endl;
    }
    return 0;
}

很常規的寫法,我們根據題目要求,找到‘@’就向下搜尋,直到把相連的@全部標記完成,統計有幾塊不相連的@。

其中遇到相連的@便進入遞迴直到沒有找到@再回退,換一個方向繼續向下搜尋。

而非遞迴實現就是模擬這個過程:

#include <iostream>
#include <cstring>

using namespace std;

struct Stack
{
    int x,y;
}s[100000];

char mp[150][150];
int chx[] = {1,0,-1,0,1,-1,1,-1};
int chy[] = {0,1,0,-1,1,-1,-1,1};
bool bj[150][150];

void dfs(int x,int y,int n,int m)
{
    int t = 0;
    Stack now,tmp;
    now.x = x;
    now.y = y;
    s[t++] = now;
    bj[x][y] = 1;
    while(t > 0)
    {
        now = s[t - 1];
        int flag = 0;
        for(int i = 0;i < 8;i++)
        {
            tmp.x = now.x + chx[i];
            tmp.y = now.y + chy[i];

            if(tmp.x >= 0 && tmp.y >= 0 && tmp.x < n && tmp.y < m)
            {
                if(bj[tmp.x][tmp.y] == 0)
                {
                    bj[tmp.x][tmp.y] = 1;
                    if(mp[tmp.x][tmp.y] == '@')
                    {
                        s[t++] = tmp;
                        flag = 1;
                        break;
                    }
                }
            }
        }

        if(flag)
            continue;
        t--;
    }
}

int main()
{
    int n,m;

    while(cin >> n >> m,n)
    {
        memset(bj,0,sizeof(bj));
        for(int i = 0;i < n;i++)
            cin >> mp[i];

        int ans = 0;
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j < m;j++)
            {
                if(bj[i][j] == 0 && mp[i][j] == '@')
                {
                    dfs(i,j,n,m);
                    ans++;
                }
            }
        }

        cout << ans << endl;
    }
    return 0;
}

我們手動建立了一個棧,如果找到‘@’就更新棧頂,然後跳出各個方向的向下遍歷,因為如果不跳出,就成以這個點為起點向周圍各個方向搜尋的類似BFS的東西了。

更新棧頂之後便以棧頂為起點繼續向下搜尋,但是如果向下沒有找到‘@’,也就是說沒法繼續更新棧頂,那隻能做回退處理。但是這裡有個問題,當然在這種型別的DFS中並不是問題,但是再第二個型別的DFS中需要特殊處理,那就是在遞迴中我們可以很好實現當回退之後,換個方向繼續搜尋,因為剛才的路徑、方向都已經在遞迴棧中儲存了,相當於掛起,但迴圈模擬卻沒有對之前的狀態進行儲存處理,舉個例子:從A以第1個方向走到B(並作標記),在B沒有找到下路,需要回退到A,然後A重新成為棧頂繼續向下遍歷,理論上應該是從第2個方向繼續遍歷,而迴圈實現依舊是從第1個方向開始,原因很好理解。至於怎麼改善,講第二種DFS的時候再做敘述。

第二種DFS,以搜尋路徑為目的,最短路也好,恰好到達也好,找的是一條路徑問題,而最大的區別就是如果發生退棧,要進行清除標記,因為退棧後,可能存在別的路徑依然會訪問到這個結點。我們以ZOJ2110為例,我昨天這個題卡了一下午,遞迴實現很順利就A掉了,但是非遞迴實現到現在還是TLE,十分費解(壞了,留下陰影了,再也不會愛了TAT)。

還是先看交題記錄

因為第一次寫這種型別的DFS,開始的那幾個WA大家請忽視= =。

其中有個AC是我實在懷疑非遞迴了(一開始就是用非遞迴寫的),然後用遞迴寫了一下,一下就A掉了,簡直醉了。

這個題目需要剪枝,其實就是深度優先搜尋(DFS)的奇偶剪枝那篇文章的例題,如果想看遞迴實現和剪枝優化,請左轉那篇之前寫的文章,下面只說非遞迴實現。程式碼:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <math.h>

using namespace std;

char map_[10][10];
bool bj[10][10];
int chx[] = {0,1,0,-1};
int chy[] = {1,0,-1,0};

struct Stack
{
    int x,y,t;
    int n;
} s[100000];
int main()
{
    int n,m,tim;

    while(cin >> n >> m >> tim,n)
    {
        for(int i = 0; i < n; i++)
        {
            cin >> map_[i];
        }

        int x = -1,y = -1;
        int fx = -1,fy = -1;
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                if(map_[i][j] == 'S')
                {
                    x = i;
                    y = j;
                }
                if(map_[i][j] == 'D')
                {
                    fx = i;
                    fy = j;
                }
            }
        }

        if(x == -1 || fx == -1)
        {
            cout << "NO" << endl;
            continue;
        }

        memset(bj,0,sizeof(bj));

        int t = 0;
        s[t].x = x;
        s[t].y = y;
        s[t].t = 0;
        s[t].n = 0;
        t++;
        bj[x][y] = 1;

        bool flag = 1;
        Stack tmp,now;
        while(t > 0)
        {
            now = s[t - 1];

            if(now.x == fx && now.y == fy)
            {
                if(now.t == tim)
                {
                    cout << "YES" << endl;
                    flag = 0;
                    break;
                }
            }

            int pdtmp = tim - now.t - fabs(fx - now.x) - fabs(fy - now.y);
            if(pdtmp < 0 || pdtmp % 2)
            {
                t--;
                if(t == 0)
                    break;
                s[t - 1].n++;
                bj[s[t].x][s[t].y] = 0;
                continue;
            }

            int tf = 0;
            for(int i = now.n; i < 4; i++)
            {
                tmp.x = now.x + chx[i];
                tmp.y = now.y + chy[i];
                tmp.t = now.t + 1;
                tmp.n = 0;
                now.n = i;

                if(tmp.x >= 0 && tmp.y >= 0 && tmp.x < n && tmp.y < m)
                {
                    if(map_[tmp.x][tmp.y] != 'X')
                    {
                        if(bj[tmp.x][tmp.y] == 0)
                        {
                            bj[tmp.x][tmp.y] = 1;

                            s[t++] = tmp;
                            tf = 1;
                            break;
                        }
                    }
                }

            }

            if(!tf)
            {
                t--;
                if(t == 0)
                    break;
                s[t - 1].n++;
                bj[s[t].x][s[t].y] = 0;
            }
            
        }

        if(flag)
            cout << "NO" <<endl;
    }
    return 0;
}

看棧結構體,除了常規的x,y,t之外,還有個n變數,他就是用來儲存他的下一步遍歷方向,然後在回退的時候注意不再走之前走過的路。因為回退的時候會清標記,所以如果能走之前走過的路,將會進入死迴圈。

也就是這個原因,在回退的時候不僅需要注意吐棧頂,還有注意更改之前元素的方向,已經清楚退出元素的標記,所以在繼續遍歷的時候一定要避開。

2015-02-12

最後,嘯爺終於給我了他寫的手寫遞迴棧,居然過了。。。。研究一個下午也不明白他是怎麼過的,算了以後就這麼寫吧。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <cmath>
#include <stack>
#include <map>

#pragma comment(linker, "/STACK:1024000000")
#define EPS (1e-8)
#define LL long long
#define ULL unsigned long long
#define _LL __int64
#define INF 0x3f3f3f3f
#define Mod 100007

using namespace std;

int jx[] = {-1, 0, 1, 0};
int jy[] = { 0,-1, 0, 1};

bool vis[10][10];

char Map[10][10];

struct N
{
    int x,y,c,dir;
}st[55*10*10],f,t;

bool Judge(int x,int y,int n,int m)
{
    return (1 <= x && x <= n && 1 <= y && y <= m && Map[x][y] != 'X');
}

int main()
{
    int i,j,n,m,k;

    while(scanf("%d %d %d",&n,&m,&k) && (n||m||k))
    {
        for(i = 1;i <= n; ++i)
            scanf("%s",Map[i]+1);

        int sx = -1,sy = -1,ex = -1,ey = -1;

        for(i = 1;i <= n; ++i)
        {
            for(j = 1;j <= m; ++j)
                if(Map[i][j] == 'S')
                    sx = i,sy = j;
                else if(Map[i][j] == 'D')
                    ex = i,ey = j;
        }

        if(sx == -1 || ex == -1 || (abs(ex-sx) + abs(ey-sy))%2 != k%2 )
        {
            puts("NO");
            continue;
        }

        memset(vis,false,sizeof(vis));

        vis[sx][sy] = true;
        st[0] = (N){sx,sy,0,-1};
        int Top = 0,site;

        while(Top > -1)
        {
            f = st[Top];
            site = Top;

            if(Map[f.x][f.y] == 'D' && f.c == k)
                break;

            if(f.dir == 3 || f.c > k)
            {
                Top--;
                vis[f.x][f.y] = false;
                continue;
            }

            st[site].dir = ++f.dir;

            t.x = jx[f.dir] + f.x;
            t.y = jy[f.dir] + f.y;
            t.c = f.c + 1;
            t.dir = -1;

            if(Judge(t.x,t.y,n,m) == false || vis[t.x][t.y])
                continue;

            vis[t.x][t.y] = true;

            st[++Top] = t;
        }

        if(Top > -1)
            puts("YES");
        else
            puts("NO");

    }


    return 0;
}

歡迎到微信裡去當吃瓜群眾