算法錄 之 BFS和DFS


  說一下BFS和DFS,這是個比較重要的概念,是很多很多算法的基礎。

  不過在說這個之前需要先說一下圖和樹,當然這里的圖不是自拍的圖片了,樹也不是能結蘋果的樹了。這里要說的是圖論和數學里面的概念。

  

  以上概念來自百度百科。

  數學里面的圖就是許多的點和許多的邊把這些點連了起來,具體每個點放在那里沒啥關系,重點是他們之間的連接關系。

  一個圖長得就像是下面這樣:

  這個圖有6個點,8條邊,其中有一條是自己連接自己的。

  然后圖的話有有向圖,無向圖等等,還有很多很多分類,比如二分圖等等,可以百度百科或者維基看一下就差不多明白了。

 

  然后樹的話其實也是圖,但是比較特殊而已,他有N個點,N-1條邊,而且這N個點是互相連通的,那么這個圖就能畫成一顆樹一樣的樣子。

  倒過來看就很像一棵樹。

 

  然后下面要說的是BFS和DFS,這兩個是一個縮寫,全稱是 BFS:Breadth-First-Search,寬度優先搜索;DFS:Depth-first search,深度優先搜索。

  都是一種搜索,只不過搜索的方法不一樣而已。

  先說說搜索,顧名思義,搜索就是。。。搜索。對於一個圖來說,搜索就是從某個點開始,不停的搜索與他相連的所有的點,然后以此接連下去,直到所有的點都被搜索到了。

  然后BFS的話就是寬度優先。

  比如這個圖,如果從1開始進行搜索的話,BFS的步驟就是,先搜索所有和1相連的,也就是2和5被找到了,然后再從2開始搜索和他相連的,也就是3被找到了,然后從5搜,也就是4被找到了,然后從3開始搜索,4被找到了,但是4之前已經被5找到了,所以忽略掉就行。然后3開始搜索,忽略4所以啥都沒搜到,然后從4開始,6被找到了。。。

  就是這樣,這就是BFS。。。

  說完DFS比較一下兩個的區別可能會比較好理解。

  DFS的話從1開始,先找到其中一個相連的,2被找到了,然后直接開始從2開始搜索,3被找到了,然后從3開始搜索,4被找到了,然后從4開始搜索,5被找到了,然后從5開始搜索,忽略已經找到的所以啥都沒找到。然后沒路可走了,回到前面去再走另一條路,從4開始,6被找到了,然后又沒路可走了,然后再回去前面4,然后沒路了 ,回去前面3,然后一直這樣。。。

  DFS 就是像走迷宮一樣一條路走到頭直到走不通才回到前一個換一條路。。。就是這樣。。。

 

  DFS和BFS主要是運用於對於圖和樹的搜索,但是絕大部分問題模型都是可以建模變成一個圖或者樹的,所以差不多不少問題都會涉及到這兩個。

 

  現在知道了這個東西的實現的步驟了。下面就要說一下怎么用代碼來實現他。

  先說圖吧,對於每個點來說就是標號1,2,3。。。。N就好,表示有N個結點,一般題目也已經標好號了。

  然后邊的話一般會就是 u,v 這樣表示有一條邊連接u點和v點。

  存儲一個圖的邊有三種方法:

  首先說一下存圖就是對於每個點u,記錄他能到的所有點就行了。。。

  鄰接矩陣:

  直接開一個N×N的二維數組E,然后 E[i][j] 為1的時候表示 i 和 j 之間有一條邊,0的時候就沒有。

  這樣很方便簡單,但是有幾個缺陷,首先是效率問題,超過1000個點一般不管是空間還是時間都不允許了。然后就是如果從 3 到 5 有兩條邊的話,就沒法表示了。。。

  所以一般很少用了現在,當然有些算法還是會用到的。

int E[110][110];

E[1][2]=1;
E[5][3]=0;

  鄰接鏈表:

  使用鏈表的方式保存一個結點的所有邊,就是每個點都有一個鏈表。

  當然寫個鏈表很麻煩,所以一般是用vector來替代。就像是下面這樣。

vector <int> E[110];

E[3].push_back(6) // 有一條從3到6的邊。

  具體vector怎么用自行學習

 

  前向星:

  這個名字實在逼格太高,而且很好用效率也高,所以我一直都用這種方式來存圖。

  他和鏈表幾乎沒什么區別,就是每次添加新的邊的時候往開頭加,而不是往最后加。

具體就像是下面這樣:

struct Edge
{
    int to,next;
};

Edge E[1010];            // 總共不超過1000條邊。
int head[110],Ecou;        // 不超過100個點。

void init()                // 初始化。
{
    memset(head,-1,sizeof(head));
    Ecou=0;
}

void addEdge(int u,int v)    // 增加邊 u,v。
{
    E[Ecou].to=v;
    E[Ecou].next=head[u];
    head[u]=Ecou++;
}

  具體的代碼可以慢慢理解,而且剛開始的話用前面兩種也可以。

 

  然后說說BFS和DFS怎么寫。

  首先BFS的話需要一個隊列這種數據結構來保存,隊列在另一篇有說。

  因為每次找到和u相連的之后要一個個找這些點,符合先進先出。

代碼如下:(采用第二種存圖方式。)

bool vis[110];            // 記錄已經走過的點,防止重復訪問。

void BFS(int root,int N)        // N個點的圖,從root點開始搜索。
{
    queue <int> que;

    memset(vis,0,sizeof(vis));    // 初始化。
    vis[root]=1;
    que.push(root);

    int u,len;

    while(!que.empty())
    {
        u=que.front();
        que.pop();

        len=E[u].size();
        for(int i=0;i<len;++i)        // 找到和u相連的所有點,存在一個vector里面。
            if(vis[E[u][i]]==0)
            {
                vis[E[u][i]]=1;
                que.push(E[u][i]);
            }
    }
}

  十分建議手算模擬一下這個算法,對於步驟有一個清晰的認識。

 

然后是DFS:需要一個棧,因為每次都是搜到之后不停的往下搜,符合先進先出。但是一般來說不用棧,而是直接通過函數的遞歸就行了。

bool vis[110];
int N;

void DFS(int u)
{
    int len;

    vis[u]=1;
    len=E[u].size();

    for(int i=0;i<len;++i)
        if(vis[E[u][i]]==0)
            DFS(E[u][i]);
}

  差不多就是這樣,也建議好好模擬一下。

 

  至於這兩個的用途,其實在一定程度上是可以相互轉化的,但是有些需要各自的特性的話就不行了。

  DFS主要的特性是深度優先,總是不停的往下找,走到沒路才罷休。

  BFS則是從root開始擴展,每一層都是精密的搜索完整了才下一個。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM