圖的基本概念及圖演算法


回顧:數據的邏輯結構
在這裏插入圖片描述

一,圖的定義的基本術語

圖: G =(V,E)

  • V:頂點(數據元素)的有窮非空集合;
  • E:邊的有窮集合。

無向圖(Undirected graph): 每條邊的沒有方向,eg:G1

  • 無向圖G=(V,E),V代表點集合,E代表邊集合。E 中的元素形式為集合 {u,v},代表邊的兩端。

有向圖(Directed graph): 每條邊都有方向,eg:G2

  • 有向圖G=(V,E),V代表點集合,E代表邊集合。E 中的元素形式為 (u,v),u代表起點,v代表中點。
    在這裏插入圖片描述

完全圖: 任意兩個頂點都有一條邊相連(有去有回)
在這裏插入圖片描述
稀疏圖: 有很少邊或弧的圖(e < n*log n)。

稠密圖: 有較多邊或弧的圖。

網: 邊 / 弧帶權值的圖。

鄰接: 有邊 / 弧相連的兩個頂點之間的關係。

  • 存在(vi,vj),則稱 vi 和 vj 互爲鄰接點
  • 存在 <vi,vj>,則稱 vi 鄰接到 vj,vj 鄰接於 vi

關聯(依附): 邊 / 弧與頂點之間的關係。

頂點的度: 與該頂點相關聯的邊的數目,記爲:TD(v)

  • 在有向圖中,頂點的度等於該點的入度與出度之和。
  • 頂點v的入度是以v爲終點的有向邊的條數,記作:ID(v)
  • 頂點v的出度是以v爲始點的有向邊的條數,記作:OD(v)
    在這裏插入圖片描述

那麼,問:當有向圖中僅1個頂點的入度爲0,其餘頂點的入度均爲1,此時是什麼形狀?(答案:有向樹,如下圖)
在這裏插入圖片描述
路徑: 接續的邊構成的頂點序列。
路徑長度: 路徑上邊或弧的數目 / 權值之和。
例,有無向圖如上圖,由 v5 到 v3 的路徑可以是 [v5,v2,v3] 也可以是 [v5,v4,v3] 或 [v5,v2,v4,v3] 和 [v5,v4,v2,v3]。它們的路徑長度分別爲 2,2,3,3;若有權值的話需要將權值相加。
在這裏插入圖片描述
迴路(環): 第一個頂點和最後一個頂點相同的路徑。(起點與重點相同)
簡單路徑: 除路徑起點和終點可以相同外,其餘頂點均不相同的路徑。
簡單迴路(簡單環): 除路徑起點和終點相同外,其餘頂點均不相同的路徑。
在這裏插入圖片描述
(a)簡單路徑:0 - 1 - 3 - 2,起點終點可以不同,但不能出現重複頂點。
(b)非簡單路徑:0 - 1 - 3 - 0 - 1 - 2,頂點 v0 和 v1 重複了。
(c)迴路:0 - 1 - 3 - 0,起點和終點均爲 v0

連通圖(強連通圖): 在無(有)向圖 G = ( V, {E} )中,對任意兩個頂點v、u,都存在從 v 到 u 的路徑,則稱G是連通圖(強連通圖)。
在這裏插入圖片描述
權與網: 圖中邊或弧所具有的相關數稱爲(表明從一個頂點到另一個頂點的距離或耗費)。帶權的圖稱爲

子圖: 設有兩個圖 G = ( V, {E} ) 和 G1 = ( V1, {E1} ),若 V1 包含於 V,E1 包含於 E,則稱 G1 是 G 的子圖。
在這裏插入圖片描述
連通分量(強連通分量)

  • 將非連通圖分爲了若干連通的部分;
  • 無向圖G的極大連通子圖稱爲 G的連通分量
    極大連通子圖意思是:該子圖是 G 連通子圖,將 G 的任何不在該子圖中的頂點加入,子圖不再連通。
    在這裏插入圖片描述
  • 有向圖G的極大強連通子圖稱爲 G的強連通分量
    極大強連通子圖意思是:該子圖是 G 的強連通子圖,將 G 的任何不在該子圖中的頂點加入,子圖不再是強連通的。
    在這裏插入圖片描述
    極小連通子圖: 該子圖是G 的連通子圖,在該子圖中刪除任何一條邊子圖都不再連通。
    生成樹: 包含無向圖G 所有頂點的極小連通子圖。
    生成森林: 對非連通圖,有各個連通分量的生成樹的集合
    在這裏插入圖片描述

