30 | 图的表示:如何存储微博、微信等社交网络中的好友关系?
列出功能需求->翻譯成邏輯算法->抽象出數據結構->確定物理存儲結構 后面的不會脫離前面的獨立存在,只存在于工作流的運用中,所以不能把它們獨立地看。
問題引入
在微博中,兩個人可以互相關注;在微信中,兩個人可以互加好友。如何存儲微博、微信等這些社交網絡的好友關系嗎?
圖的概念
| 圖 | 特點: 非線性數據結構; 圖中的元素就叫做頂點(vertex); 圖中的一個頂點可以與任意其他頂點建立連接關系,這叫做邊; |
| 舉例:微信 一個用戶看做一個頂點;兩個用戶互加好友,就建立一條邊; 每個用戶有多少個好友,對應到圖中,就叫做頂點的度(degree),就是跟頂點相連接的邊的條數。 微博:(微博的社交關系跟微信不一樣更加復雜一點。微博允許單向關注,用戶 A 關注了用戶 B,但用戶 B 可以不關注用戶 A) 如果用戶 A 關注了用戶 B,畫一條從 A 到 B 的帶箭頭的邊,來表示邊的方向。 如果用戶 A 和用戶 B 互相關注了,那就畫一條從 A 指向 B 的邊,再畫一條從 B 指向 A 的邊。 有向圖:邊有方向的圖叫做“有向圖” 無向圖:把邊沒有方向的圖就叫做“無向圖”。 有向圖中又分入度和出度 入度:有多少條邊指向這個頂點 出度:有多少條邊是以這個頂點為起點指向其他頂點 對應到微博的例子,入度就表示有多少粉絲,出度就表示關注了多少人。 | |
| QQ:親密度-如果兩個用戶經常往來,那親密度就比較高;如果不經常往來,親密度就比較低 帶權圖:在帶權圖中,每條邊都有一個權重(weight),我們可以通過這個權重來表示 QQ 好友間的親密度。 ? |
?
圖的存儲方法
| 鄰接矩陣 | 鄰接矩陣的底層依賴一個二維數組。 1、對于無向圖來說,如果頂點 i 與頂點 j 之間有邊,我們就將 A[i][j]和 A[j][i]標記為 1; 2、對于有向圖來說,如果頂點 i 到頂點 j 之間,有一條箭頭從頂點 i 指向頂點 j 的邊,那我們就將 A[i][j]標記為 1。同理,如果有一條箭頭從頂點 j 指向頂點 i 的邊,我們就將 A[j][i]標記為 1。 3、對于帶權圖,數組中就存儲相應的權重。 優點: 1、直觀、簡單; 2、基于數組,獲取兩個頂點的關系非常高效 3、另外一個好處是方便計算。這是因為,用鄰接矩陣的方式存儲圖,可以將很多圖的運算轉換成矩陣之間的運算 缺點:浪費存儲空間——對于無向圖來說,如果 A[i][j]等于 1,那 A[j][i]也肯定等于 1。實際上,我們只需要存儲一個就可以了。也就是說,無向圖的二維數組中,如果我們將其用對角線劃分為上下兩部分,那我們只需要利用上面或者下面這樣一半的空間就足夠了,另外一半白白浪費掉了。如果我們存儲的是稀疏圖(Sparse Matrix),也就是說,頂點很多,但每個頂點的邊并不多,那鄰接矩陣的存儲方法就更加浪費空間了 比如微信有好幾億的用戶,對應到圖上就是好幾億的頂點。但是每個用戶的好友并不會很多,一般也就三五百個而已。如果我們用鄰接矩陣來存儲,那絕大部分的存儲空間都被浪費了 |
| 鄰接表(類似散列表) | 方式:每個頂點對應一條鏈表,鏈表中存儲的是與這個頂點相連接的其他頂點。有向圖的鄰接表存儲方式,每個頂點對應的鏈表里面,存儲的是指向的頂點。對于無向圖來說,也是類似的,不過,每個頂點的鏈表中存儲的,是跟這個頂點有邊相連的頂點。 特點:時間、空間復雜度互換的設計思想。鄰接表存儲起來比較節省空間,但是使用起來就比較耗時間。 缺點:如果我們要確定,是否存在一條從頂點 2 到頂點 4 的邊,那就要遍歷頂點 2 對應的那條鏈表,看鏈表中是否存在頂點 4。而且,鏈表的存儲方式對緩存不友好。在鄰接表中查詢兩個頂點之間的關系比較低效。 改進:將鏈表換成其他更加高效的數據結構,比如平衡二叉查找樹比如紅黑樹 |
解答開篇
以微博為例子(數據結構是為算法服務的,所以具體選擇哪種存儲方法,與期望支持的操作有關系)。假設支持以下幾種操作:
- 判斷用戶 A 是否關注了用戶 B;
- 判斷用戶 A 是否是用戶 B 的粉絲;
- 用戶 A 關注用戶 B;
- 用戶 A 取消關注用戶 B;
- 根據用戶名稱的首字母排序,分頁獲取用戶的粉絲列表;
- 根據用戶名稱的首字母排序,分頁獲取用戶的關注列表。
因為社交網絡是一張稀疏圖,使用鄰接矩陣存儲比較浪費存儲空間。所以采用鄰接表來存儲。但是僅僅用一個鄰接表來存儲是不夠的。當我們去查詢某個用戶被哪些用戶關注了,也即用戶的粉絲列表,是非常困難的,因此再加上一個逆鄰接表。
| 鄰接表 | 存儲了用戶的關注關系; 每個頂點的鏈表中,存儲的就是這個頂點指向的頂點; 如果要查找某個用戶關注了哪些用戶,在鄰接表中查找; |
| 改進:基礎的鄰接表不適合快速判斷兩個用戶之間是否是關注與被關注的關系,將鄰接表中的鏈表改為支持快速查找的動態數據結構 因為我們需要按照用戶名稱的首字母排序,分頁來獲取用戶的粉絲列表或者關注列表,用跳表這種結構再合適不過了。這是因為,跳表插入、刪除、查找都非常高效,時間復雜度是 O(logn),空間復雜度上稍高,是 O(n)。最重要的一點,跳表中存儲的數據本來就是有序的了,分頁獲取粉絲列表或關注列表,就非常高效 | |
| 逆鄰接表 | 存儲的是用戶的被關注關系; 每個頂點的鏈表中,存儲的是指向這個頂點的頂點; 如果要查找某個用戶被哪些用戶關注了,從逆鄰接表中查找。 |
| 可能問題 | 如果對于小規模的數據,比如社交網絡中只有幾萬、幾十萬個用戶,可以將整個社交關系存儲在內存中,上面的解決思路是沒有問題。但是如果像微博那樣有上億的用戶,數據規模太大,我們就無法全部存儲在內存中了。這個時候該怎么辦呢? 思路:通過哈希算法等數據分片方式,將鄰接表存儲在不同的機器上。你可以看下面這幅圖,我們在機器 1 上存儲頂點 1,2,3 的鄰接表,在機器 2 上,存儲頂點 4,5 的鄰接表。逆鄰接表的處理方式也一樣。當要查詢頂點與頂點關系的時候,我們就利用同樣的哈希算法,先定位頂點所在的機器,然后再在相應的機器上查找 另外一種解決思路,就是利用外部存儲(比如硬盤)。用下面這張表來存儲這樣一個圖。為了高效地支持前面定義的操作,我們可以在表上建立多個索引,比如第一列、第二列,給這兩列都建立索引 |
?
?
總結
以上是生活随笔為你收集整理的30 | 图的表示:如何存储微博、微信等社交网络中的好友关系?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring中拦截/和拦截/*的区别 -
- 下一篇: Excel模糊匹配