圖表算法—無向圖


1. 圖表算法

  本篇隨筆寫的是圖表算法。圖表可以大致分為兩種:無向圖和有向圖。

  無向圖例子:

  

  有向圖例子:

  

  從上述例子中可以看出,一個圖表是由數個頂點和邊組成的。

  其中,無向圖的邊是沒方向的,即兩個相連的頂點可以互相抵達。

  而有向圖的邊是有方向的,即兩個相連的頂點,根據邊的方向,只能由一個頂點通向另一個頂點。(當然,如有向圖例子中的2和3,由於有兩個指向對方的方向,所以2和3是互通的。)

   本文寫的是無向圖的算法,有向圖的算法將寫在下篇隨筆中。

2. 無向圖(UndirectedGraph)

  要討論無向圖的算法前,首先要討論如何創建一個無向圖。

  創建無向圖有兩個關鍵點:

  a. 這個無向圖有哪些點

  b. 哪些點可以通往哪些點

  舉個例子:

  

  顯然,這個無向圖有13個點。0可以通往1,2,5,6;6可以通往0,4;5可以通往0,3,4。

  即0的鄰居點有1,2,5,6;6的鄰居點有0,4。

  那么,我們怎么告訴程序這個無向圖呢?

  目前有兩種主流的方法:鄰接矩陣(Adjacency-matrix)和鄰接列表(Adjacency-list)。

  鄰接矩陣:

  

  如果兩個點相鄰,則用1表示,否則用0。例如1是0的鄰居點,所以0和1相交對應的格子為1。

  從這個圖可以看出,如果每個點不是與大量的其它點互為鄰居,則會有很多0出現。如果點的數量龐大,矩陣將十分巨大,且有很多空間浪費(0占據的格子)。

  鄰接列表:

  

  就是用一個數組把所有點裝起來,每個位置是由對應點的所有鄰居點形成的數組。

  即adj[0]=6,2,1,5(0可以通往1,2,5,6);adj[6]=0,4(6可以通往0,4)。

  這個列表比較適合每個點只與少量其它點相鄰的情況。

  我們可以根據實際情況進行選擇用列表還是矩陣。

  通過創建一個矩陣或者列表,程序可以知道這個無向圖有哪些點和點與點之間的聯系。如果要加點或者刪點,應該不難實現,這里不做詳述。

  接下來,我們討論算法:深度優先搜索(depth-first search)和廣度優先搜索(breadth-first search)。

 

3.深度優先搜索(depth-first search)

  深度優先搜索可以解決的問題有:

  a. 給定一個點,求所有它能抵達的所有點。

  b. 給定兩個點,它們是否能抵達彼此,如果能,求路線。

  等等。

  這個算法簡單地用一句話概括就是:一路走到盡頭,然后返回上一個分支,走另一條路到盡頭,再返回,再走其他路,直到全部走完為止。

  從例子入手:

  

  為了方便講解,作下圖:

  

  0~12代表着圖中的所有點。

  一開始所有點標記為False(F),當我們走到某個點后,此點標記為True(T)。標記過的點不需要再走一次。

  EdgeTo記錄了部分路線,所有部分路線可以整合成一個完整的路線。例如從E點抵達A點,則記為EdgeTo[A]=E;

  Group記錄了這些點形成了多少個組,且哪些點分別屬於哪些組。這樣可以瞬間查到兩個點是否相連。例如本例中分為3組,其中7,8屬於一組;9,10,11,12屬於第二組;剩下的屬於第三組。

  先看最大的這組:

  

  從0開始,先把0標記了(變紅)。記0為第0組。

  

  0的鄰居點有4個,隨便選一條路來走。例如去2:2標記為True;2從0來,故EdgeTo[2]=0; 2與0屬於同一組:0組。

  

  然后2只有一個鄰居點0,但0已標記為T,故不用走。2已經無路可走了,返回上一個分支點0。

  0再選擇走其它路:6。6標記為True;6從0來,故EdgeTo[6]=0; 6與0屬於同一組:0組。

  

  然后6有兩個鄰居點:0和4。由於0已標記為T,不走。

  去4:4標記為True;4從6來,故EdgeTo[4]=6; 4與6屬於同一組:0組。

 

  然后4有三個鄰居點:6,3,5。由於6已標記為T,不走。

  隨便選一個路走:5。5標記為True;5從4來,故EdgeTo[5]=4; 5與4屬於同一組:0組。

  然后5有三個鄰居點:0,3,4。由於0,4已標記為T,不走。

  只能走3:3標記為True;3從5來,故EdgeTo[3]=5; 3與5屬於同一組:0組。

 

  然后3有兩個鄰居點:5和4。由於4,5已標記為T,不走。

  3無路可走,返回上一個分支5。5有三個鄰居點:0,3,4。由於0,3,4已標記為T,不走。

  5無路可走,返回上一個分支4。4有三個鄰居點:5,3,6。由於3,5,6已標記為T,不走。

  4無路可走,返回上一個分支6。6有兩個鄰居點:0,4。由於0,4已標記為T,不走。

  6無路可走,返回上一個分支0。0有四個鄰居點:5,1,2,6。由於5,2,6已標記為T,不走。

  0走向1:1標記為True;1從0來,故EdgeTo[1]=0; 1與0屬於同一組:0組。

  1有一個鄰居點:0。由於0已標記為T,不走。返回上一個分支0。

  0有四個鄰居點:5,1,2,6。由於5,2,6,1已標記為T,不走。

  0無路可走,且無上一個分支點。查找標記為F的其它點,隨便選一個來走,如7。

  7標記為True;7屬於組1。

  

  然后重復上述過程,直到所有點標記為T為止。

  看懂上述例子的思路,那么通用思路也是一樣的。如果想查1和6是否相連,只需看它們所屬組別是否相同即可。

  如查1到6的路徑,從1和6分別查EdgeTo,查到重復點,則把路線結合即可。

