深度/廣度優先遍歷如何實現對(非)連通圖的遍歷?

圖的遍歷

上一篇文章給大家介紹了圖相關的一些重要的基本概念以及圖的存儲表示方式。在明確了圖的存儲方式之後,我們進一步給大家介紹如何遍歷圖中的各個頂點。

從給定圖中任意指定的頂點(稱爲初始點)出發,按照 某種搜索方法 沿着圖的邊訪問圖中的所有頂點 ,使每個頂點僅被訪問一次 ,這個過程稱爲 圖的遍歷 。通過圖的遍歷得到的頂點序列稱爲圖遍歷序列

我們已經知道圖的頂點之間是多對多的關係,然而從一個頂點出發一次只能找另外一個相鄰的頂點。如圖所示,我們從頂點1 出發,可以有多種遍歷路徑來遍歷圖中的所有頂點:

根據搜索方法的不同,可以將圖的遍歷方法總結爲兩種 :深度優先遍歷(DFS )廣度優先遍歷(BFS)。下面我們將對這兩種遍歷算法進行詳細的介紹。

深度優先遍歷(DFS )

要搞懂深度優先遍歷算法,我們首先要弄清楚深度優先遍歷的過程:

  1. 我們選定一個初始點,從圖中某個初始頂點v出發,首先訪問初始點v;
  2. 選擇一個與頂點v相鄰且沒有被訪問過的頂點w,再從w出發進行深度優先搜索,知道圖中國與當前頂點v鄰接的所有頂點都被訪問過爲止。

知道了圖深度優先遍歷的遍歷過程,那麼我們該如何通過代碼實現呢?其實,通過觀察遍歷的過程,我們可以得知深度優先遍歷的過程符合先進後出的特點,因此我們可以通過棧或者遞歸的思想來實現。

我們通過棧來保存訪問過的頂點,如何確定一個頂點是否訪問過? 設置 一個visited[] 全局數組,visited[i]=0 表示頂點i 沒有訪問; visited[i]=1 表示頂點i 已經訪問過。因此,我們採用鄰接表來實現DFS算法可以寫爲:

void DFS(ALGraph *G ,int v)
{ ArcNode *p; int w;
	visited[v]=1; // 置已訪問標記
	printf("%d " ,v); // 輸出被訪問頂點的編號
	p=G->adjlist[v].firstarc;//p 指向頂點v 的第一條邊的邊頭節點
	while (p!=NULL)
	{ 	w=p->adjvex;
		if (visited[w]==0)
			DFS(G ,w); // 若w 頂點未訪問 , 遞歸訪問它
		p=p->nextarc; //p 指向頂點v 的下一條邊的邊頭節點
	}
} // 該算法的時間複雜度爲O(n+e)。 。

我們以初始訪問頂點v=2爲例對深度優先遍歷的過程進行可視化表示:

整個遍歷過程爲:我們首先訪問頂點2,讓頂點2入棧,然後根據鄰接表訪問頂點2的下一個相鄰且沒有被訪問的頂點,找到該頂點爲1,然後讓頂點1入棧,並在頂點1的鄰接表序列中查找與頂點1相鄰且沒有被訪問過的下一個頂點;以此類推,最終得到DFS序列爲:2 1 0 3 4。

廣度優先遍歷算法

瞭解了深度優先遍歷的過程以及實現方式,我們按照同樣的方法對廣度優先遍歷算法進行分析首先是廣度優先遍歷算法的過程:

  1. 訪問初始點v,接着訪問v的所有未被訪問過的鄰接頂點v_1,v_2,…,v_t 。

  2. 然後按照v_1,v_2,…,v_t 的次序,依次訪問每個頂點的所有未被訪問過的鄰接點。

  3. 以此類推,知道圖中所有和初始點v有路徑相通的頂點都被訪問過爲止。

與深度優先遍歷不同的是廣度優先遍歷算法符合先進先出的特點,因此我們通過隊列來實現。

爲了確定一個頂點是否 訪問過,我們和深度優先遍歷一樣設置 一個visited[] 數組 ,visited[i]=0 表示頂點i沒有訪問; visited[i]=1 表示頂點i 已經訪問過。但是這裏的visited[] 數組不再設置爲全局變量,而是局部變量。我們採用鄰接表來實現BFS算法可以寫爲:

