數據結構知識整理 - 構造圖

主要內容


 

前言

在線性表中,數據元素之間僅有線性關係,每個數據元素只有一個直接前驅和一個直接後繼;

在樹形結構中,數據元素之間有明顯的層次關係,並且每一層中的數據元素可能和下一層的多個元素(子結點)相關,但只能和上一層的一個元素(父結點)相關;

在圖結構中,結點之間的關係是任意的,可以說樹形結構是特殊的圖結構。


 

圖的定義

圖(Graph)G由兩個集合VG組成,記作G = (V,G)。其中V是各頂點(結點)的有窮非空集合V中的任意兩個頂點配對後作爲集合E的元素,頂點偶對亦稱爲

有向圖中,E中的元素形式爲<x,y>表示從頂點x到頂點y的一條有向邊,有向邊也稱作,x爲弧尾,y爲弧頭;

無向圖中,E中的元素形式爲(x,y),僅表示連接頂點x和頂點y的一條邊,效果同(y,x)。

 

在實際應用中,每條邊可以標上具有某種含義的數值,該數值稱爲邊上的,這些權可以表示從一個頂點到另一個頂點的距離或耗費。這種帶權的圖又稱作


 

圖的存儲結構

由於圖的任意兩個頂點之間都可能存在聯繫,因此無法以數據元素在存儲區中的物理位置來表示元素之間的關係,即圖沒有順序存儲結構,但我們可以用二維數組(矩陣)來表示元素之間的關係——鄰接矩陣。除此之外還有鏈式存儲結構,包括鄰接表十字鏈表鄰接多重表其中鄰接矩陣和鄰接表最常用。


 

鄰接矩陣

鄰接矩陣(Adjacency Matrix)是表示頂點之間相鄰關係的矩陣,存儲在二維數組中。

1)在無向圖中,若頂點v1與v2有聯繫,v1與v3沒有聯繫,則在矩陣中,(1,2)和(2,1)的值爲1,(1,3)和(3,1)的值爲0,對稱矩陣

2)在無向網中,若邊(v1,v2)的權爲7,v1與y3沒有聯繫,則在矩陣中,(1,2)和(2,1)的值爲7,(1,3)和(3,1)的值爲∞(無限)

3)在有向圖中,若v1爲弧尾,v2爲弧頭,v2不指向v1,則在矩陣中,(1,2)的值爲1,(2,1)的值爲0;

4)有向網的情況可以類推。

*需要注意的是,矩陣和二維數組的“座標”含義是不同的,矩陣的(1,1)等價於二維數組中的[0,0]

鄰接矩陣僅記錄着任意兩點間的聯繫,我們還需要一個一維數組來記錄每個頂點的信息,有邊有頂點,這樣才能構成一個完整的圖。

清楚鄰接矩陣的使用方法後,我們可以輕鬆寫出以它爲基礎的圖的結構形式。

爲了避免概念的混淆,之後我會把圖稱作鄰接矩陣圖(AMGraph)


/*鄰接矩陣的結構表示*/

#include <iostream>
using namespace std;

#define MaxInt 32767       /*表示∞(無限)*/
#define MaxVNum 100        /*表示圖中最多可以包含的頂點數*/

typedef char Vextype;      /*將頂點的數據類型設爲字符型,如果你喜歡也可以把它設成更復雜的結構體*/
typedef int Arctype;       /*將邊的權值設爲整型*/

typedef struct
{
    Vextype vexs[MaxVNum];              /*用來保存頂點信息的一維數組*/
    Arctype arcs[MaxVNum][MaxVNum];     /*鄰接矩陣*/
    int vexnum, arenum;                 /*記錄圖的頂點數和邊數*/
} AMGraph;                              /*將結構體命名爲AMGraph*/

​

 

寫出鄰接矩陣圖的結構形式後,可以開始往裏面放東西了。

下面以“無向網”爲例寫一下鄰接矩陣表示法的使用方法:

<思路>

(1)數一下無向網有多少個頂點(vexnum),多少條邊(arcnum),然後將數值輸入。當然,你也可以憑空造一個網,在先前定義的MaxVNum內賦值就行。

(2)一維數組vex內輸入頂點信息,一個循環完成。

(3)初始化鄰接矩陣,將每個權值初始化爲MaxInt。爲什麼呢?因爲矩陣裏除了若干個權值外,剩餘的都是MaxInt,不可能先把權值填好,再來一個個把沒有權值的地方填上MaxInt,這樣效率極低而且繁瑣。

