圖(Graph)和樹比起來,是一種更為複雜的非線性表結構

一 頂點&邊 

在樹中的元素稱為節點,而在圖中的元素叫作頂點(vertex)。

圖中一個頂點可以與任意其他頂點建立連接關係,這種建立的關係叫邊(edge)。

 

 

 

跟頂點相連接的邊的條數, 叫作頂點的度(degree)。

 

二、有向圖&無向圖

 

這種邊有方向的圖叫作「有向圖」。邊沒有方向的圖就叫作「無向圖」。

在有向圖中度分為入度(In-degree)和出度(Out-degree)。

  • 頂點的入度,表示有多少條邊指向這個頂點

  • 頂點的出度,表示有多少條邊是以這個頂點為起點指向其他頂點。

對應到微博的例子,入度就表示有多少粉絲,出度就表示關注了多少人。

 

三、帶權圖

在帶權圖(weighted graph)中,每條邊都有一個權重(weight),可以通過這個權重來表示 QQ 好友間的親密度。

 

 

 

 

一、鄰接表存儲方法

 

鄰接表的每個頂點對應一條鏈表,鏈表中存儲的是與這個頂點相連接的其他頂點。

無向圖可以看作每條邊都是雙方向的有向圖。

 

鄰接矩陣存儲起來比較浪費空間,但是使用起來比較節省時間。相反,鄰接表存儲起來比較節省空間,但是使用起來就比較耗時間。這是時間、空間複雜度互換的設計思想,前者是空間換時間,後者是時間換空間。

上圖鄰接表的例子中,如果要確定是否存在一條從頂點 2 到頂點 4 的邊,就要遍歷頂點 2 對應的那條鏈表,看鏈表中是否存在頂點 4。當然如果鏈過長,可以將鏈表換成紅黑樹、散列表等來提高查找效率,還可以將鏈表改成有序動態數組,通過二分查找的方法來快速定位兩個頂點之間否存在邊。

 

二、如何存儲微博、微信等社交網路中的好友關係?

基本實現思想

用兩個鄰接表來存儲

  • 一個鄰接表存儲某個用戶關注了哪些用戶,即用戶的關注關係
  • 另一個鄰接表稱為逆鄰接表存儲某個用戶被哪些用戶關注,即用戶的粉絲列表

如果要查找某個用戶關注了哪些用戶,在鄰接表中查找即可;如果要查找某個用戶被哪些用戶關注了,從逆鄰接表中查找即可。

 

 

較大數據量實現方案

如果對於小規模的數據,可以將整個社交關係存儲在記憶體中。

如果對於大規模的數據,可以通過哈希演算法等數據分片方式,將鄰接表存儲在不同的機器上。

例如:

 

 

 

 

當要查詢頂點與頂點關係的時候,就利用同樣的哈希演算法,先定位頂點所在的機器再在相應的機器上查找。

另外一種解決思路,就是利用外部存儲(比如硬碟),資料庫是經常用來持久化存儲關係數據的

用下面這張表來存儲這樣一個圖。為了高效地支援前面定義的操作,可以在表上建立這兩列都建立索引。

 

 

 

 

BFS&DFS

圖的遍歷:依次把圖中所有的頂點都訪問一次。

圖上的搜索演算法有

  • 深度優先搜索演算法
  • 廣度優先搜索演算法

廣度優先搜索和深度優先搜索是圖上的兩種最常用、最基本的搜索演算法,僅適用於狀態空間不大的搜索。

廣度優先搜索,採用地毯式層層推進,從起始頂點開始,依次往外遍歷。廣度優先搜索需要藉助隊列來實現,遍歷得到的路徑就是起始頂點到終止頂點的最短路徑。

深度優先搜索,採用回溯思想,適合用遞歸或棧來實現。遍歷得到的路徑並不是最短路徑。

一、深度優先搜索(DFS)

深度優先搜索(Depth-First-Search),簡稱 DFS。