二,鄰接(Adjacency)

首先回顧一下上邊提到的鄰接,什麼是鄰接
鄰接: 有邊 / 弧相連的兩個頂點之間的關係。

  • 存在(vi,vj),則稱 vi 和 vj 互爲鄰接點
  • 存在 <vi,vj>,則稱 vi 鄰接到 vj,vj 鄰接於 vi

2.1 列表表示(Adjacency-List)

無向圖
可藉由 Adjacency-list 表示,將每一個相鄰的點集合用一個List 存起來。
在這裏插入圖片描述
有向圖
可藉由Adjacency-List表示,將每個點所指向的點集合存在List中。
在這裏插入圖片描述

2.2 矩陣表示(Adjacency-Matrix)

無向圖
可藉由 Adjacency-Matrix 表示,利用一個二維矩陣將每對點之間是否有邊連起來;

  • 有存1;
  • 沒有存0。
    在這裏插入圖片描述
    有向圖
    可藉由 Adjacency Matrix 表示,利用一個二維矩陣A
  • 若(u,v)是一個邊則A[u][v]=1 ,由 u 指向 v
  • 反之 A[u][v]=0。
    在這裏插入圖片描述

三,圖的遍歷

遍歷定義:

  • 從已給的連通圖中的某一頂點出發,沿着一些邊訪遍圖中所有的頂點,且使每一個頂點僅被訪問一次,就叫做圖的遍歷,他是圖的基本運算
    在這裏插入圖片描述

遍歷實質: 找每個頂點的鄰接點的過程。