代碼大概是這樣的:

  

 

4. 廣度優先搜索(breadth-first search)

  從深度優先搜索的思路上看,顯然不適合用於尋找最短路徑。

  這里介紹另一種思路:廣度優先搜索。此算法需要用到隊列(queue),對隊列不熟悉的,可以先看下隊列

  從例子入手:

  

  為了方便講解,作下圖:

  

  Marked和EdgeTo與深度優先搜索的一樣,DistTo表示起始點與目標點的距離。如:0與3的距離為2,即DistTo[3]=2。

  先看最大的這組:

  

  從0開始,先把0標記了(變紅)。記0到0的距離為0。(從哪個點開始可以根據需求來決定。)把0加入到隊列A中。

  

  0的鄰居點有4個:5,1,2,6。隊列A輸出一個值:0,隊列A按順序輸入值5,1,2,6。(這個順序沒所謂,1,6,5,2也行。)

  5,1,2,6全部標記為True,從0點來,距離0點為1:

  

  隊列A輸出一個值,如果上一步是按5,1,2,6順序輸入的,則這里輸出的是5.

  5的鄰居點有3個:0,3,4。由於0已經標記為True,所以不管。將3,4按順序輸入到隊列A中(當然,順序無所謂)。 

   3,4全部標記為True,從5點來,距離0點為2:

  

  隊列A輸出一個值,如果之前那步是按5,1,2,6順序輸入的,則這里輸出的是1.

  1的鄰居點有1個:0。由於0已經標記為True,所以不管。

  隊列A輸出一個值,如果之前那步是按5,1,2,6順序輸入的,則這里輸出的是2.

  2的鄰居點有1個:0。由於0已經標記為True,所以不管。

  隊列A輸出一個值,如果之前那步是按5,1,2,6順序輸入的,則這里輸出的是6.

  6的鄰居點有2個:0,4。由於0,4已經標記為True,所以不管。

  隊列A輸出一個值,如果上一步是按3,4順序輸入的,則這里輸出的是3.

  3的鄰居點有2個:5,4。由於5,4已經標記為True,所以不管。

  隊列A輸出一個值,如果上一步是按3,4順序輸入的,則這里輸出的是4.

  4的鄰居點有3個:5,3,6。由於3,5,6已經標記為True,所以不管。

  隊列A為空,這部分處理完畢。

  其它部分也是相同處理方法,DistTo要小心處理,一般要遍歷全部的時候,DistTo是不需要的。DistTo一般用於尋找兩個點之間的最短距離與路線。

  當然也可以像深度優先處理那樣加入Group來記錄哪些點在哪個團體中。

  通用思路可以從例子中看出來,實在看不出的,可以看下面的代碼。

代碼大概是這樣的:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM