圖的基本概念
圖(Graph)是一種非線性數據結構。
頂點:樹中的元素我們叫做節點;圖中的元素我們叫做頂點(vertex)。
邊:圖中的一個頂點可以與任意其他頂點建立關係,我們把這種建立的關係叫做邊(edge)。
度:以微信爲例,我們可以把每個用戶看成一個頂點,如果兩個用戶之間互加好友,那就在兩者之間建立一條邊。所以整個微信的好友關係就可以用一張圖來表示。其中每個用戶有多少個好友,對應到圖中就是就叫做頂點的度(degree),即跟頂點相連接的邊的條數。
實際上,微博的好友關係跟微信不同,微博更復雜,因爲微博允許單行關注,即A用戶關注了B用戶,但是B用戶可以不關注A用戶。這種關係也可以用圖來表示。不過此時的關係即邊是有方向的。
如果A關注B,那麼就畫一條從A指向B的邊。
如果A和B互相關注,那麼畫一條從A指向B的邊,再畫一條從B指向A的邊。
有向圖:我們把這種邊有方向的圖叫做有向圖,而反之邊沒有方向的就叫做無向圖。
無向圖中有度的概念,表示一個頂點有多少條邊。
有向圖中也有度的概念,分爲入度和出度。
頂點的入度(In-degree):表示有多少條邊指向這個頂點。
頂點的出度(out-degree):表示有多少條邊是以這個頂點爲起點指向其他頂點的。
以微博爲例,入度可以表示有多少粉絲,出度可以表示自己關注了多少人。
實際上,QQ的好友關係跟微博和微信都不同,QQ有親密度,這個親密度其實就是圖中的邊的權重的概念。
帶權圖:在圖中每條邊都有一個權重(weight),可以通過這個權重來表示QQ好友間的親密度。
圖的存儲
鄰接矩陣存儲法
圖最直觀的一種存儲方法就是鄰接矩陣存儲法(Adjacency Matrix)
鄰接矩陣底層依賴於一個二維數組。
對於無向圖來說,如果頂點i和頂點j之間有邊,我們就將a[i][j]和a[j][i]標記爲1
對於有向圖來說,如果頂點i到頂點j之間,有一條箭頭從頂點i指向頂點j的邊,那麼我們就將
a[i][j]標記爲1,同理,如果有一條箭頭從頂點j指向頂點i的邊,那麼我們就將a[j][i]標記爲1。
對於帶權圖,數組中就存儲相應的權重。
使用鄰接矩陣來存儲圖,確實很簡單直接,但是非常浪費空間。
比如對於無向圖來說,如果a[i][j]=1,那麼a[j][i]肯定也等於1.實際上我們只需要存儲一個就可以了。也就是說,無向圖中我們可以用對角線將其劃分爲上下兩部分,那麼我們只需要利用上下兩部分就足夠了,剩下一半就浪費了。
還有,如果存儲的圖示稀疏圖(Sparse Matrix),即頂點很多但是每個頂點的邊並不多。這種情況下鄰接矩陣存儲法更加浪費空間了。
比如說,微信用戶有很多好幾個億,也就是有好幾個億的頂點,但是每個用戶也就幾百好友不等,那麼絕大多數的存儲空間就浪費了。
鄰接矩陣存儲法的優點:簡單直接、基於數據訪問友好、矩陣計算方便。
鄰接表存儲法
鄰接表存儲法(Adjacency List)跟散列表相似,如圖,每個頂點對應一條鏈表,鏈表中存儲的是跟這個頂點連接的其他頂點,下圖是一個有向圖鄰接表的存儲,每個頂點對應鏈表存儲的是這個頂點所指向的其他頂點,可以用來表示自己關注的微博用戶。無向圖類似。
其實鄰接矩陣存儲法簡單直接,耗費內存,但是速度比較快。而鄰接表空間利用率高但查找速度相對慢。這就是空間和時間相互交換的思想的體現。
比如我們想確定一個頂點是否有到另一個頂點的邊,利用鄰接表存儲方式,就得遍歷原始頂點對應的那條鏈表。由於鏈表存儲方式對緩存不友好,相對於鄰接矩陣存儲法查找效率就偏低了。但是不要緊,可以升級改造這條鏈表爲其他支持快速crud的數據結構比如跳錶、紅黑樹這種平衡二叉查找樹等。或者改造爲動態有序數組利用二分法查找定位頂點是否存在。