算法8:巧妙的鄰接表(數組實現)


之前我們介紹過圖的鄰接矩陣存儲法,它的空間和時間復雜度都是N2,如今我來介紹第二種存儲圖的方法:鄰接表,這樣空間和時間復雜度就都是M。

對於稀疏圖來說,M要遠遠小於N2。

先上數據。例如以下

1
2
3
4
5
6
4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7

091650e0f00oqrcjcfnq93.png

第一行兩個整數m

n表示頂點個數(頂點編號為1~n),m表示邊的條數。接下來m行表示,每行有3個數x y z表示頂點x到頂點y的邊的權值為z下圖就是一種使用鏈表來實現鄰接表的方法。

091650gyll6hbqbjyxls8s.png


上面這樣的實現方法為圖中的每一個頂點(左邊部分)都建立了一個單鏈表(右邊部分這樣我們就能夠通過遍歷每一個頂點的鏈表。從而得到頂點全部的邊了使用鏈表來實現鄰接表對於痛恨指針的的朋友來說,這簡直就是噩夢。

這里我將為大家介紹一種使用數組來實現鄰接表這是一種在實際應用中很easy實現的方法。這樣的方法為每一個頂點ii1~n保存了一個類似鏈表的東西。里面保存的是從頂點i出發的全部的邊,詳細例如以下。


首先我們依照讀入的順序為每一條邊進行編號1~m

比方第一條邊“1 4 9”的編號就是1,“1 3 7”這條邊的編號是5


這里uvw三個數組用來記錄每條邊的詳細信息,即u[i]v[i]w[i]表示第i條邊是從第u[i]號頂點到v[i]號頂點(u[i]àv[i],且權值為w[i]


091650h35zq3wgx30x3oe3.png

再用一個first數組來存儲每一個頂點當中一條邊的編號以便待會我們來枚舉每頂點全部的邊(你可能存儲當中一條邊的編號就能夠了?不可能吧,每一個頂點都須要存儲其全部邊的編號才行吧!甭着急。繼續往下看比方1號頂點有一條邊是 “1 4 9”(該條邊的編號是1),那么就將first[1]的值設為1。假設某個頂點i沒有以該頂點為起始點的邊,則將first[i]的值設為-1。如今我們來看看詳細怎樣操作,初始狀態例如以下。


091650zw3988qpj5iljj8g.png


咦?上圖中怎么多了一個next數組。有什么作用呢?不着急。待會再解釋。如今先讀入第一條邊“1 4 9”。


讀入第1條邊1 4 9,將這條邊的信息存儲到u[1]v[1]w[1]

同一時候這條邊賦予一個編號由於這條邊是最先讀入的,存儲在uvw數組下標為1的單元格中,因此編號就是1。這條邊的起始點是1號頂點。因此將first[1]的值設為1


另外這條編號為1的邊1號頂點u[1]為起始點第一條邊,所以next[1]的值設為-1。也是說,假設當前這條編號為i的邊,是我們發現的u[i]起始點的第一條邊。就將next[i]的值設為-1(貌似的這個next數組非常神奇啊⊙_

091651kwo5g0aycy07wfwd.png


讀入第2條邊4 3 8將這條邊的信息存儲到u[2]v[2]w[2]中。這條邊的編號為2

這條邊的起始頂點是4號頂點,因此將first[4]的值設為2另外這條“編號為2的邊”是我們發現以4號頂點為起始點的第一條邊,所以將next[2]的值設為-1

091651fkswcj34c05k8w4k.png


讀入第3條邊1 2 5將這條邊的信息存儲到u[3]v[3]w[3]中。這條邊的編號為3,起始頂點是1號頂點。我們發現1號頂點已經有一條編號為的邊了。假設此時將first[1]的值設為3那“編號為1的邊豈不是丟失了?我有辦法,此時僅僅需將next[3]的值設為1就可以。如今你知道next數組是用來做什么的吧。

next[i]存儲的是“編號為i的邊”的“一條邊”的編號。

091651df28foy9ct7fl7qf.png


讀入第4條邊(2 4 6),將這條邊的信息存儲到u[4]v[4]w[4]中,這條邊的編號為4起始頂點是2號頂點,因此將first[2]的值設為4

另外這條“編號為4的邊”是我們發現以2號頂點為起始點的第一條邊。所以將next[4]的值設為-1

091652vf4eg69f5zfsese9.png


讀入第5條邊(1 3 7),將這條邊的信息存儲到u[5]v[5]w[5]中,這條邊的編號為5,起始頂點1號頂點。此時須要first[1]的值設為5並將next[5]的值改為3

091652li6mzammza242tmp.png


此時,假設我們想遍歷1號頂點的每一條邊就非常easy1號頂點的當中一條邊的編號存儲在first[1]中。其余的邊則能夠通過next數組尋找到請看下圖

091652rtjh5qe2211eee58.png

                                                                             算法8:巧妙的鄰接表(數組實現)


細心的同學會發現,此時遍歷邊某個頂點邊的時候的遍歷順序正好與讀入時候的順序相反。

由於為每一個頂點插入邊的時候都直接插入鏈表的首部而不是尾部。只是這並不會產生不論什么問題。這正是這樣的方法的其妙之處


創建鄰接表的代碼例如以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int  n,m,i;
//u、v和w的數組大小要依據實際情況來設置,要比m的最大值要大1
int  u[6],v[6],w[6];
//first和next的數組大小要依據實際情況來設置,要比n的最大值要大1
int  first[5],next[5];
scanf ( "%d %d" ,&n,&m);
//初始化first數組下標1~n的值為-1,表示1~n頂點臨時都沒有邊
for (i=1;i<=n;i++)
     first[i]=-1;
for (i=1;i<=m;i++)
{
     scanf ( "%d %d %d" ,&u[i],&v[i],&w[i]); //讀入每一條邊
     //以下兩句是關鍵啦
     next[i]=first[u[i]];
     first[u[i]]=i;
}


接下來怎樣遍歷每一條邊呢?我們之前說過事實上first數組存儲的就是每一個頂點ii1~n)的第一條邊。比方1號頂點的第一條邊是編號為5的邊(1 3 7)。2號頂點的第一條邊是編號為4的邊(2 4 6)。3號頂點沒有出向邊,4號頂點的第一條邊是編號為2的邊(2 4 6)。那么怎樣遍歷1號頂點的每一條邊呢?也非常easy。

請看下圖:


遍歷1號頂點全部邊的代碼例如以下。

1
2
3
4
5
6
k=first[1]; // 1號頂點當中的一條邊的編號(事實上也是最后讀入的邊)
while (k!=-1)  //其余的邊都能夠在next數組中依次找到
{
     printf ( "%d %d %d\n" ,u[k],v[k],w[k]);
     k=next[k];
}


遍歷每一個頂點的全部代碼例如以下

1
2
3
4
5
6
7
8
9
for (i=1;i<=n;i++)
{
     k=first[i];
     while (k!=-1)
     {
         printf ( "%d %d %d\n" ,u[k],v[k],w[k]);
         k=next[k];
     }
}


        能夠發現使用鄰接表來存儲圖的時間空間復雜度是O(M),遍歷每一條邊的時間復雜度是也是O(M)。假設一個圖是稀疏圖的話,M要遠小於N2

因此稀疏圖選用鄰接表來存儲要比鄰接矩陣來存儲要好非常多。

   歡迎轉載,碼字不easy啊,轉載麻煩注明出處

   【啊哈!算法】系列8:巧妙的鄰接表(數組實現)  http://ahalei.blog.51cto.com/4767671/1391988



免責聲明!

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



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