1. 程式人生 > >圖的儲存結構-鄰接矩陣和鄰接表

圖的儲存結構-鄰接矩陣和鄰接表

1、鄰接矩陣

圖的鄰接矩陣(Adjacency Matrix)儲存方式是用兩個陣列來表示圖。

一個一維陣列儲存圖中頂點資訊,一個二維陣列(稱為鄰接矩陣)儲存圖中的邊或弧的資訊。

設圖Gn個頂點,則鄰接矩陣是一個nxn的方陣,定義為:

Arc[i][j]=1,(vi,vj)E<vi,vj>∈E,反之等於0。如下圖是一個簡單無向圖。

我們可以設定兩個陣列,頂點陣列為vertex[4]={v0,v1,v2,v3}陣列arc[4][4]為上圖的矩陣。

對於矩陣的主對角線的值,即arc[0][0],arc[1][1],arc[2][2],arc[3][3]全為0是因為不存在頂點到自身的邊,比如v0

v0

Arc[0][1]=1是因為v0v1的邊存在,而arc[1][3]=0是因為v1v3的邊不存在。

由於是無向圖,v1v3的邊不存在,意味著v3v1的邊也不存在。所以無向圖的邊陣列是一個對稱矩陣。

--要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行(或第i列)的元素之和。

下圖是一個有向圖:

頂點陣列為vertex[4]={v0,v1,v2,v3},弧陣列arc[4][4]為上圖的矩陣。

主對角線上數值依然為0.但因為是有向圖,所以此矩陣並不對稱,比如從v1v0有弧,得到arc[1][0]=1

v0v1沒有弧,因此arc[0][1]=0

有向圖論入度和出度,頂點v1的入度為1

,正好是第v1列上的數之和。

頂點v1的出度為2,即第v1行的各數之和。與無向圖同樣的辦法,判斷頂點vivj是否存在弧,只需要查詢矩陣

arc[i][j]是否為1即可。要想知道vi的所有鄰接點就是將矩陣第i行元素掃描一遍,查詢arc[i][j]1的頂點。

typedef char VertexType;
typedef int EdgeType;
#define Maxvex 100
#define Infinity 65535
typedef struct
{
	VertexType vexs[Maxvex]; //頂點表
	EdgeType arc[Maxvex][Maxvex]; //鄰接矩陣,可看作邊表
	int numVertexes,numEdges;  //圖中當前的頂點數和邊數
}MGraph;
//有了上面的結構定義,構造一個圖,其實就是給頂點表和邊表輸入資料的過程。
//建立無向網圖的鄰接矩陣表示
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++)
	{
		scanf(&G->vexs[i]);
	}
	for(i=0;i<G->numVertexes;i++)
		for(j=0;j<G->numVertexes;i++)
			G->arc[i][j]=Infinity; //鄰接矩陣初始化
	for(k=0;k<G->numEdges;k++)
	{
		printf("輸入邊(vi,vj)的下標i,下標j和權w:\n");
		scanf("%d,%d,%d",&i,&j,&w);
		G->arc[i][j]=w;
		G->arc[j][i]=G->arc[i][j];
	}
}

從程式碼中可以得到:n個頂點和e條邊的無向網圖的建立,時間複雜度是O(n+n^2+e).

2、鄰接表

我們發現,當圖中的邊數相對於頂點較少時,鄰接矩陣是對儲存空間的極大浪費。我們可以考慮對邊或弧使用鏈式儲存的方式來避免空間浪費的問題。回憶樹結構的孩子表示法,將結點存入陣列,並對結點的孩子進行鏈式儲存,不管有多少孩子,也不會存在空間浪費問題。

應用這種思路,我們把這種陣列與連結串列相結合的儲存方法稱為鄰接表(Adjacency List)。

鄰接表的處理辦法是這樣。

1)  圖中頂點用一個一維陣列儲存,當然也可以用單鏈表來儲存,不過用陣列可以較容易的讀取頂點資訊,更加方便。另外,對於頂點陣列中,每個資料元素還需要儲存指向第一個鄰接點的指標,以便於查詢該頂點的邊資訊。

2)  圖中每個頂點vi的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以用單鏈表儲存,無向圖稱為頂點vi邊表,有向圖則稱為以vi為弧尾的出邊表

下圖是一個無向圖的鄰接表結構:

從圖中我們知道,頂點表的各個結點由datafirstedge兩個域表示,data是資料域,儲存頂點的資訊。

firstedge是指標域,指向邊表的第一個結點,即此頂點的第一個鄰接點。

邊表結點由adjvexnext兩個域組成。adjvex是鄰接點域,儲存某頂點的鄰接點在頂點表中的下標,next則儲存

向邊表中下一個結點的指標,比如v1頂點與v0v2互為鄰接點,則在v1的邊表中,adjvex分別為v00v22.

如果想知道某個頂點的,就去查詢這個頂點的邊表中結點的各數。

若要判斷頂點vivj是否存在邊,只需要測試頂點vi的邊表adjvex中是否存在結點vj的下標就行了。

若求頂點的所有鄰接點,其實就是對此頂點的邊表進行遍歷,得到的adjvex域對應的頂點就是鄰接點。

有向圖的鄰接表中頂點vi的邊表是指以vi為弧尾的弧來儲存的,這樣很容易就可以得到每個頂點的出度。

有時為了便於確定頂點的入度或以頂點為弧頭的弧,可以建立一個有向圖的逆鄰接表,即對每個頂點vi都建立

一個連結為vi為弧頭的表。如下圖所示:

此時我們很容易就可以算出某個頂點的入度或出度是多少,判斷兩頂點是否存在弧也很容易實現。

對於帶權值的網圖,可以在邊表結點定義中再增加一個weight的資料域,儲存權值資訊即可,如下圖所示:

//結點的定義
typedef char VertexType;
typedef int EdgeType;
#define MaxVex 100
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++)
	{
		printf("輸入邊(vi,vj)上的頂點序號:\n");
		scanf("%d,%d",&i,&j);
		e = (EdgeNode *)malloc(sizeof(EdgeNode)); //向記憶體申請空間,生成邊表結點
		e->adjvex = j; //鄰接序號為j
		e->next = G->adjList[i].firstedge;
		G->adjList[i].firstedge = e; //將當前頂點的指標指向e

		e = (EdgeNode *)malloc(sizeof(EdgeNode));
		e->adjvex = i; //鄰接序號為i
		e->next = G->adjList[j].firstedge;
		G->adjList[j].firstedge = e; 
	}
}