void BFS(ALGraph *G ,int v)
{   ArcNode *p; int w ,i;
    int queue[MAXV] ,front=0 ,rear=0; // 定義循環隊列
    int visited[MAXV];
    for (i=0;i<G->n;i++)
    	visited[i]=0; // 訪問標誌數組初始化
    printf("%2d" ,v); // 輸出被訪問頂點的編號
    visited[v]=1; // 置已訪問標記
    rear=(rear+1)%MAXV;
    queue[rear]=v; //v
    while (front!=rear) // 隊列 不空時循環
    { 	front=(front+1)%MAXV;
   	 	w=queue[front]; // 出隊並賦給w
    	p=G->adjlist[w].firstarc; // 找w 的第一個的鄰接點
    	while (p!=NULL)
    	{ if (visited[p->adjvex]==0)
    		{ 	printf(“%2d” ,p->adjvex); // 訪問之
                visited[p->adjvex]=1;
                rear=(rear+1)%MAXV; // 相鄰頂點 進隊
                queue[rear]=p->adjvex;
            }
    		p=p->nextarc; // 找下一個鄰接頂點
    	}
    }
} //該算法的時間複雜度爲O(n+e)。

同樣,我們以初始訪問頂點v=2爲例對深度優先遍歷的過程進行可視化表示:

圖中的豎列綠色線是爲了說明廣度優先遍歷是按照層來訪問的。整個遍歷過程爲:我們首先訪問頂點2,讓頂點2入隊,然後根據鄰接表讓頂點2出隊並訪問頂點2,然後讓其下一個相鄰且沒有被訪問的頂點入隊,找到該頂點爲1,然後讓頂點1入隊,然後鄰接表的邊節點指針指向下一個鄰接頂點,如果不爲空則出隊1訪問之,尋找與頂點1相鄰且沒有被訪問的頂點入隊;以此類推,最終得到DFS序列爲:2 1 3 4 0。

上面介紹了連通圖的遍歷方法,對於無向連通圖調用一次DFS 或BFS ,能夠 訪問到圖中的所有 頂點。那麼對於非連通圖的遍歷該如何實現呢?對於無向非連通圖 ,調用一次DFS 或BFS,只能訪問到初始點所在連通分量中的所有頂點 ,不可能訪問到其他連通分量中的頂點 。我們可以分別遍歷每個連通分量 , 這樣便能夠訪問到圖中的所有頂點 。因此,採用 深度優先遍歷方法和廣度 優先遍歷方法遍歷非連通圖的算法可以描述爲:

//深度優先遍歷方法
void DFS1(ALGraph *G)
{ int i;
    for (i=0;i<G->n;i++) // 遍歷所有未訪問過的頂點
    	if (visited[i]==0)
    		DFS(G ,i);
}
// 廣度優先遍歷方法
void BFS1(ALGraph *G)
{ int i;
	for (i=0;i<G->n;i++) // 遍歷所有未訪問過的頂點
		if (visited[i]==0)
			BFS(G ,i);
}

對於非連通圖,調用DFS() /BFS() 的次數恰好等於連通分量的個數。

應用示例

假設圖G 採用鄰接表存儲 , 設計一個算法 ,判斷無向圖G 是否連通 。 若連通則返回true; 否則返回false。

求解思路

採用某種遍歷方式來判斷 無向圖G 是否連通。這裏用深度優先遍歷方法,先給visited[] 數組(爲全局變量)置初值0 ,然後從0 頂點開始遍歷該圖。在一 次遍歷之後,若所有頂點i的 的visited[i] 均爲1 ,則該圖是連通的;否則不連通 。

算法設計

int visited[MAXV];
bool Connect(ALGraph *G) // 判斷無向圖G 的連通性
{  int i;
    bool flag=true;
    for (i=0;i<G->n;i++) //visited 數組置初值
        visited[i]=0;
    DFS(G,0);  // 調用前面的中DSF 算法, 從頂點0 開始深度優先遍歷
    for (i=0;i<G->n;i++)
      if (visited[i]==0)
      { flag=false;
        break;
      }
    return flag;
}

關注公衆號,獲取更多的資料。
img

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章