(4)構造鄰接矩陣。依次輸入每條邊依附的兩個頂點v1和v2以及其權值w並賦值。因爲輸入的v1、v2有可能不在0~MaxInt的範圍內,而且矩陣和二維數組的“座標”含義是不同的,即矩陣的(1,1)等價於二維數組中的[0,0],所以需要一個函數LocateVex()幫助判斷、轉化並確定“座標”。

int CreatUDN(AMGraph &G)      /*UDN意爲Omnidirectional Net,無向網*/
{                             /*上面<思路>中寫了的東西下面就不再囉嗦嘍*/
    int i, j, w;
    cin>>G.vexnum>>G.arcnum;
    
    for(i = 0; i < G.vexnum; i++) cin>>G.vexs[i];

    for(i = 0; i < G.vexnum; i++)
        for(j = 0; j < G.vexnum; j++)
            G.arcs[i][j] = MaxInt;

    for(int k = 0; k < G.arcnum; k++)
    {
        cin>>v1>>v2>>w;
        i = LocateVex(G, v1); j = LocateVex(G, v2);
        G.arc[i][j] = G.arc[j][i] = w;
    }
 
    return 0;
}
    
    

 

鄰接矩陣的優缺點:

1)優點:

a. 便於判斷兩個頂點是否有聯繫。確定頂點後再確定矩陣上的相應位置是否非0或非MaxInt即可。

b. 便於計算各個頂點的度。其實也不用計算,數就完事了。對於無向圖,多少個(1,n)的值爲1,v1的就是多少;對於有向圖,多少個<1,n>的值爲1,v1的出度就是多少,多少個<n,1>的值爲1,v1的入度就是多少。

2)缺點:

a. 不便於增加和刪除頂點。非鏈式結構的通病。

b. 不便於統計邊的數目,需要遍歷鄰接矩陣的所有元素,時間複雜度爲O(n²)。無向圖遍歷完後還要除以2。

c. 空間複雜度高。非鏈式結構的另一通病,稀疏圖(邊或弧數較少)尤其浪費空間。


 

鄰接表

鄰接表(Adjacency List)是圖的一種鏈式結構。圖中有多少個頂點,就有多少個單鏈表,每個頂點分別作爲每個單鏈表的頭結點,與頂點相連的其餘頂點無序地(無特殊的順序要求)連接在對應的單鏈表上。

頂點間的關係蘊含在若干個單鏈表中,因此鄰接表圖(ALGraph)的結構體只需要包含{每個單鏈表的頭結點(即每個頂點)以及頂點數和邊數}。

爲了方便管理頭結點,我們可以把它們放到一維數組ALs[MaxVNum]中,且該一維數組的功能包含着鄰接矩陣圖vexs[MaxVNum]的功能。

*下面將頭結點代表的頂點稱作起始頂點

再把注意力放到單鏈表上,由於起始頂點已經確定,所以與起始頂點相連的每一個頂點都分別對應着一條邊,因此我們可以把單鏈表上的其餘結點看作邊結點

邊結點需要包含的信息有{邊依附的另一個頂點編號(因爲頂點放在一維數組ALs[MaxVNum]中,所以頂點v1的編號爲0,如此類推)、指向下一個邊結點的指針以及邊上的權}。

頭結點需要包含的信息有{起始頂點的信息、指向下一個邊結點的指針}

依據上面的思路,我們一共需要創建三個結構體

/*鄰接表的結構表示*/

typedef struct ArcNode         /*邊結點*/
{
    int anothervex;            /*另一個頂點的編號*/
    ArcNode *nextarcnode;      /*指向下一邊結點的指針*/
    int weight;                /*權*/
};

typedef struct VexNode         /*頭結點*/
{
    VexType data;              /*起始頂點的信息*/
    ArcNode *nextarcnode;      /*指向下一邊結點的指針*/
};

typedef struct ALGraph         /*鄰接表圖*/
{
    VexNode ALs[MaxInt];       /*頭結點數組*/
    int vexnum, arcnum;        /*頂點數、邊數*/
};

 

寫出鄰接表圖的結構形式後,可以開始往裏面放東西了。

下面以“無向圖”爲例寫一下鄰接表表示法的使用方法:

<思路>

