鄰接表

在圖論和計算機科學中，鄰接表（英語：adjacency list）是表示了圖中與每一個頂點相鄰的邊集的集合，這裡的集合指的是無序集。

如果是無向圖，那麼每條邊由兩個結點組成，分別代表邊的兩個端點；如果是有向圖，那麼每條邊是一個結點對，分別代表邊的始點和終點。

計算機科學中的應用

在計算機科學中，鄰接表描述一種緊密相關的資料結構，用於表徵圖。在鄰接表的表示中，對於圖中的每個頂點，將儲存所有其它與之相連的頂點（即 “鄰接表”）。例如，由吉多・範羅蘇姆提出的，使用雜湊表將每個頂點和該頂點的鄰接點陣列關連起來，就可以看作是上述表示方法的一種實現。又如，在 Cormenetal 中，頂點陣列的每個元素都指向一個鄰接點單鏈表。

鄰接表結構的困難之一是無法明確在什麼地方儲存相關邊的長度或花銷。為了解決這個問題，一些演算法，如

Goodrich and Tamassia 所提出的面向物件鄰接表，有時也稱「關聯度」，它為每個頂點儲存一個物件表，每個物件表示指向頂點的那條邊的關聯度。為了完善這個結構，每條邊必須指向兩個組成其端點的頂點。這個額外的邊物件使得它比直接列出所有邊的鄰接表消耗更多的記憶體，但它不失為一種儲存邊相關資訊的好方法。

1.1 C 實現

/ 圖的鄰接表儲存表示 /

define MAXVERTEXNUM 20

typedef enum {DG,DN,UDG,UDN} GraphKind; / {有向圖，有向網，無向圖，無向網} /

typedef struct ArcNode

{

int adjvex; / 該弧所指向的頂點的位置 /

struct ArcNode nextarc; / 指向下一條弧的指標 */

InfoType info; / 網的權值指標） */

} ArcNode; / 表結點 /

typedef struct

{

VertexType data; / 頂點資訊 /

ArcNode firstarc; / 第一個表結點的地址，指向第一條依附該頂點的弧的指標 */

} VNode,AdjListMAXVERTEXNUM; / 頭結點 /

typedef struct

{

AdjList vertices;

int vexnum,arcnum; / 圖的當前頂點數和弧數 /

GraphKind kind; / 圖的種類標誌 /

} ALGraph;

/ 圖的鄰接表儲存的基本操作 (15 個)/

include"bo2-8.c" / 不帶頭結點的單鏈表基本操作 /
include"func2-1.c" / 不帶頭結點的單鏈表擴充套件操作 /

int LocateVex (ALGraph G,VertexType u)

{ / 初始條件：圖 G 存在，u 和 G 中頂點有相同特徵 /

/ 操作結果：若 G 中存在頂點 u，則返回該頂點在圖中位置；否則返回 - 1 /

int i;

for (i=0;i=2) / 無向圖或網，產生第 2 個表結點，並插在第 j 個元素 (入弧) 的表頭 /

{

e.adjvex=i; / e.info 不變，不必再賦值 /

ListInsert (&(G).verticesj.firstarc,1,e); / 插在第 j 個元素的表頭，在 bo2-8.c 中 */

}

}

}

void CreateGraphF (ALGraph *G)

{/ 採用鄰接表 儲存結構，由檔案構造沒有相關資訊圖或網 G (用一個函式構造 4 種圖) /

int i,j,k,w; / w 是權值 /

VertexType va,vb; / 連線邊或弧的 2 頂點 /

ElemType e;

char filename13;

FILE *graphlist;

printf ("請輸入資料檔名 (f7-1.txt 或 f7-2.txt)：");

scanf ("% s",filename);

printf ("請輸入圖的型別 (有向圖：0, 有向網：1, 無向圖：2, 無向網：3):");

scanf ("% d",&(*G).kind);

graphlist=fopen (filename,"r"); / 以讀的方式開啟資料檔案，並以 graphlist 表示 /

fscanf (graphlist,"% d",&(*G).vexnum);

fscanf (graphlist,"% d",&(*G).arcnum);

for (i=0;i=2) / 無向圖或網，產生第 2 個表結點，並插在第 j 個元素 (入弧) 的表頭 /

{

e.adjvex=i; / e.info 不變，不必再賦值 /

ListInsert (&(G).verticesj.firstarc,1,e); / 插在第 j 個元素的表頭，在 bo2-8.c 中 */

}

}

fclose (graphlist); / 關閉資料檔案 /

}

void DestroyGraph (ALGraph *G)

{ / 初始條件：圖 G 存在。操作結果：銷燬圖 G /

int i;

ElemType e;

for (i=0;ii) / 頂點序號 > i (保證動態生成的權值空間只釋放 1 次) /

free (e.info);

}

else / 圖 /

DestroyList (&(G).verticesi.firstarc); / 銷燬弧或邊連結串列，在 bo2-8.c 中 */

(G).vexnum=0; / 頂點數為 0 */

(G).arcnum=0; / 邊或弧數為 0 */

}

