1. 程式人生 > >程式設計訓練(三)

程式設計訓練(三)

關於深度優先演算法(dfs)和廣度優先演算法(bfs)的一點東西。

文章目錄

深度優先遍歷

深度優先,本質應該是圖的遍歷,從某個頂點出發,訪問圖中的每一個頂點,而深度優先就意味著它是優先選擇更深層次的頂點,用樹來看得的話,那就是最快到達某個葉子結點。 一般步驟:

1)選取圖中某一頂點Vi為出發點,訪問並標記該頂點; 2)以Vi為當前頂點,依次搜尋Vi的每個鄰接點Vj,若Vj未被訪問過,則訪問和標記鄰接點Vj,若Vj已被訪問過,則搜尋Vi的下一個鄰接點; 3)以Vj為當前頂點,重複步驟2),直到圖中和Vi有路徑相通的頂點都被訪問為止; 4)若圖中尚有頂點未被訪問過(非連通的情況下),則可任取圖中的一個未被訪問的頂點作為出發點,重複上述過程,直至圖中所有頂點都被訪問。

通俗來講,深度優先就像是地下尋寶,好東西都在地下深處,我們要一直往下挖,差不多就留個標記,不能往下挖了,我們就往前後左右挖,然後再試著往下挖,都不行的話,我們就往上爬回去一點,再往前後左右挖,直到去過所有能去的地方,回到地面上。

經典例子

我們來看一個圖 在這裡插入圖片描述 我們按照深度優先來排列這個無向圖的拓撲序列,其中字母按ABCDEFG順序儲存。從標記頂點A並從它出發,訪問A的鄰接點C、D、F,在同深度的情況下,按照儲存順序優先選擇C並標記;然後繼續訪問C的鄰接點B、D,這裡優先選B並標記;在B無鄰接點的情況下回退一個節點,接著訪問C的另一個鄰接點D並標記;一直回退到A,因為D已經被標記,所以選擇A的最後一個鄰接點F,按照之前的步驟訪問,最後得到的序列為:A -> C -> B -> D -> F -> G -> E

實際應用

單詞接龍

單詞接龍是一個與我們經常玩的成語接龍相類似的遊戲,現在我們已知一組單詞,且給定一個開頭的字母,要求出以這個字母開頭的最長的“龍”(每個單詞都最多在“龍”中出現兩次),在兩個單詞相連時,其重合部分合為一部分,例如 beast和astonish,如果接成一條龍則變為beastonish,另外相鄰的兩部分不能存在包含關係,例如at 和 atide 間不能相連。

輸入輸出格式

輸入格式: 輸入的第一行為一個單獨的整數n (n≤20)表示單詞數,以下n每行有一個單詞,輸入的最後一行為一個單個字元,表示“龍”開頭的字母。你可以假定以此字母開頭的“龍”一定存在. 輸出格式: 只需輸出以此字母開頭的最長的“龍”的長度

看程式碼

#include<iostream>
#include<string>

#define maxSize 30

using namespace std;

int n,maxLength;
int repeat[maxSize][maxSize],visit[maxSize];
string words[maxSize];

//預處理,把重複長度存到repeat
void pre(string a,string b,int x,int y){
    int length_a = a.size()-1,length_b = b.size()-1;

    for(int i = 0;i <= length_b;i++){		//列舉
        if(a[0] == b[i]){	
            int pos=0,re=0;  //pos是當前a的第幾個字母,re是a和b的重合部分長度 
            for(int j = i;j <= length_b;j++){
                if(a[pos] == b[j]){ 
                    re++;
                    pos++;
                    if(j == length_b && re != min(length_a,length_b)+1){	//不能包含,找重合最短的
							repeat[x][y]=re;  
					}
                }
				else{
					break;
				} 
            }
        }
    }
}

void dfs(int pos,int sum){
    maxLength=max(maxLength,sum);   
    for(int i = 1;i <= n;i++){
        if(repeat[i][pos] && visit[i]){
            visit[i]--;
            int length=sum+words[i].size()-repeat[i][pos];  //新的長度
            dfs(i,length);  //接上新單詞繼續搜尋 
            visit[i]++;			
        }
    }
}

