內容整理于魚c工作室教程
1. 圖的基本概念
1.1 圖的概念
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。
1.2 圖的基本概念
頂點: 線性表中我們把數據元素叫元素,樹中叫結點,在圖中數據元素我們則稱之為頂點(Vertex)。線性表可以沒有數據元素,稱為空表,樹中可以沒有結點,叫做空樹,而圖結構在咱國內大部分的教材中強調頂點集合V要有窮非空。
線性表中,相鄰的數據元素之間具有線性關系,樹結構中,相鄰兩層的結點具有層次關系,而圖結構中,任意兩個頂點之間都可能有關系,頂點之間的邏輯關系用邊來表示,邊集可以是空的。
無向邊:若頂點Vi到Vj之間的邊沒有方向,則稱這條邊為無向邊(Edge),用無序偶(Vi,Vj)來表示。
有向邊:若從頂點Vi到Vj的邊有方向,則稱這條邊為有向邊,也成為弧(Arc),用有序偶來表示,Vi稱為弧尾,Vj稱為弧頭。
上圖G2是一個有向圖,G2={V2,E2},其中V2={A,B,C,D},E2={<B,A>,<B,C>,<C,A>,<A,D>}
簡單圖:在圖結構中,若不存在頂點到其自身的邊,且同一條邊不重復出現,則稱這樣的圖為簡單圖。
無向完全圖:在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。含有n個頂點的無向完全圖有n*(n-1)/2條邊。
有向完全圖:在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,則稱該圖為有向完全圖。含有n個頂點的有向完全圖有n*(n-1)條邊。
稀疏圖和稠密圖:這里的稀疏和稠密是模糊的概念,都是相對而言的,通常認為邊或弧數小于n*logn(n是頂點的個數)的圖稱為稀疏圖,反之稱為稠密圖。
有些圖的邊或弧帶有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight),帶權的圖通常稱為網(Network)。
假設有兩個圖G1=(V1,E1)和G2=(V2,E2),如果V2?V1,E2?E1,則稱G2為G1的子圖(Subgraph)。
圖的頂點與邊之間的關系:
(1) 無向圖的頂點與邊之間的關系:
對于無向圖G=(V,E),如果邊(V1,V2)∈E,則稱頂點V1和V2互為鄰接點(Adjacent),即V1和V2相鄰接。
邊(V1,V2)依附(incident)于頂點V1和V2,或者說邊(V1,V2)與頂點V1和V2相關聯。
頂點V的度(Degree)是和V相關聯的邊的數目,記為TD(V),如下圖,頂點A與B互為鄰接點,邊(A,B)依附于頂點A與B上,頂點A的度為3。
(2) 有向圖的頂點與邊之間的關系:
對于有向圖G=(V,E),如果有∈E,則稱頂點V1鄰接到頂點V2,頂點V2鄰接自頂點V1。
以頂點V為頭的弧的數目稱為V的入度(InDegree),記為ID(V),以V為尾的弧的數目稱為V的出度(OutDegree),記為OD(V),因此頂點V的度為TD(V)=ID(V)+OD(V)。
下圖頂點A的入度是2,出度是1,所以頂點A的度是3。
簡單路徑:序列中頂點不重復出現的路徑稱為簡單路徑,除了第一個頂點和最后一個頂點之外,其余頂點不重復出現的回路,稱為簡單回路或簡單環。
下圖左側是簡單環,右側不是簡單環:
連通圖:在無向圖G中,如果從頂點V1到頂點V2有路徑,則稱V1和V2是連通的,如果對于圖中任意兩個頂點Vi和Vj都是連通的,則稱G是連通圖(ConnectedGraph)。
下圖左側不是連通圖,右側是連通圖:
1.3 圖的存儲結構
圖的存儲結構相比較線性表與樹來說就復雜很多。我們回顧下,對于線性表來說,是一對一的關系,所以用數組或者鏈表均可簡單存放。樹結構是一對多的關系,所以我們要將數組和鏈表的特性結合在一起才能更好的存放。那么我們的圖,是多對多的情況,另外圖上的任何一個頂點都可以被看作是第一個頂點,任一頂點的鄰接點之間也不存在次序關系。
因為任意兩個頂點之間都可能存在聯系,因此無法以數據元素在內存中的物理位置來表示元素之間的關系(內存物理位置是線性的,圖的元素關系是平面的)。如果用多重鏈表來描述倒是可以做到,但在幾節課前的樹章節我們已經討論過,純粹用多重鏈表導致的浪費是無法想像的(如果各個頂點的度數相差太大,就會造成巨大的浪費)。
1.3.1 鄰接矩陣
(無向圖)
考慮到圖是由頂點和邊或弧兩部分組成,合在一起比較困難,那就很自然地考慮到分為兩個結構來分別存儲。頂點因為不區分大小、主次,所以用一個一維數組來存儲是狠不錯的選擇。而邊或弧由于是頂點與頂點之間的關系,一維數組肯定就搞不定了,那我們不妨考慮用一個二維數組來存儲。
圖的鄰接矩陣(Adjacency Matrix)存儲方式是用兩個數組來表示圖。一個一維數組存儲圖中頂點信息,一個二維數組(稱為鄰接矩陣)存儲圖中的邊或弧的信息。
我們可以設置兩個數組,頂點數組為vertex[4]={V0,V1,V2,V3},邊數組arc[4][4]為對稱矩陣(0表示不存在頂點間的邊,1表示頂點間存在邊)。
對稱矩陣:所謂對稱矩陣就是n階矩陣的元滿足a[i][j]=a[j][i](0<=i,j<=n)。即從矩陣的左上角到右下角的主對角線為軸,右上角的元與左下角相對應的元全都是相等的。
有了這個二維數組組成的對稱矩陣,我們就可以很容易地知道圖中的信息:
要判定任意兩頂點是否有邊無邊就非常容易了;
要知道某個頂點的度,其實就是這個頂點Vi在鄰接矩陣中第i行(或第i列)的元素之和;
求頂點Vi的所有鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]為1就是鄰接點咯。
(有向圖)
無向圖的邊構成了一個對稱矩陣,貌似浪費了一半的空間,那如果是有向圖來存放,會不會把資源都利用得很好呢?
可見頂點數組vertex[4]={V0,V1,V2,V3},弧數組arc[4][4]也是一個矩陣,但因為是有向圖,所以這個矩陣并不對稱,例如由V1到V0有弧,得到arc[1][0]=1,而V0到V1沒有弧,因此arc[0][1]=0。
另外有向圖是有講究的,要考慮入度和出度,頂點V1的入度為1,正好是第V1列的各數之和,頂點V1的出度為2,正好是第V1行的各數之和。
(網)
在圖的術語中,我們提到了網這個概念,事實上也就是每條邊上帶有權的圖就叫網。
1.3.2 鄰接表
(無向圖)
鄰接矩陣看上去是個不錯的選擇,首先是容易理解,第二是索引和編排都很舒服~
但是我們也發現,對于邊數相對頂點較少的圖,這種結構無疑是存在對存儲空間的極大浪費。
因此我們可以考慮另外一種存儲結構方式,例如把數組與鏈表結合一起來存儲,這種方式在圖結構也適用,我們稱為鄰接表(AdjacencyList)。
鄰接表的處理方法是這樣:
圖中頂點用一個一維數組存儲,當然,頂點也可以用單鏈表來存儲,不過數組可以較容易地讀取頂點信息,更加方便。圖中每個頂點Vi的所有鄰接點構成一個線性表,由于鄰接點的個數不確定,所以我們選擇用單鏈表來存儲。
(有向圖)
若是有向圖,鄰接表結構也是類似的,我們先來看下把頂點當弧尾建立的鄰接表,這樣很容易就可以得到每個頂點的出度:
(網)
對于帶權值的網圖,可以在邊表結點定義中再增加一個數據域來存儲權值即可:
1.3.3 十字鏈表
鄰接表固然優秀,但也有不足,例如對有向圖的處理上,有時候需要再建立一個逆鄰接表~那我們思考了:有沒有可能把鄰接表和逆鄰接表結合起來呢?答案是肯定的,這就是我們現在要談的十字鏈表(Orthogonal List)。為此我們重新定義頂點表結點結構:
1.3.4 鄰接多重表
講了有向圖的優化存儲結構,對于無向圖的鄰接表,有沒有問題呢?如果我們在無向圖的應用中,關注的重點是頂點的話,那么鄰接表是不錯的選擇,但如果我們更關注的是邊的操作,比如對已經訪問過的邊做標記,或者刪除某一條邊等操作,鄰接表就顯得不那么方便了。
到底有多煩?小甲魚用圖片告訴你:
若要刪除(V0,V2)這條邊,就需要對鄰接表結構中邊表的兩個結點進行刪除操作。
1.3.5 邊集數組
邊集數組是由兩個一維數組構成,一個是存儲頂點的信息,另一個是存儲邊的信息,這個邊數組每個數據元素由一條邊的起點下標(begin)、終點下標(end)和權(weight)組成。
1.4 圖的遍歷
樹的遍歷我們談了四種方式,大家回憶一下,樹因為根結點只有一個,并且所有的結點都只有一個雙親,所以不是很難理解。但是談到圖的遍歷,那就復雜多了,因為它的任一頂點都可以和其余的所有頂點相鄰接,因此極有可能存在重復走過某個頂點或漏了某個頂點的遍歷過程。對于圖的遍歷,如果要避免以上情況,那就需要科學地設計遍歷方案,通常有兩種遍歷次序方案:它們是深度優先遍歷和廣度優先遍歷。
1.4.1 深度優先遍歷
深度優先遍歷(DepthFirstSearch),也有稱為深度優先搜索,簡稱為DFS。
它的具體思想類似于課程開頭講的找鑰匙方案,無論從哪一間房間開始都可以,將房間內的墻角、床頭柜、床上、床下、衣柜、電視柜等挨個尋找,做到不放過任何一個死角,當所有的抽屜、儲藏柜中全部都找遍,接著再尋找下一個房間。現在請大家一起來想辦法走以下這個迷宮:
我們可以約定右手原則:在沒有碰到重復頂點的情況下,分叉路口始終是向右手邊走,每路過一個頂點就做一個記號。迷宮走完了,所有的頂點也遍歷過了,這就是深度優先遍歷!反應快的童鞋一定會感覺深度優先遍歷其實就是一個遞歸的過程嘛~如果再細心觀察,你會發現整個遍歷過程就像是一棵樹的前序遍歷!
1.4.2 廣度優先遍歷
廣度優先遍歷(BreadthFirstSearch),又稱為廣度優先搜索,簡稱BFS。
如果以之前我們找鑰匙的例子來講,運用深度優先遍歷意味著要先徹底查找完一個房間再開始另一個房間的搜索。但我們知道,鑰匙放在沙發地下等犄角旮旯的可能性極低,因此我們運用新的方案:先看看鑰匙是否放在各個房間的顯眼位置,如果沒有,再看看各個房間的抽屜有沒有。這樣逐步擴大查找的范圍的方式我們稱為廣度優先遍歷。
廣度優先遍歷是連通圖的一種遍歷策略。其基本思想如下:
1、從圖中某個頂點V0出發,并訪問此頂點;
2、從V0出發,訪問V0的各個未曾訪問的鄰接點W1,W2,…,Wk;然后,依次從W1,W2,…,Wk出發訪問各自未被訪問的鄰接點;
3、重復步驟2,直到全部頂點都被訪問為止。