VertexType* GetVex (ALGraph G,int v)

{ / 初始條件：圖 G 存在，v 是 G 中某個頂點的序號。操作結果：返回 v 的值 /

if (v>=G.vexnum

v-1) / v 是 G 的頂點 /

{strcpy ((*G).verticesi.data,value);

return OK;

}

return ERROR;

}

int FirstAdjVex (ALGraph G,VertexType v)

{ / 初始條件：圖 G 存在，v 是 G 中某個頂點 /

/ 操作結果：返回 v 的第一個鄰接頂點的序號。若頂點在 G 中沒有鄰接頂點，則返回 - 1 /

LinkList p;

int v1;

v1=LocateVex (G,v); / v1 為頂點 v 在圖 G 中的序號 /

p=G.verticesv1.firstarc;

if (p)

return p->data.adjvex;

else

return -1;

}

Status equalvex (ElemType a,ElemType b)

{/ DeleteArc 、DeleteVex 和 NextAdjVex 要呼叫的函式 /

if (a.adjvex==b.adjvex)

return OK;

else

return ERROR;

}

int NextAdjVex (ALGraph G,VertexType v,VertexType w)

{ / 初始條件：圖 G 存在，v 是 G 中某個頂點，w 是 v 的鄰接頂點 /

/ 操作結果：返回 v 的 (相對於 w 的) 下一個鄰接頂點的序號。若 w 是 v 的最後一個鄰接點，則返回 - 1 /

LinkList p,p1; / p1 在 Point 中用作輔助指標，Point 在 func2-1.c 中 /

ElemType e;

int v1;

v1=LocateVex (G,v); / v1 為頂點 v 在圖 G 中的序號 /

e.adjvex=LocateVex (G,w); / e.adjvex 為頂點 w 在圖 G 中的序號 /

p=Point (G.verticesv1.firstarc,e,equalvex,&p1); / p 指向頂點 v 的連結串列中鄰接頂點為 w 的結點 /

if (!p

p->next) / 沒找到 w 或 w 是最後一個鄰接點 /

return -1;

else / p->data.adjvex==w /

return p->next->data.adjvex; / 返回 v 的 (相對於 w 的) 下一個鄰接頂點的序號 /

}

void InsertVex (ALGraph *G,VertexType v)

{ / 初始條件：圖 G 存在，v 和圖中頂點有相同特徵 /

/ 操作結果：在圖 G 中增添新頂點 v (不增添與頂點相關的弧，留待 InsertArc 去做) /

strcpy ((G).vertices (G).vexnum.data,v); / 構造新頂點向量 /

(G).vertices (G).vexnum.firstarc=NULL;

(G).vexnum++; / 圖 G 的頂點數加 1 */

}

Status DeleteVex (ALGraph *G,VertexType v)

{ / 初始條件：圖 G 存在，v 是 G 中某個頂點。操作結果：刪除 G 中頂點 v 及其相關的弧 /

int i,j,k;

ElemType e;

LinkList p,p1;

j=LocateVex (G,v); / j 是頂點 v 的序號 */

if (jnext=p->next; / 從連結串列中刪除 p 所指結點 /

else / p 指向首元結點 /

(G).verticesi.firstarc=p->next; / 頭指標指向下一結點 */

if ((G).kinddata.info); / 釋放動態生成的權值空間 */

}

free (p); / 釋放 v 為入度的結點 /

}

for (k=j+1;kj 的結點，其序號 - 1 */

{

e.adjvex=k;

p=Point ((G).verticesi.firstarc,e,equalvex,&p1); / Point 在 func2-1.c 中 */

if (p)

p->data.adjvex--; / 序號 - 1 (因為前移) /

}

}

return OK;

}

Status InsertArc (ALGraph *G,VertexType v,VertexType w)

{ / 初始條件：圖 G 存在，v 和 w 是 G 中兩個頂點 /

/ 操作結果：在 G 中增添弧，若 G 是無向的，則還增添對稱弧 /

ElemType e;

int i,j;

i=LocateVex (G,v); / 弧尾或邊的序號 */

j=LocateVex (G,w); / 弧頭或邊的序號 */

if (i=2) / 無向，生成另一個表結點 /

{

e.adjvex=i; / e.info 不變 /

ListInsert (&(G).verticesj.firstarc,1,e); / 將 e 插在弧頭的表頭 */

}

return OK;

}

Status DeleteArc (ALGraph *G,VertexType v,VertexType w)

{ / 初始條件：圖 G 存在，v 和 w 是 G 中兩個頂點 /

/ 操作結果：在 G 中刪除弧，若 G 是無向的，則還刪除對稱弧 /

int i,j;

Status k;

ElemType e;

i=LocateVex (G,v); / i 是頂點 v (弧尾) 的序號 */

j=LocateVex (G,w); / j 是頂點 w (弧頭) 的序號 */

if (i=2) / 無向，刪除對稱弧 /

{

e.adjvex=i;

DeleteElem (&(*G).verticesj.firstarc,&e,equalvex);

}

return OK;

}

else / 沒找到待刪除的弧 /

return ERROR;

}