最直觀的例子就是「走迷宮」。假設你站在迷宮的某個岔路口,然後想找到出口。你隨意選擇一個岔路口來走,走著走著發現走不通的時候,你就回退到上一個岔路口,重新選擇一條路繼續走,直到最終找到出口。這種走法就是一種深度優先搜索策略。

下圖中,搜索的起始頂點是 s,終止頂點是 t,希望在圖中尋找一條從頂點 s 到頂點 t 的路徑。如果映射到迷宮那個例子,s 就是你起始所在的位置,t 就是出口。

下圖標記了遞歸演算法的搜索的過程,裡面實線箭頭表示遍歷,虛線箭頭表示回退。但深度優先搜索最先找出來的路徑,並不是頂點 s 到頂點 t 的最短路徑。

 

 

深度優先搜索的過程類似於樹的先序遍歷,首先從例子中體會深度優先搜索。

例如圖 1 是一個無向圖,採用深度優先演算法遍歷這個圖的過程為:

  1. 首先任意找一個未被遍歷過的頂點,例如從 V1 開始,由於 V1 率先訪問過了,所以,需要標記 V1 的狀態為訪問過;
  2. 然後遍歷 V1 的鄰接點,例如訪問 V2 ,並做標記,然後訪問 V2 的鄰接點,例如 V4 (做標記),然後 V8 ,然後 V5 ;
  3. 當繼續遍歷 V5 的鄰接點時,根據之前做的標記顯示,所有鄰接點都被訪問過了。此時,從 V5 回退到 V8 ,看 V8 是否有未被訪問過的鄰接點,如果沒有,繼續回退到 V4 , V2 , V1 ;
  4. 通過查看 V1 ,找到一個未被訪問過的頂點 V3 ,繼續遍歷,然後訪問 V3 鄰接點 V6 ,然後 V7 ;
  5. 由於 V7 沒有未被訪問的鄰接點,所有回退到 V6 ,繼續回退至 V3 ,最後到達 V1 ,發現沒有未被訪問的;
  6. 最後一步需要判斷是否所有頂點都被訪問,如果還有沒被訪問的,以未被訪問的頂點為第一個頂點,繼續依照上邊的方式進行遍歷。

根據上邊的過程,可以得到圖 1 通過深度優先搜索獲得的頂點的遍歷次序為:

V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7

所謂深度優先搜索,是從圖中的一個頂點出發,每次遍歷當前訪問頂點的臨界點,一直到訪問的頂點沒有未被訪問過的臨界點為止。然後採用依次回退的方式,查看來的路上每一個頂點是否有其它未被訪問的臨界點。訪問完成後,判斷圖中的頂點是否已經全部遍歷完成,如果沒有,以未訪問的頂點為起始點,重複上述過程。

深度優先搜索是一個不斷回溯的過程。

二、廣度優先搜索(BFS)

廣度優先搜索(Breadth-First-Search),簡稱 BFS。它是一種「地毯式」層層推進的搜索策略,即先查找離起始頂點最近的,然後是次近的,依次往外搜索

廣度優先搜索類似於樹的層次遍歷。從圖中的某一頂點出發,遍歷每一個頂點時,依次遍歷其所有的鄰接點,然後再從這些鄰接點出發,同樣依次訪問它們的鄰接點。按照此過程,直到圖中所有被訪問過的頂點的鄰接點都被訪問到。

最後還需要做的操作就是查看圖中是否存在尚未被訪問的頂點,若有,則以該頂點為起始點,重複上述遍歷的過程。

img

以上面的無向圖為例,假設 V1 作為起始點,遍歷其所有的鄰接點 V2 和 V3 ,以 V2 為起始點,訪問鄰接點 V4 和 V5 ,以 V3 為起始點,訪問鄰接點 V6 、 V7 ,以 V4 為起始點訪問 V8 ,以 V5 為起始點,由於 V5 所有的起始點已經全部被訪問,所有直接略過, V6 和 V7 也是如此。 以 V1 為起始點的遍歷過程結束後,判斷圖中是否還有未被訪問的點,由於圖 1 中沒有了,所以整個圖遍歷結束。遍歷頂點的順序為:

V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8