數據結構:什麼是圖

前幾天發佈的關於“圖”的漫畫中,十字鏈表的部分有一些小錯誤,在此做一下更正。

 

 

 

 

 

圖的概念

 

究竟什麼是圖呢?大家先來想一想咱們常用的互聯網產品。

 

舉個栗子,大家一定都用過微信,假設你的微信朋友圈中有若干好友:張三、李四、王五、趙六、七大姑、八大姨。

 

 

 

而你七大姑的微信號裏,又有若干好友:你、八大姨、Jack、Rose。

 

 

 

微信中,許許多多的用戶組成了一個多對多的朋友關係網,這個關係網就是數據結構當中的圖(Graph)

 

再舉一個栗子,咱們在用百度地圖的時候,常常會使用導航功能。比如你在地鐵站A附近,你想去的地點在地鐵站F附近,那麼導航會告訴你一個最佳的地鐵線路換乘方案。

 

 

這許許多多地鐵站所組成的交通網絡,也可以認爲是數據結構當中的圖。

 

圖,是一種比樹更爲複雜的數據結構。樹的節點之間是一對多的關係,並且存在父與子的層級劃分;而圖的頂點(注意,這裏不叫節點)之間是多對多的關係,並且所有頂點都是平等的,無所謂誰是父誰是子。

 

 

 

圖的術語

 

下面我們來介紹一下圖的基本術語:

 

 

在圖中,最基本的單元是頂點(vertex),相當於樹中的節點。頂點之間的關聯關係,被稱爲邊(edge)

 

在有些圖中,每一條邊並不是完全等同的。比如剛纔地鐵線路的例子,從A站到B站的距離是3公里,從B站到C站的距離是5公里......這樣就引入一個新概念:邊的權重(Weight)。涉及到權重的圖,被稱爲帶權圖(Weighted Graph)

 

還有一種圖,頂點之間的關聯並不是完全對稱的。還拿微信來舉例,你的好友列表裏有我,但我的好友列表裏未必有你。

 

 

 

 

 

 

這樣一來,頂點之間的邊就有了方向的區分,這種帶有方向的圖被稱爲有向圖

 

 

 

相應的,在QQ當中,只要我把你從好友裏刪除,你在自己的好友列表裏也就看不到我了。(貌似是這樣)

 

因此,QQ的好友關係可以認爲是一個沒有方向區分的圖,這種圖被稱爲無向圖

 

 

圖的表示

 

 

 

鄰接矩陣

 

擁有n個頂點的圖,它所包含的連接數量最多是n(n-1)個。因此,要表達各個頂點之間的關聯關係,最清晰易懂的方式是使用二維數組(矩陣)。

 

具體如何表示呢?我們首先來看看無向圖的矩陣表示:

 

 

 

如圖所示,頂點0和頂點1之間有邊關聯,那麼矩陣中的元素A[0][1]與A[1][0]的值就是1;頂點1和頂點2之間沒有邊關聯,那麼矩陣中的元素A[1][2]與A[2][1]的值就是0。

 

像這樣表達圖中頂點關聯關係的矩陣,就叫做鄰接矩陣

 

需要注意的是,矩陣從左上到右下的一條對角線,其上的元素值必然是0。這樣很容易想明白:任何一個頂點與它自身是沒有連接的。

 

同時,無向圖對應的矩陣是一個對稱矩陣,V0和V1有關聯,那麼V1和V0也必定有關聯,因此A[0][1]和A[1][0]的值一定相等。

 

那麼,有向圖的鄰接矩陣又是什麼樣子呢?

 

 

從圖中可以看出,有向圖不再是一個對稱矩陣。從V0可以到達V1,從V1卻未必能到達V0,因此A[0][1]和A[1][0]的值不一定相等。

 

鄰接矩陣的優點是什麼呢?簡單直觀,可以快速查到一個頂點和另一頂點之間的關聯關係。

 

鄰接矩陣的缺點是什麼呢?佔用了太多的空間。試想,如果一個圖有1000個頂點,其中只有10個頂點之間有關聯(這種情況叫做稀疏圖),卻不得不建立一個1000X1000的二維數組,實在太浪費了。

 

鄰接表和逆鄰接表

 

爲了解決鄰接矩陣佔用空間的問題,人們想到了另一種圖的表示方法:鄰接表。

 

在鄰接表中,圖的每一個頂點都是一個鏈表的頭節點,其後連接着該頂點能夠直接達到的相鄰頂點。

 

 

很明顯,這種鄰接表的存儲方式,佔用的空間比鄰接矩陣要小得多。

 

要想查出從頂點0能否到達頂點1,該怎麼做呢?很簡單,我們從頂點0開始,順着鏈表的頭節點向後遍歷,看看後繼的節點中是否存在頂點1。

 

要想查出頂點0能夠到達的所有相鄰節點,也很簡單,從頂點0向後的所有鏈表節點,就是頂點0能到達的相鄰節點。

 

那麼,要想查出有哪些節點能一步到達頂點1,又該怎麼做呢?這樣就麻煩一些了,我們要遍歷每一個頂點所在的鏈表,看看鏈表節點中是否包含節點1,最後發現頂點0和頂點3可以到達頂點1。

 

 

像這種逆向查找的麻煩,該如何解決呢?我們可以是用逆鄰接表來解決。

 

 

 

逆鄰接表顧名思義,和鄰接表是正好相反的。逆鄰接表每一個頂點作爲鏈表的頭節點,後繼節點所存儲的是能夠直接達到該頂點的相鄰頂點。

 

這樣一來,要想查出有哪些節點能一步到達頂點1就容易了,從頂點1向後的所有鏈表節點,就是能一步到達頂點1的節點。

 

因此,我們可以根據實際需求,選擇使用鄰接表還是逆鄰接表。

 

 

 

十字鏈表

 

十字鏈表長什麼樣呢?用最直觀的示意,是下面這樣:

 

 

如圖所示,十字鏈表的每一個頂點,都是兩個鏈表的根節點,其中一個鏈表存儲着該頂點能到達的相鄰頂點,另一個鏈表存儲着能到達該頂點的相鄰節點。

 

不過,上圖只是一個便於理解的示意圖,我們沒有必要把鏈表的節點都重複存儲兩次。在優化之後的十字鏈表中,鏈表的每一個節點不再是頂點,而是一條邊,裏面包含起止頂點的下標。

 

十字鏈表節點和邊的對應關係,如下圖所示:

 

 

 

因此,優化之後的十字鏈表,是下面這個樣子:

 

 

圖中每一條帶有藍色箭頭的鏈表,存儲着從頂點出發的邊;每一條帶有橙色箭頭的鏈表,存儲着進入頂點的邊。初學十字鏈表的時候,可能會覺得有些亂。

 

 

總結

 

1.我們這一次介紹了圖的定義和分類。根據圖的邊是否有方向,可分爲有向圖無向圖。根據圖的邊是否有權重,可分爲帶權無權圖。當然,也可以把兩個維度結合起來描述,比如有向帶權圖,無向無權圖等等。

 

2.圖的表示方法有很多種。包括鄰接矩陣、鄰接表、逆鄰接表、十字鏈表。(還有一種鄰接多重表,有興趣的小夥伴可以自學下)

 

 

 

 

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