0基礎學演算法 搜尋篇第一講 深度優先搜尋

球君發表於2020-11-01

  相信絕大多數人對於深度優先搜尋和廣度優先搜尋是不會特別陌生的,如果我這樣說似乎你沒聽說過,那如果我說dfs和bfs呢?先不說是否學習過它們,至少它們的大名應該是都是聽說過的吧,深度優先搜尋(Depth-First-Search)和廣度優先搜尋(Breadth-First-Search)同為搜尋(Search)中的兩個大類。顧名思義,他們的作用便是在一定的元素當中選取符合要求的元素,有些情況下無論你使用dfs或bfs都能得出結果,但是事實上它們兩個本質上是有區別的

第一講深度優先搜尋(Depth-First-Search)

  深度優先搜尋是一種用途很廣泛的演算法,這種搜尋方法使用的是一種比較基本的概念----遞迴,dfs就是基於遞迴而產生的一種嚴格意義上的演算法,最開始的時候dfs是一種在開發爬蟲早期使用較多的方法,它的目的是要達到被搜尋結構的葉結點。那麼深度優先搜尋到底是如何實現的呢?

  要能理解dfs,首先理解遞迴是必要的(遞迴連結https://www.cnblogs.com/qj-Network-Box/p/12729230.html),在理解了遞迴後,就可以來看看這種演算法的思想了

  假如你要搜尋節點①~節點⑩,然後你通過遍歷找到了節點①,那麼接下來你可以使用dfs找到接下來的節點

  找到第一個節點是必須的,然後就可以接著往下遞迴

 

然後就要一路找到底,即一直找到要回溯的節點

 

如果到了節點⑤的時候又找到了可以繼續往下遞迴的條件,那麼還可以繼續往下搜尋

 

然後在②處又找到了下面的一個節點

到了新的節點又可以向下搜搜

雖然現在回溯到了①,但是搜尋還差幾步,因為①再往下還有節點

要一直等到這裡才是dfs最終結束的地方

 

 以上便是dfs的基本思想了,但是呢dfs並不是一種一成不變的演算法,事實上不同的情況下所寫出來的dfs是不同的,但是思路差不多就類似以上

深度優先搜尋(Depth-First-Search)的例題

  在某個神奇的網站上,你總可以找到一些有趣的題鞏固你學的知識,題目連結-->https://www.luogu.com.cn/problem/P1162

  這一道題算是一道比較基礎的練習題吧(應該),其實這道題在你能夠理解dfs的情況下,並找到思路的情況下應該是手到擒來的事。

  首先我們來仔細地讀一讀題大概意思就是被1框住的0要改寫成2,我想很多人看到題的第一反應是想著怎麼區別圈裡面和圈外面的0,然後把圈裡面的0改成2,似乎這個思路只有一個難點,那就是如何確定一個0到底處在圈裡還是圈外,我開始也是這麼想的,然後我想到了一個解決辦法,如下圖

一般的情況就如左圖,所以我後來的分析都是基於左圖的

 

 

然後我就想到了這個判定方法,也許你也想到了,但是我在實現這種方法的時候我發現了一個很大的瑕疵,假如那個圈不是規則的,而且不僅不規則,而且還是凹多邊形這種思路就會出問題,問題如下圖所示

 

 

如果測試點給的是這樣圖形,之前的判定方法就徹底失效了,因為很明顯這個本來是一個在圈外的點,卻會被判定成圈內的,那到底該如何識別一個0到底是圈外還是圈內呢?說實話,我到現在還是沒想出來,但是很多時候我們就可以使用逆向思維大法,既然很難判定是否是圈內的,那就反過來判斷他是不是圈外的不就完了?首先我們可以確定一個0處於方陣邊緣處肯定不是圈內的,所以我們可以通過這個方法可以先找到邊緣的零,那麼,與它相鄰的0以及相鄰於與他相鄰的0定然也是在圈外的,那麼這道題dfs的思路也好說了,找到一個圈外邊緣的0,標記一下,然後搜尋與他相鄰的0,並標記後繼續搜尋,由於題目要求,我們每到一點只要向周圍四個方向搜尋即可

供上搜尋部分的程式碼

 1 int n,a[35][35]; 
 2 int x1[4]={0,1,0,-1};
 3 int y1[4]={1,0,-1,0};//四個方向
 4 void dfs(int x,int y)
 5 {
 6     a[x][y]=5;
 7     for(int i=0;i<4;i++)
 8     {
 9         if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)
10         {
11             if(a[x+x1[i]][y+y1[i]]==0)    dfs(x+x1[i],y+y1[i]);//dfs
12         }
13     }
14 }

但是這道題只搜尋這一次肯定是不夠的,我們還需要考慮其他情況,比如下圖

這種情況下有兩塊不在圈內的區域,所以我們需要不止一次地執行dfs,也就是說我們在主函式中可以將他的邊緣“走”一圈以確保每一塊圈外的0都可以被標記到

 

 

供上主函式+dfs

#include<iostream>
using namespace std;
int n,a[35][35]; 
int x1[4]={0,1,0,-1};
int y1[4]={1,0,-1,0};
void dfs(int x,int y)
{
    a[x][y]=5;
    for(int i=0;i<4;i++)
    {
        if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)
        {
            if(a[x+x1[i]][y+y1[i]]==0)    dfs(x+x1[i],y+y1[i]);
        }
    }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        cin>>a[i][j];
    }
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        if((i==0||i==n-1)&&a[i][j]==0) dfs(i,j);
        else if(i>0&&(j==0||j==n-1)&&a[i][j]==0) dfs(i,j);
    }
    pp();
    return 0;
}

現在的程式碼就可以實現把所有不在圈裡的0標記成5,在圈裡的不變,不過程式碼還差最後一步,因為我們標記的時候將圈外的0標記成5,所以輸出的時候要將所有是5的地方輸出0,將原本是0的地方輸出2,所以將這種輸出處理一下就完成了

完整程式碼

#include<iostream>
using namespace std;
int n,a[35][35]; 
int x1[4]={0,1,0,-1};
int y1[4]={1,0,-1,0};
void pp()        
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(a[i][j]==5) cout<<0;
            else if(a[i][j]==1) cout<<1;
            else if(a[i][j]==0) cout<<2;
            cout<<" ";
        }
        cout<<endl;
    }
}
void dfs(int x,int y)
{
    a[x][y]=5;
    for(int i=0;i<4;i++)
    {
        if(x+x1[i]>=0&&x+x1[i]<n&&y+y1[i]>=0&&y+y1[i]<n)
        {
            if(a[x+x1[i]][y+y1[i]]==0)    dfs(x+x1[i],y+y1[i]);
        }
    }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        cin>>a[i][j];
    }
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    {
        if((i==0||i==n-1)&&a[i][j]==0) dfs(i,j);
        else if(i>0&&(j==0||j==n-1)&&a[i][j]==0) dfs(i,j);
    }
    pp();
    return 0;
}

本期的內容就到這裡了,如果你覺得對你有幫助,那麼幫忙把贊?點一下吧,如果還期待我日後的表現,就點個關注➕好了,我們下期說說bfs,再見!

相關文章