(1)輸入無向圖的頂點數和邊數;

(2)一維數組ALs[MaxVNum]中輸入起始頂點的信息,並使指針域初始化爲NULL完成各單鏈表的初始化

(3)構造鄰接表。輸入起始頂點va相鄰頂點va的值,以及(va,vb)的權w

(4)new一個臨時邊結點tempnode,使tempnode中“另一頂點的編號”爲vb的編號,並使其指針指向頭結點指針原本指向的“空間”,再令頭結點指針指向tempnode完成tempnode的插入,即完成相鄰頂點的連接

(5)因爲這個是無向圖,所以我們需要對稱地完成起始頂點爲vb的單鏈表的插入

(6)在達到邊數arcnum循環執行步驟(3)(4)(5)。

int CreatUDG(ALGraph &G)
{                                /*下面的代碼已經按照<思路>步驟分塊嘍*/
    cin>>G.vexnum>>G.arcnum;

    int i, j;    
    for(i =0; i < G.vexnum; i++)
    {
        cin>>G.ALs[i].data;
        G.ALs[i].nextarcnode = NULL;
    }

    for(int k = 0; k < G.arcnum; k++)
    {
        cin>>va>>vb>>w;
        i = LocateVex(G, va); j = LocateVex(G, vb);

        tempnode1 = new ArcNode;
        tempnode1->anothervex = j;
        tempnode1->nextarcnode = G.ALs[i].nextarcnode;
        G.ALs[i].nextarcnode = tempnode1;

        tempnode2 = new ArcNode;
        tempnode2->anothervex = i;
        tempnode2->nextarcnode = G.ALs[j].nextarcnode;
        G.ALs[j].nextarcnode = tempnode2;
    }

    return 0;
}

 

鄰接表的優缺點:

1)優點:

a. 便於增加和刪除頂點。

b. 便於統計邊的數量,時間複雜度爲O(n+e)。

c. 空間效率高。

2)缺點:

a. 不便於判斷頂點間是否有聯繫。

b. 不便於計算有向圖各個頂點的入度,需要遍歷其餘所有起始頂點的單鏈表。


 

對比鄰接矩陣與鄰接表

1)由上面總結的各自的優缺點可以看出,“我的優點就是你的缺點,and vice versa”。

2)一個圖的鄰接矩陣唯一,而鄰接表不唯一。前面提過,“圖中有多少個頂點,就有多少個單鏈表,每個頂點分別作爲每個單鏈表的頭結點,與頂點相連的其餘頂點無序地(無特殊的順序要求)連接在對應的單鏈表上”,因此每個單鏈表的鏈接次序取決於算法以及輸入。

3)鄰接矩陣和鄰接表是圖最常用的兩種存儲結構。(所以“十字鏈表”和“鄰接多重表”暫時就不詳細介紹嘍_(:з」∠)_)

4)在下一篇將會講到的“遍歷圖”中,不論是深度優先搜索還是廣度優先搜索,存儲結構爲鄰接矩陣的時間複雜度都爲O(n²),存儲結構爲鄰接表的時間複雜度都爲O(n+e)


 

十字鏈表

十字鏈表(Orthogonal List)是有向圖的另一種鏈式存儲結構,可以看作是有向圖的鄰接表(單鏈表的結點數-1爲出度)逆鄰接表(單鏈表的結點數-1爲入度)結合起來的一種鏈表。

在十字鏈表中容易找到以vi(vi∈G(V))爲弧尾或弧頭的弧,因而更容易求頂點的入度和出度。


 

鄰接多重表

鄰接多重表(Adjacency Multilist)是無向圖的另一種鏈式存儲結構。

對無向圖而言,鄰接表的缺點:每一條邊(va,vb)有兩個結點,分別在第a和第b個單鏈表中,這可能會給邊的搜索、刪除等操作帶來了不便。

鄰接多重表與鄰接表的區別僅在於,同一條邊,在鄰接表中要用兩個結點表示,而在鄰接多重表中只需要一個結點

此外,鄰接多重表中增加了標誌域用以標記該條邊是否被搜索過,避免了同一條邊的重複搜索。


 

路過的圈毛君:“圖形結構分三塊講:構造圖、遍歷圖和應用圖。本來想一次寫完的,無奈精力有限,要做的事比較多.......而且我也差不多該開始護肝了_(:з」∠)_”

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