鄰接表

這是一個無向循環圖，可以用下面幾個表來描述：{a,b}, {a,c}, {b,c}。
在圖論和計算機科學中，鄰接表（英語：adjacency list）是表示了圖中與每一個頂點相鄰的邊集的集合，這裡的集合指的是無序集。
如果是無向圖，那麼每條邊由兩個結點組成，分別代表邊的兩個端點；如果是有向圖，那麼每條邊是一個結點對，分別代表邊的始點和終點。

**計算機科學中的應用**
在計算機科學中，鄰接表描述一種緊密相關的數據結構，用於表徵圖。在鄰接表的表示中，對於圖中的每個頂點，將保存所有其它與之相連的頂點（即“鄰接表”）。例如，由吉多·範羅蘇姆提出的，使用哈希表將每個頂點和該頂點的鄰接點數組關連起來，就可以看作是上述表示方法的一種實現。又如，在Cormenetal中，頂點數組的每個元素都指向一個鄰接點單鏈表。
鄰接表結構的困難之一是無法明確在什麼地方保存相關邊的長度或花銷。為了解決這個問題，一些算法，如
Goodrich and Tamassia所提出的面向對象鄰接表，有時也稱「關聯度」，它為每個頂點保存一個對象表，每個對象表示指向頂點的那條邊的關聯度。為了完善這個結構，每條邊必須指向兩個組成其端點的頂點。這個額外的邊對象使得它比直接列出所有邊的鄰接表消耗更多的內存，但它不失為一種保存邊相關信息的好方法。

**C 實現**
/* 圖的鄰接表存儲表示 */
#define MAX_VERTEX_NUM 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,AdjListMAX_VERTEX_NUM; /* 頭結點 */
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 visitedMAX_VERTEX_NUM; /* 訪問標誌數組(全局量) */
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標記）時間。而如果你想知道給定的兩個頂點間是否存在有邊，在鄰接矩陣裡可以立刻查到，在鄰接表中則需要花費以邊的最小關聯度成比例的時間。

**參考資料==