Boolean visitedMAXVERTEXNUM; / 訪問標誌陣列 (全域性量) /

void (VisitFunc)(char v); / 函式變數 (全域性量) /

void DFS (ALGraph G,int v)

{ / 從第 v 個頂點出發遞迴地深度優先遍歷圖 G。演算法 7.5 /

int w;

visitedv=TRUE; / 設定訪問標誌為 TRUE (已訪問) /

VisitFunc (G.verticesv.data); / 訪問第 v 個頂點 /

for (w=FirstAdjVex (G,G.verticesv.data);w>=0;w=NextAdjVex (G,G.verticesv.data,G.verticesw.data))

if (!visitedw)

DFS (G,w); / 對 v 的尚未訪問的鄰接點 w 遞迴呼叫 DFS /

}

void DFSTraverse (ALGraph G,void (Visit)(char))

{ / 對圖 G 作深度優先遍歷。演算法 7.4 /

int v;

VisitFunc=Visit; / 使用全域性變數 VisitFunc，使 DFS 不必設函式指標引數 /

for (v=0;v=0;w=NextAdjVex (G,G.verticesu.data,G.verticesw.data))

if (!visitedw) / w 為 u 的尚未訪問的鄰接頂點 /

{

visitedw=TRUE;

Visit (G.verticesw.data);

EnQueue (&Q,w); / w 入隊 /

}

}

}

printf ("\n");

}

void DFS1 (ALGraph G,int v,void (Visit)(char))

{ / 從第 v 個頂點出發遞迴地深度優先遍歷圖 G。僅適用於鄰接表儲存結構 /

ArcNode p; / p 指向表結點 */

visitedv=TRUE; / 設定訪問標誌為 TRUE (已訪問) /

Visit (G.verticesv.data); / 訪問該頂點 /

for (p=G.verticesv.firstarc;p;p=p->next) / p 依次指向 v 的鄰接頂點 /

if (!visitedp->data.adjvex)

DFS1 (G,p->data.adjvex,Visit); / 對 v 的尚未訪問的鄰接點遞迴呼叫 DFS1 /

}

void DFSTraverse1 (ALGraph G,void (Visit)(char))

{ / 對圖 G 作深度優先遍歷。DFS1 設函式指標引數 /

int v;

for (v=0;vnext) / p 依次指向 u 的鄰接頂點 /

if (!visitedp->data.adjvex) / u 的鄰接頂點尚未被訪問 /

{visitedp->data.adjvex=TRUE; / 該鄰接頂點設為已被訪問 /

Visit (G.verticesp->data.adjvex.data); / 訪問該鄰接頂點 /

EnQueue (&Q,p->data.adjvex); / 入隊該鄰接頂點序號 /

}

}

}

printf ("\n");

}

void Display (ALGraph G)

{ / 輸出圖的鄰接矩陣 G /

int i;

LinkList p;

switch (G.kind)

{case DG: printf ("有向圖 \n");

	 break;

case DN: printf ("有向網 \n");

break;

case UDG:printf ("無向圖 \n");

break;

case UDN:printf ("無向網 \n");

}

printf ("% d 個頂點：\n",G.vexnum);

for (i=0;idata.adjvex) / 有向或無向兩次中的一次 /

{printf ("% s→% s",G.verticesi.data,G.verticesp->data.adjvex.data);

	 if (G.kind%2) / 網 /

printf (":% d",*(p->data.info));

}

p=p->nextarc;

}

printf ("\n");

}

}

權衡

可用於替代鄰接表的主要有鄰接矩陣。用稀疏鄰接矩陣表示鄰接表時，將佔用更少的空間。這是因為它能避免為不存在的邊分配任何空間。在一臺 32 位計算機上，如果使用原始的陣列結構實現鄰接表，那麼對於一個無向圖來說，它大約需要佔用位元組的儲存空間，其中表示邊的個數。每條邊都將會在兩個鄰接表中重複出現，並分別佔用 4 位元組空間。

相反地，由於鄰接矩陣中的每個元素僅佔用一位（bit），故可以以非常緊密的方式來儲存，僅佔用個位元組，其中代表頂點個數。除了節省空間外，這種緊密儲存也發揚了 locality of reference。

注意到一個圖至多能有條邊（允許迴圈）。令表示圖的緻密度，則由，可知鄰接表將佔用更多的空間，準確地說，僅當 ' 以說只有當圖比較稀疏的時候，才有可能以較少的空間來儲存鄰接表。不過，以上分析只有在僅考慮邊的連線性，而不考慮關於邊的任何數值資訊時才有效。

除了空間方面的考慮外，不同的資料結構也使得不同的操作變得更容易。在一個鄰接表中，給定一個頂點，可以很容易地找出它的所有鄰邊，因為只需要讀取它的鄰接表就可以了。在一個鄰接矩陣中，相同的操作則需要掃描一行，花費大約（大 O 標記）時間。而如果你想知道給定的兩個頂點間是否存在有邊，在鄰接矩陣裡可以立刻查到，在鄰接表中則需要花費以邊的最小關聯度成比例的時間。