圖的存儲結構
圖的存儲結構相較線性表與樹來說就更加複雜了。首先,我們口頭上說的“頂點的位置”或“鄰接點的位置”只是一個相對的概念。其實從圖的邏輯結構定義來看,圖上任何一個頂點都可被看成是第一個頂點,任一頂點的鄰接點之間也不存在次序關係。如下圖
用之前學過的數據結構來表示圖,不是很方便
鄰接矩陣
考慮到圖是由頂點和邊或弧兩部分組成。合在一起比較困難,那就很自然地考慮到分兩個結構來分別存儲。頂點不分大小、主次,所以用一個一維數組來存儲是很不錯的選擇。而邊或弧由於是頂點與頂點之間的關係,一維搞不定,那就考慮用一個二維數組來存儲。於是鄰接矩陣誕生了啦。
圖的鄰接矩陣(Adjacency Matrix)存儲方式是是用兩個數組來表示圖。一個一維數組存儲圖中頂點信息,一個二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧信息。
設圖G有n個頂點,則鄰接矩陣是一個n X n的方陣,定義爲:
下圖是一個無向圖
對於矩陣的主對角線的值,全爲是0是因爲不存在頂點到自身的邊。比如v0到v0 ,arc[0][1]=1是因爲v0到v1的邊存在。
對稱矩陣就是n階矩陣的元滿足aij=aji(0 i,j n)。即從矩陣的左上角到右下角的主對角線爲軸,右上角的元與左下角對應 的元全都相等。
有了這個矩陣,我們就可以很容易地知道圖中的信息。
1. 我們要判定任意兩頂點是否有邊無邊就非常容易
2. 我們要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行(或第i列)的元素之和。如頂點V1的度主是1+0+1+0=2
3. 求頂點Vi的所有鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]爲1就是鄰接點。
我們再來看一個有向圖樣例,如下圖
與無向圖同樣的辦法,判斷頂點vi到vj是否存在弧,只需要查找矩陣中arc[i][j]是否爲1即可。
之前我提到網的概念,也就是每條邊上帶有權的的圖叫做網。
設圖G是網圖,有n個頂點,則鄰接矩陣是一個nXn的方陣,定義爲:
這裏wij表示(vi,vj)或<vi,vj>上的權值。∞表示一個計算機允許的、大於所有邊上權值的值,也就是一個不可能的權限值。這是由於權值wij大多數情況下是正值,因此必須要用一個不可能的值來代表不存在。權值wij大多數情況下是正值,但個別時候可能就是0,甚至有可能是負值。因此必須要用一個不可能的值來代表不存在。
如下圖:
typedef char VertexType //頂點類型由用戶定義
typedef int EdgeType //權值類型
#define MAXVEX 100
#define INFINITY 65535
typedef struct
{
VertexType vexs[MAXVEX];
EdgeType arc[MAXVEX][MAXVEX];
int numVertexes,numEdges;
}
有了這個結構定義,我們構造一個圖其實就是給頂點表和邊表輸入數據的過程。我們來看看無向網圖的創建代碼。如下:
void CreateMGraph(MGraph *G)
{
int i,j,k,w;
printf(“輸入頂點和邊數:\n”);
scanf(“%d,%d”,&G->numVertexes,&G->numEdges);
for(i=0;i<G->numVertexes;i++)
for(j=0;j<G->numVertexes;j++)
G->arc[i][j] =INFINITY ; //鄰接矩陣初始化
for(k=0;k<G->numEdges;k++)
{
printf(“輸入邊(vi,vi)的上標i,下標j和權w”);
scanf(“%d,%d,%d”,&i,&j,&w);
G->arc[i][j] = w;
G->arc[j][i] = w; //無向圖是對稱矩陣。
}
}
從代碼中也可以看到,n個頂點和e條邊的無向網圖的創建,時間複雜度O(n+n2+e)。其中對鄰接矩陣G.arc的初始化耗費了O(n2)的時間。
鄰接表
鄰接矩陣是不錯的一種圖存儲結構,但是我們也發現,對於邊數相對頂點較少的圖,這種結構是存在對存儲空間的極大浪費的。
如圖
我們把數組與鏈表相結合的存儲方法稱爲鄰接表(Adjacency List)
處理方法如下:
1. 圖中頂點用一個一維數組存儲,當然,頂點也可以用單鏈表來存儲,不過數組可以較容易地讀取頂點信息,更加方便。另外,對於頂點數組中,每個數據元素還需要存儲指向一個鄰接點的指針,以便查找該頂點的邊信息
2. 圖中每個頂點vi的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以用單鏈表存儲,無向圖稱爲頂點vi邊表,有向圖則稱爲頂點vi作爲弧尾的出邊表。
從圖中我們知道,頂點表的各個結點由data和firstedge兩個域表示,data是數據域,存儲頂點的信息,firstedge是指針域,指向邊表的第一個結點,即此頂點的第一個鄰接點。
邊表結點由adjvex和next兩個域組成。adjvex是鄰接點域,存儲某頂點的鄰接點在頂點表中的下標,next則存儲指向邊表中下一個結點的指針。
比如:v1頂點與v0、v2互爲鄰接點,則在v1的邊表中,adjvex分別爲v0的0和v2的2。
這樣的結構,對於我們要獲得圖的相關信息也是很方便的。比如我們要想知道某個頂點的度,就去查找這個頂點的邊表中結點的個數。若要判斷頂點vi到vj是否存在邊,只需要測試頂點vi的邊表中adjvex是否存在結點vj的下標j就行了若求頂點的所有鄰接點,其實就是對此頂點的邊表進行遍歷,得到的adjvex域對應的頂點就是鄰接點。
若圖是有向圖,鄰接表的結構是類似的,
注意的是有向圖由於有方向,我們是以頂點爲弧尾來存儲邊表的,這樣很容易就可以得到每個頂點的出席。但也有時爲了便於確定頂點的入度或以頂點爲弧頭的弧,我們可以建立一個有向圖的逆鄰接表,即對我個頂點vi都建立一個鏈接爲vi爲弧頭的表。
對於帶權值的網圖,可以在邊表結點定義中再增加一個weight的數據域,存儲權值信息即可。
結構定義:
typedef char VertexType
typedef int EdgeType; //邊上的權值類型應由用戶定義
typedef struct EdgeNode //邊表結點
{
int adjvex; //鄰接點域,存儲該頂點對應的下標
EdgeType weight;
struct EdgeNode *next; //指向下一個連接點
}EdgeNode;
typedef struct VertexNode //頂點表結點
{
VertexType data;
EdgeNode *firstedge; //邊表頭指針
}VertexNode,AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges; //
}GraphAdjList;
建立圖的鄰接表結構,無向圖
void CreateALGraph(GraphAdjList *G)
{
int i,j,k;
EdgeNode *e;
printf(“輸入頂點數和邊數:\n”);
scanf(“%d,%d”,&G->numVertexes,&G->numEdges);//輸入頂點和邊數
for(i=0;i<G->numVertexes;i++)
{
scanf(&G->adjList[i].data);
G->adjList[i].firstedge=NULL;
}
//建立邊表
for(k=0;k<G->numEdges;k++)
{
prinf(“輸入邊(vi,vj)上的頂點序號\n”);
scanf(“%d,%d”,&i,&j); //輸入邊(vi,vj)上的頂點序號
e=(EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = j;
e->next=G->adjList[i].firstedge; //
G->adjList[i].firstedge=e;
e=(EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = i;
e->next=G->adjList[j].firstedge;
G->adjList[j].firstedge = e;
}
}