int main(){
	char ch;
	int i,j;

    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>words[i];
        visit[i] = 2; //每個單詞能用兩次 
    }

    cin>>ch;
	for(i = 1;i <= n;i++){
		for(j = 1;j <= n;j++){
			pre(words[i],words[j],i,j); //預處理repeat陣列 
		}   
	}
      

    for(i = 1;i <= n;i++){
        if(words[i][0] == ch){  //找到開始部分 
            visit[i]--;
            dfs(i,words[i].size()); //深搜 
            visit[i]++;
        }  
    }

    cout<<maxLength<<endl;

	system("pause");
    return 0;
}

廣度優先遍歷

廣度優先,也可以叫寬度優先,更像是樹的層序遍歷,是一層一層的去訪問結點的,只有在訪問完一層以後,再去訪問下一層結點,一直到訪問葉子結點所在的層。 一般步驟:

1、從圖中某個頂點V0出發,並訪問此頂點; 2、從V0出發,訪問V0的各個未曾訪問的鄰接點W1,W2,…,Wk;然後,依次從W1,W2,…,Wk出發訪問各自未被訪問的鄰接點; 3、重複步驟2,直到全部頂點都被訪問為止。

舉個通俗的例子,廣度優先遍歷就是魔塔遊戲,你要先把這一層裡的每一個位置的怪物都打死(相當於留下標記),然後才能拿到鑰匙去到下一層。

經典例子

再看剛剛那個圖 在這裡插入圖片描述 我們可以先給這個圖簡單的分一下層:

  • 第一層:A
  • 第二層:C、D、F
  • 第三層:B、G
  • 第四層:E

然後讓我們來遍歷它。 先訪問根結點A並標記,然後按字典順序依次訪問第二層的C、D、F,並標記;接著訪問第三層的B、G,標記;最後一層的E,標記;最後的拓撲序列就是:A -> C -> D -> F -> B -> G -> E

實際應用

01迷宮

有一個僅由數字0與1組成的n×n格迷宮。若你位於一格0上,那麼你可以移動到相鄰4格中的某一格1上,同樣若你位於一格1上,那麼你可以移動到相鄰4格中的某一格0上。 你的任務是:對於給定的迷宮,詢問從某一格開始能移動到多少個格子(包含自身)。

輸入輸出格式

輸入格式: 第1行為兩個正整數n,m; 下面n行,每行n個字元,字元只可能是0或者1,字元之間沒有空格; 接下來m行,每行2個用空格分隔的正整數i,j對應了迷宮中第i行第j列的一個格子,詢問從這一格開始能移動到多少格。 輸出格式: m行,對應每個詢問輸出相應答案。

來看程式碼

#include<iostream>
#include<cstring>

#define maxSize 1000001
#define size 1001

using namespace std;

int father[maxSize],child[maxSize];		//fa為根,ch為子節點
int n;

int u[4] = {1,-1,0,0};
int v[4] = {0,0,1,-1};		//四個方向

char s[size][size];		//存迷宮

//查
int find(int x){
	return father[x] == x ? x : father[x] = find(father[x]);
}

//並
void union_(int x,int y){
    int r1 = find(x),r2 = find(y);
	if(r1 != r2){		
		child[r1] += child[r2],father[r2] = father[r1];
	}
}


//構造點到節點一一對映:(i,j)=i*n+j;
int dfs(int x,int y){
	if(father[x*n+y] != -1){
		return find(x*n+y);
	}

    father[x*n+y] = x*n+y,child[x*n+y] = 1;		//構造對映
    
	for(int k = 0;k < 4;k++){
        int dx = x+u[k],dy = y+v[k];
		if(dx >= 0 && dx < n && dy >= 0 && dy < n && s[x][y] != s[dx][dy]){
			union_(x*n+y,dfs(dx,dy));
		}	
    }
    return find(x*n+y);
}

int main(){
    int m;  
    cin>>n>>m;			
    memset(father,-1,4*n*n); 		//初始化集合,負數為根
    
	for(int i = 0;i < n;i++)
		cin>>s[i];

    int i[size],j[size];
    
	for(int k = 0;k < m;k++){
		cin>>i[k]>>j[k];		//起始位置
	}

	for(int k = 0;k < m;k++){
        cout<<child[dfs(i[k]-1,j[k]-1)]<<endl;
    }

	system("pause");
    return 0;
}

以上!