圖的特點: 圖中可能存在迴路,且圖的任一頂點都可能與其他頂點相同,在訪問完某個頂點之後可能會沿着某些邊又回到了曾經訪問的頂點。(例如,圖中v2 和 v3

那麼如何避免重複訪問?
解決思路:設置輔助數組 visited[ n ],用來標記是否訪問過該頂點

  • 初始狀態爲 visited[ i ] = 0
  • 頂點 vi 被訪問時,改 visited[ i ] = 1,以此來避免多次訪問。

圖的常用遍歷

  • 深度優先搜索(Depth_First Search ---- DFS
  • 廣度優先搜索(Breadth_First Search ---- BFS

3.1 深度優先搜索(DFS)

參考:視頻講解 青島大學–王卓
引例:點亮所有的燈
思路:選擇一條路徑,已知走到沒有未點亮的燈或到盡頭時再回頭,每次退一步,若有未點亮的燈則選擇該點,若沒有未點亮的燈則再退回一步,直到所有燈全部都被點亮。
在這裏插入圖片描述
最初的選擇不同,會有不同的訪問路徑。
在這裏插入圖片描述

3.1.1 深度優先搜索遍歷算法的實現

在這裏插入圖片描述
設置輔助數組 visited[ n ] = [ 0,0,0,0,0,0 ],分別對應每一個頂點 vi

首先,我們選擇 v2 爲起始點,則 visited[ 1 ] = 1( visited[ n ] = [ 0,1,0,0,0,0 ] )
然後來看鄰接矩陣,只看 v2 這一行就可以,可以看出與 v1 相連,則我們走到頂點 v1(設置 visited[ 0 ] = 1;visited[ n ] = [ 1,1,0,0,0,0 ]);

接下來,看鄰接矩陣 v1 這一行,可以看出 v2、v3、v4 均與 v1 相連,則從後兩者之間做出選擇,選擇 v3(設置 visited[ 2 ] = 1;visited[ n ] = [ 1,1,1,0,0,0 ]);

以此類推,下一個鄰接點爲 v5(設置 visited[ 4 ] = 1;visited[ n ] = [ 1,1,1,0,1,0 ]) ,接下來與 v5 鄰接的 v2、v3 均已經訪問過,則依次退回 v2、v1,發現有未訪問的點 v4,則選該點爲下一個鄰接點,之後一直重複上述操作。。。

最終達到 visited[ n ] = [ 1,1,1,1,1,1 ],訪問路徑爲 v2 - v1 - v3 - v5 - v4 - v6
在這裏插入圖片描述

void DFS(AMGraph G, int v){		//圖G爲鄰接矩陣類型
	cout<<v;					//訪問第v個頂點
	visited[v] = true;			//依次檢查鄰接矩陣v所在的行
	for(w = 0; w < G.vexnum; w++)
		if((G.arcs[v][w] != 0)&&(!visited[w]))
			DFS(G, w),
		//w是v的鄰接點,如果w未訪問,則遞歸調用DFS
}

3.1.2 DFS 算法效率分析

鄰接矩陣來表示圖(假設有 n 個頂點,則爲 n*n矩陣)

  • 遍歷圖中每一個頂點都要從頭掃描該頂點所在的行
  • 時間複雜度爲 O(n2)

鄰接表表示圖

  • 雖然有 2e 個表結點,但只需要掃描 e 個結點即可完成遍歷,加上訪問 n 個頭結點的時間
  • 時間複雜度爲 O(n + e)

結論:

  • 稠密圖(邊較多)適於在鄰接矩陣上進行深度遍歷;
  • 稀疏圖(邊少 e < n*log n)適於在鄰接表上進行深度遍歷。

3.1.3 非連通圖的遍歷

在這裏插入圖片描述
以上圖爲例:
首先,以a爲初始點(隨機的),接下來按照上邊的步驟依次找到鄰接點直到全部都訪問過(該連通分量已經遍歷完成),因爲我們知道是非連通圖,則我們在沒有訪問過得頂點中再隨機找一個頂點開始新的遍歷。

3.2 廣度優先搜索(BFS)

參考:視頻講解 青島大學–王卓
與DFS不同,廣度優先搜索是先選定一個初始點,然後訪問他所有的鄰接點,如下圖所示
在這裏插入圖片描述
方法:

  • 從圖的某一節點出發,首先依次訪問該節點的所有鄰接點 vi1、vi2、…、vin
  • 再按這些頂點被訪問的先後次序訪問與他們想鄰接的所有未被訪問的頂點;
  • 重複此過程,直至所有頂點均被訪問爲止。

其實就是一層一層的訪問,如下例
在這裏插入圖片描述
非連通圖的廣度遍歷
在這裏插入圖片描述

3.2.1 實現和步驟

在這裏插入圖片描述
以上圖爲例:

  1. 首先我們需要兩個輔助數組
    visited[ n ] = [ 0,0,0,0,0,0,0,0 ] 用來記錄頂點是否訪問過,訪問後設置 visited[ i ] = 1;
    next[ n ] = [ -1,-1,-1,-1,-1,-1,-1,-1 ] 用來記錄訪問點的所有未訪問鄰接點;
  2. 選擇 v1 爲初始點,則記錄 visited[ 0 ] = 1 和 next[ n ] = [ 1,2,3,-1,-1,-1,-1,-1 ], v1 的鄰接點是 v2、v3
  3. 然後依次訪問 v2、v3,並記錄 visited[ 1, 2 ] = 1 並且 next[ n ] = [ 1,2,3,4,5,6,7,-1 ];
  4. 依照上邊的步驟繼續訪問頂點4567並記錄他們的鄰接點
  5. 最終使得
    visited[ n ] = [ 1,1,1,1,1,1,1,1 ]
    next[ n ] = [ 1,2,3,4,5,6,7,8 ]
  6. 當 visited[ n ]中所有元素均爲 1,next[ n ]中沒有任何元素爲 -1 時候說明迭代完成。

3.2.2 BFS 算法及其效率

void BFS(Graph G, int v){	// 按廣度優先非遞歸遍歷連通圖G
	cout<<v;				// 訪問第 v 個頂點
	visited[v] = true;		
	InitQueue(Q);			// 輔助隊列Q初始化,置空
	EnQueue(Q, v);			// v 進隊
	while(!QueueEmpty(Q)){	// 隊列非空
		DeQueue(Q, u);		// 隊頭元素出隊並置爲 u
		for(w = FirstAdjVex(G,u);w >= 0;w = NextAdjVex(G, u, w))
		if(!visited[w]){	// w 是 u尚未訪問的鄰接點
			cout<<w;		
			visited[w] = true;
			EnQueue(Q, w); // w 進隊
			}//
	}//
}//

BFS 算法效率分析

  • 如果使用鄰接矩陣,則BFS對於每一個被訪問到的頂點,都要循環檢測矩陣中的整整一行(n 個元素),總的時間代價爲 O( n2 )。
  • 用鄰接表來表示圖,雖然有 2e 個表結點,但只需要掃描 e 個結點即可完成遍歷,加上訪問 n 個頭結點的時間,時間複雜度爲 O( n + e )。

更多關於圖的應用:

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