圖的表示

1. 如何理解 「圖」

圖由頂點(vertex)和(edge)組成,頂點之間經過邊來創建一種聯繫。算法

生活中有不少符合圖結構的例子,好比社交網絡,就是一個很是典型的圖結構。在微信中,每一個用戶能夠看做是一個頂點,若是兩個用戶互爲好友,那就在這兩個頂點之間創建一條邊。一個用戶的好友數量,也就是和這個頂點相鏈接的邊的個數,稱爲這個頂點的(degree)。數據庫

在微博中,還容許單向關注,也就是說用戶 A 關注了 B,但用戶 B 能夠不關注用戶 A。對此,咱們引入邊的 「方向」 這個概念。用戶 A 關注了用戶 B,咱們就用一條從 A 到 B 的帶箭頭的邊來表示。反之,用戶 B 關注了用戶 A,咱們就用一條從 B 到 A 的帶箭頭的邊來表示。這種有方向的圖稱爲 「有向圖」,反之則稱爲 「無向圖」。數組

在有向圖中,咱們將指向這個頂點的全部邊的數量叫做入度(In-degree),以這個頂點爲起點而指向其餘頂點的全部邊的數量叫做出度(Out-degree)。在微博中,入度就是粉絲數量,出度就是關注數量。微信

另外,在 QQ 中,兩個用戶之間的親密度則是經過帶權圖(weighted graph)來表示的。所謂帶權圖,就是每條邊都有一個權重,這個權重大小就能夠做爲兩個用戶之間的親密度的度量。網絡

2. 鄰接矩陣存儲方法

圖最直觀的一種存儲方法,就是鄰接矩陣(Adjacency Matrix)。數據結構

鄰接矩陣其實就是一個二維數組。對於無向圖,若是頂點 i 和頂點 j 之間有邊,那麼 A[i][j] 和 A[j][i] 就等於 1。對於有向圖,若是邊從頂點 i 指向頂點 j ,那麼 A[i][j] 就等於 1,若是邊從頂點 j 指向頂點 i ,那麼 A[j][i] 就等於 1。而對於帶權圖,數組中存儲的就是對應邊的權重。.net

用鄰接矩陣來表示圖很是簡單、直觀,可是比較浪費內存空間。3d

對無向圖來講, A[i][j] 和 A[j][i] 始終是相同的,鄰接矩陣是一個對稱矩陣,只須要一半空間就能夠了。cdn

另外,若是存儲的是稀疏圖(Sparse Graph),也就是頂點很是多,但每一個頂點的邊並很少,那鄰接矩陣就更加浪費空間了。好比,微信有幾億用戶,但每一個用戶的好友卻不會不少。blog

鄰接矩陣的優勢就是簡單、直接,由於是基於數組存儲,所以獲取兩個頂點的關係就很是高效。並且,將圖存儲爲鄰接矩陣,還能夠將不少圖的運算轉換爲矩陣之間的運算,計算很是方便。

3. 鄰接表存儲方法

因爲鄰接矩陣比較浪費內存,咱們提出了另外一種圖的存儲方法——鄰接表(Adjacency List)。

鄰接表有點相似於散列表,每一個頂點對應一個鏈表,鏈表中存儲的是與這個頂點相鏈接的其它頂點。

鄰接矩陣存儲起來比較浪費空間,可是使用起來比較節省時間;鄰接表雖然存儲起來比較節省空間,但使用起來就會比較耗時。好比,咱們要在鄰接表中查找兩個頂點之間是否有相鏈接的邊,就要去其中一個頂點對應的鏈表中查詢是否存在另外一個頂點。其實,一切都是以時間來換空間的。

此外,咱們還能夠對鏈表進行一些改進升級,好比替換成平衡二叉查找樹、跳錶、散列表或者是有序動態數組等,來快速查找兩個頂點之間是否存在邊。

4. 微信微博是怎麼存儲好友關係的?

微信是無向圖,微博是有向圖,但兩者的解決思路是差很少的,咱們拿微博來舉例說明。針對微博用戶,假設咱們須要支持如下這幾個操做:

  • 判斷用戶 A 是否關注了用戶 B;
  • 判斷用戶 A 是不是用戶 B 的粉絲;
  • 用戶 A 關注用戶 B;
  • 用戶 A 取消關注用戶 B;
  • 根據用戶名稱的首字母排序,分頁獲取用戶的粉絲列表;
  • 根據用戶名稱的首字母排序,分頁獲取用戶的關注列表;

由於社交網絡是一張稀疏圖,所以咱們選取鄰接表來存儲。可是鄰接表只能知道某一個用戶關注了哪些用戶,而不能知道哪些用戶關注了他。所以,咱們須要一個逆鄰接表來表示用戶的被關注狀況。逆鄰接表中,每一個頂點的鏈表存儲的是以哪一個頂點開始的邊指向了這個頂點。

基礎的鄰接表不適合快速判斷兩個用戶是不是關注與被關注的關係,咱們須要對鏈表進行改進。由於咱們須要按照用戶名稱的首字母排序,分頁獲取用戶的粉絲列表和關注列表,這時候,用跳錶這種結構就再合適不過了。跳錶插入、刪除、查找的時間複雜度都是 O(logn),空間複雜度稍高,爲 O(n),可是跳錶中的數據原本就是有序的了,分頁獲取粉絲列表或者關注列表,就很是高效。

針對小規模的數據,咱們能夠將整個社交圖都存儲在內存中,但若是數據規模過大,像微博有上億的用戶,這時候就沒法所有存儲在內存中了。

針對這種狀況,咱們能夠經過哈希算法等數據分片方式,將鄰接表存儲在不一樣的機器上。當須要查找頂點之間關係的時候,就利用一樣的哈希算法,先定位頂點所在的機器,再去相應的機器上進行查找。

除此以外,咱們還能夠在外部存儲上創建數據庫,經過創建索引來高效地支持前面定義的操做。

參考資料-極客時間專欄《數據結構與算法之美》

獲取更多精彩,請關注「seniusen」!

相關文章
相關標籤/搜索