// c7-4.h 無向圖的鄰接多重表存儲結構(見圖7.42)
#define MAX_VERTEX_NUM 20
enum VisitIf{unvisited,visited};
struct EBox
{
VisitIf mark; // 訪問標記
int ivex,jvex; // 該邊依附的兩個頂點的位置
EBox *ilink,*jlink; // 分別指向依附這兩個頂點的下一條邊
InfoType *info; // 該邊信息指針,可指向權值或其它信息
};
struct VexBox
{
VertexType data;
EBox *firstedge; // 指向第一條依附該頂點的邊
};
struct AMLGraph
{
VexBox adjmulist[MAX_VERTEX_NUM];
int vexnum,edgenum; // 無向圖的當前頂點數和邊數
};
圖743 是根據c7-4.h 定義的無向圖的存儲結構。與bo7-2.cpp、bo7-3.cpp 一樣,
bo7-4.cpp 中基本操作函數CreateGraph()也是在表頭插入邊結點的。所以,對於給定的
圖,它的邊結點的鏈表結構也不惟一,與邊的輸入順序有關。採用鄰接多重表存儲結構,
每條邊只生成一個結點。而用鄰接表存儲結構表示無向圖,圖的每條邊生成兩個結點。
在無向圖中邊的兩個頂點是沒有順序的,頂點是根據與其頂點號對應的指針域形成鄰
接頂點鏈表的。如圖743(a)所示,上排權值分別是5、4、3 的3 個結點形成與v1 相連
的3 條邊。它們的jvex=0,從頭指針adjmulist[0].firstedge 指向上排左邊結點(這個結點表
示連接頂點v1 和v3 的邊,因爲它的jvex 和ivex 分別爲0 和2)開始,通過jlink 指針鏈接
在一起。與某一頂點相連的邊不一定都由ilink 或jlink 指向,它取決於結點的ivex 和jvex
中的哪一個與該頂點的序號相同。以與頂點v3 相連的邊爲例, 從頭指針
adjmulist[2].firstedge 指向上排左邊結點,因爲該結點的ivex=2,所以,該結點的ilink 指
向與頂點v3 相連的下一條邊(下排左邊結點)。而這個結點的jvex=2,則該結點的jlink 指
向與頂點v3 相連的下一條邊(下排右邊結點)。這個結點的jvex=2,jlink=NULL,表明鏈
表結束,不再有與頂點v3 相連的邊。這樣,與頂點v3 鄰接的頂點有3 個,依次是v1、v4
和v2。與圖示相符。
雖然鄰接多重表存儲結構也是不帶頭結點的單鏈表結構,但由於指向下一結點的指針
域是變化的,可能是ilink 指向下一個結點,也可能是jlink 指向下一個結點,這取決於
ivex 和jvex 的值。所以不帶頭結點的單鏈表基本操作(在bo2-8.cpp 中)應用於鄰接多重表
存儲結構的基本操作中很不方便,這使得鄰接多重表存儲結構的基本操作(在bo7-4.cpp
中)比較冗長。
// bo7-4.cpp 無向圖的鄰接多重表(存儲結構由c7-4.h定義)的基本函數類型(16個),包括算法7.4,7.6
int LocateVex(AMLGraph G,VertexType u)
{ // 初始條件:無向圖G存在,u和G中頂點有相同特徵
// 操作結果:若G中存在頂點u,則返回該頂點在無向圖中位置;否則返回-1
int i;
for(i=0;i<G.vexnum;++i)
if(strcmp(u,G.adjmulist[i].data)==0)
return i;
return -1;
}
void CreateGraph(AMLGraph &G)
{ // 採用鄰接多重表存儲結構,構造無向圖G
int i,j,k,IncInfo;
VertexType va,vb;
EBox *p;
printf("請輸入無向圖的頂點數,邊數,是否爲帶權圖(是:1,否:0): ");
scanf("%d,%d,%d",&G.vexnum,&G.edgenum,&IncInfo);
printf("請輸入%d個頂點的值(<%d個字符):\n",G.vexnum,MAX_NAME);
for(i=0;i<G.vexnum;++i) // 構造頂點向量
{
scanf("%s",G.adjmulist[i].data);
G.adjmulist[i].firstedge=NULL;
}
printf("請順序輸入每條邊的兩個端點(以空格作爲間隔):\n");
for(k=0;k<G.edgenum;++k) // 構造表結點鏈表
{
scanf("%s%s%*c",va,vb); // %*c吃掉回車符
i=LocateVex(G,va); // 一端
j=LocateVex(G,vb); // 另一端
p=(EBox*)malloc(sizeof(EBox));
p->mark=unvisited; // 設初值
p->ivex=i;
p->ilink=G.adjmulist[i].firstedge; // 插在一端的表頭
G.adjmulist[i].firstedge=p;
p->jvex=j;
p->jlink=G.adjmulist[j].firstedge; // 插在另一端的表頭
G.adjmulist[j].firstedge=p;
if(IncInfo) // 網
{
p->info=(InfoType*)malloc(sizeof(InfoType));
printf("請輸入該邊的權值: ");
scanf("%d",p->info);
}
else
p->info=NULL;
}
}
VertexType& GetVex(AMLGraph G,int v)
{ // 初始條件:無向圖G存在,v是G中某個頂點的序號。操作結果:返回v的值
if(v>=G.vexnum||v<0)
exit(ERROR);
return G.adjmulist[v].data;
}
Status PutVex(AMLGraph &G,VertexType v,VertexType value)
{ // 初始條件:無向圖G存在,v是G中某個頂點。操作結果:對v賦新值value
int i;
i=LocateVex(G,v);
if(i<0) // v不是G的頂點
return ERROR;
strcpy(G.adjmulist[i].data,value);
return OK;
}
int FirstAdjVex(AMLGraph G,VertexType v)
{ // 初始條件:無向圖G存在,v是G中某個頂點
// 操作結果:返回v的第一個鄰接頂點的序號。若頂點在G中沒有鄰接頂點,則返回-1
int i;
i=LocateVex(G,v);
if(i<0) // G中不存在頂點v
return -1;
if(G.adjmulist[i].firstedge) // v有鄰接頂點
if(G.adjmulist[i].firstedge->ivex==i)
return G.adjmulist[i].firstedge->jvex;
else
return G.adjmulist[i].firstedge->ivex;
else
return -1;
}
int NextAdjVex(AMLGraph G,VertexType v,VertexType w)
{ // 初始條件:無向圖G存在,v是G中某個頂點,w是v的鄰接頂點
// 操作結果:返回v的(相對於w的)下一個鄰接頂點的序號。若w是v的最後一個鄰接點,則返回-1
int i,j;
EBox *p;
i=LocateVex(G,v); // i是頂點v的序號
j=LocateVex(G,w); // j是頂點w的序號
if(i<0||j<0) // v或w不是G的頂點
return -1;
p=G.adjmulist[i].firstedge; // p指向頂點v的第1條邊
while(p)
if(p->ivex==i&&p->jvex!=j) // 不是鄰接頂點w(情況1)
p=p->ilink; // 找下一個鄰接頂點
else if(p->jvex==i&&p->ivex!=j) // 不是鄰接頂點w(情況2)
p=p->jlink; // 找下一個鄰接頂點
else // 是鄰接頂點w
break;
if(p&&p->ivex==i&&p->jvex==j) // 找到鄰接頂點w(情況1)
{
p=p->ilink;
if(p&&p->ivex==i)
return p->jvex;
else if(p&&p->jvex==i)
return p->ivex;
}
if(p&&p->ivex==j&&p->jvex==i) // 找到鄰接頂點w(情況2)
{
p=p->jlink;
if(p&&p->ivex==i)
return p->jvex;
else if(p&&p->jvex==i)
return p->ivex;
}
return -1;
}
Status InsertVex(AMLGraph &G,VertexType v)
{ // 初始條件:無向圖G存在,v和G中頂點有相同特徵
// 操作結果:在G中增添新頂點v(不增添與頂點相關的弧,留待InsertArc()去做)
if(G.vexnum==MAX_VERTEX_NUM) // 結點已滿,不能插入
return ERROR;
if(LocateVex(G,v)>=0) // 結點已存在,不能插入
return ERROR;
strcpy(G.adjmulist[G.vexnum].data,v);
G.adjmulist[G.vexnum++].firstedge=NULL;
return OK;
}
Status DeleteArc(AMLGraph &G,VertexType v,VertexType w)
{ // 初始條件:無向圖G存在,v和w是G中兩個頂點。操作結果:在G中刪除弧<v,w>
int i,j;
EBox *p,*q;
i=LocateVex(G,v);
j=LocateVex(G,w);
if(i<0||j<0||i==j)
return ERROR; // 圖中沒有該點或弧。以下使指向待刪除邊的第1個指針繞過這條邊
p=G.adjmulist[i].firstedge; // p指向頂點v的第1條邊
if(p&&p->jvex==j) // 第1條邊即爲待刪除邊(情況1)
G.adjmulist[i].firstedge=p->ilink;
else if(p&&p->ivex==j) // 第1條邊即爲待刪除邊(情況2)
G.adjmulist[i].firstedge=p->jlink;
else // 第1條邊不是待刪除邊
{
while(p) // 向後查找弧<v,w>
if(p->ivex==i&&p->jvex!=j) // 不是待刪除邊
{
q=p;
p=p->ilink; // 找下一個鄰接頂點
}
else if(p->jvex==i&&p->ivex!=j) // 不是待刪除邊
{
q=p;
p=p->jlink; // 找下一個鄰接頂點
}
else // 是鄰接頂點w
break;
if(!p) // 沒找到該邊
return ERROR;
if(p->ivex==i&&p->jvex==j) // 找到弧<v,w>(情況1)
if(q->ivex==i)
q->ilink=p->ilink;
else
q->jlink=p->ilink;
else if(p->ivex==j&&p->jvex==i) // 找到弧<v,w>(情況2)
if(q->ivex==i)
q->ilink=p->jlink;
else
q->jlink=p->jlink;
} // 以下由另一頂點起找待刪除邊且刪除之
p=G.adjmulist[j].firstedge; // p指向頂點w的第1條邊
if(p->jvex==i) // 第1條邊即爲待刪除邊(情況1)
G.adjmulist[j].firstedge=p->ilink;
else if(p->ivex==i) // 第1條邊即爲待刪除邊(情況2)
G.adjmulist[j].firstedge=p->jlink;
else // 第1條邊不是待刪除邊
{
while(p) // 向後查找弧<v,w>
if(p->ivex==j&&p->jvex!=i) // 不是待刪除邊
{
q=p;
p=p->ilink; // 找下一個鄰接頂點
}
else if(p->jvex==j&&p->ivex!=i) // 不是待刪除邊
{
q=p;
p=p->jlink; // 找下一個鄰接頂點
}
else // 是鄰接頂點v
break;
if(p->ivex==i&&p->jvex==j) // 找到弧<v,w>(情況1)
if(q->ivex==j)
q->ilink=p->jlink;
else
q->jlink=p->jlink;
else if(p->ivex==j&&p->jvex==i) // 找到弧<v,w>(情況2)
if(q->ivex==j)
q->ilink=p->ilink;
else
q->jlink=p->ilink;
}
if(p->info) // 有相關信息(或權值)
free(p->info); // 釋放相關信息(或權值)
free(p); // 釋放結點
G.edgenum--; // 邊數-1
return OK;
}
Status DeleteVex(AMLGraph &G,VertexType v)
{ // 初始條件:無向圖G存在,v是G中某個頂點。操作結果:刪除G中頂點v及其相關的邊
int i,j;
EBox *p;
i=LocateVex(G,v); // i爲待刪除頂點的序號
if(i<0)
return ERROR;
for(j=0;j<G.vexnum;j++) // 刪除與頂點v相連的邊(如果有的話)
DeleteArc(G,v,G.adjmulist[j].data); // 如果存在此弧,則刪除
for(j=i+1;j<G.vexnum;j++) // 排在頂點v後面的頂點的序號減1
G.adjmulist[j-1]=G.adjmulist[j];
G.vexnum--; // 頂點數減1
for(j=i;j<G.vexnum;j++) // 修改序號大於i的頂點在表結點中的序號
{
p=G.adjmulist[j].firstedge;
if(p)
if(p->ivex==j+1)
{
p->ivex--;
p=p->ilink;
}
else
{
p->jvex--;
p=p->jlink;
}
}
return OK;
}
void DestroyGraph(AMLGraph &G)
{ // 初始條件:有向圖G存在。操作結果:銷燬有向圖G
int i;
for(i=G.vexnum-1;i>=0;i--) // 由大到小依次刪除頂點
DeleteVex(G,G.adjmulist[i].data);
}
Status InsertArc(AMLGraph &G,VertexType v,VertexType w)
{ // 初始條件:無向圖G存在,v和W是G中兩個頂點。操作結果:在G中增添弧<v,w>
int i,j,IncInfo;
EBox *p;
i=LocateVex(G,v); // 一端
j=LocateVex(G,w); // 另一端
if(i<0||j<0||i==j)
return ERROR;
p=(EBox*)malloc(sizeof(EBox));
p->mark=unvisited;
p->ivex=i;
p->ilink=G.adjmulist[i].firstedge; // 插在表頭
G.adjmulist[i].firstedge=p;
p->jvex=j;
p->jlink=G.adjmulist[j].firstedge; // 插在表頭
G.adjmulist[j].firstedge=p;
printf("該邊是否有權值(1:有0:無): ");
scanf("%d",&IncInfo);
if(IncInfo) // 有權值
{
p->info=(InfoType*)malloc(sizeof(InfoType));
printf("請輸入該邊的權值: ");
scanf("%d",p->info);
}
else
p->info=NULL;
G.edgenum++;
return OK;
}
Boolean visite[MAX_VERTEX_NUM]; // 訪問標誌數組(全局量)
void(*VisitFunc)(VertexType v);
void DFS(AMLGraph G,int v)
{
int j;
EBox *p;
VisitFunc(G.adjmulist[v].data);
visite[v]=TRUE;
p=G.adjmulist[v].firstedge;
while(p)
{
j=p->ivex==v?p->jvex:p->ivex;
if(!visite[j])
DFS(G,j);
p=p->ivex==v?p->ilink:p->jlink;
}
}
void DFSTraverse(AMLGraph G,void(*visit)(VertexType))
{ // 初始條件:圖G存在,Visit是頂點的應用函數。算法7.4
// 操作結果:從第1個頂點起,深度優先遍歷圖G,並對每個頂點調用函數Visit一次且僅一次
int v;
VisitFunc=visit;
for(v=0;v<G.vexnum;v++)
visite[v]=FALSE;
for(v=0;v<G.vexnum;v++)
if(!visite[v])
DFS(G,v);
printf("\n");
}
typedef int QElemType; // 隊列元素類型
#include"c3-2.h" // 鏈隊列的存儲結構,BFSTraverse()用
#include"bo3-2.cpp" // 鏈隊列的基本操作,BFSTraverse()用
void BFSTraverse(AMLGraph G,void(*Visit)(VertexType))
{ // 初始條件:圖G存在,Visit是頂點的應用函數。算法7.6
// 操作結果:從第1個頂點起,按廣度優先非遞歸遍歷圖G,並對每個頂點調用函數
// Visit一次且僅一次。使用輔助隊列Q和訪問標誌數組visite
int v,u,w;
LinkQueue Q;
for(v=0;v<G.vexnum;v++)
visite[v]=FALSE; // 置初值
InitQueue(Q); // 置空的輔助隊列Q
for(v=0;v<G.vexnum;v++)
if(!visite[v]) // v尚未訪問
{
visite[v]=TRUE; // 設置訪問標誌爲TRUE(已訪問)
Visit(G.adjmulist[v].data);
EnQueue(Q,v); // v入隊列
while(!QueueEmpty(Q)) // 隊列不空
{
DeQueue(Q,u); // 隊頭元素出隊並置爲u
for(w=FirstAdjVex(G,G.adjmulist[u].data);w>=0;w=NextAdjVex(G,G.adjmulist[u].data,
G.adjmulist[w].data))
if(!visite[w]) // w爲u的尚未訪問的鄰接頂點的序號
{
visite[w]=TRUE;
Visit(G.adjmulist[w].data);
EnQueue(Q,w);
}
}
}
printf("\n");
}
void MarkUnvizited(AMLGraph G)
{ // 置邊的訪問標記爲未被訪問
int i;
EBox *p;
for(i=0;i<G.vexnum;i++)
{
p=G.adjmulist[i].firstedge;
while(p)
{
p->mark=unvisited;
if(p->ivex==i)
p=p->ilink;
else
p=p->jlink;
}
}
}
void Display(AMLGraph G)
{ // 輸出無向圖的鄰接多重表G
int i;
EBox *p;
MarkUnvizited(G); // 置邊的訪問標記爲未被訪問
printf("%d個頂點:\n",G.vexnum);
for(i=0;i<G.vexnum;++i)
printf("%s ",G.adjmulist[i].data);
printf("\n%d條邊:\n",G.edgenum);
for(i=0;i<G.vexnum;i++)
{
p=G.adjmulist[i].firstedge;
while(p)
if(p->ivex==i) // 邊的i端與該頂點有關
{
if(!p->mark) // 只輸出一次
{
printf("%s-%s ",G.adjmulist[i].data,G.adjmulist[p->jvex].data);
p->mark=visited;
if(p->info) // 輸出附帶信息
printf("權值: %d ",*p->info);
}
p=p->ilink;
}
else // 邊的j端與該頂點有關
{
if(!p->mark) // 只輸出一次
{
printf("%s-%s ",G.adjmulist[p->ivex].data,G.adjmulist[i].data);
p->mark=visited;
if(p->info) // 輸出附帶信息
printf("權值: %d ",*p->info);
}
p=p->jlink;
}
printf("\n");
}
}
// main7-4.cpp 檢驗bo7-4.cpp的主程序
#include"c1.h"
#define MAX_NAME 3 // 頂點字符串的最大長度+1
typedef int InfoType; // 權值類型
typedef char VertexType[MAX_NAME]; // 字符串類型
#include"c7-4.h"
#include"bo7-4.cpp"
void visit(VertexType v)
{
printf("%s ",v);
}
void main()
{
int k,n;
AMLGraph g;
VertexType v1,v2;
CreateGraph(g);
Display(g);
printf("修改頂點的值,請輸入原值新值: ");
scanf("%s%s",v1,v2);
PutVex(g,v1,v2);
printf("插入新頂點,請輸入頂點的值: ");
scanf("%s",v1);
InsertVex(g,v1);
printf("插入與新頂點有關的邊,請輸入邊數: ");
scanf("%d",&n);
for(k=0;k<n;k++)
{
printf("請輸入另一頂點的值: ");
scanf("%s",v2);
InsertArc(g,v1,v2);
}
Display(g);
printf("刪除一條邊,請輸入待刪除邊的兩頂點(以空格作爲間隔):");
scanf("%s%s",v1,v2);
DeleteArc(g,v1,v2);
Display(g);
printf("刪除頂點及相關的邊,請輸入頂點的值: ");
scanf("%s",v1);
DeleteVex(g,v1);
Display(g);
printf("深度優先搜索的結果:\n");
DFSTraverse(g,visit);
printf("廣度優先搜索的結果:\n");
BFSTraverse(g,visit);
DestroyGraph(g);
}
代碼的運行結果:
請輸入無向圖的頂點數,邊數,是否爲帶權圖(是:1,否:0): 2,1,1
請輸入2個頂點的值(<3個字符):
a b
請順序輸入每條邊的兩個端點(以空格作爲間隔):
a b
請輸入該邊的權值: 3
2個頂點:(見圖744)
a b
1條邊:
a-b 權值: 3
修改頂點的值,請輸入原值新值: a A
插入新頂點,請輸入頂點的值: c
插入與新頂點有關的邊,請輸入邊數: 2
請輸入另一頂點的值: A
該邊是否有權值(1:有0:無): 1
請輸入該邊的權值: 4
請輸入另一頂點的值: b
該邊是否有權值(1:有0:無): 1
請輸入該邊的權值: 5
3個頂點:(見圖745)
A b c
3條邊:
c-A 權值: 4 A-b 權值: 3
c-b 權值: 5
刪除一條邊,請輸入待刪除邊的兩頂點(以空格爲間隔):A b
3個頂點:(見圖746)
A b c
2條邊:
c-A 權值: 4
c-b 權值: 5
刪除頂點及相關的邊,請輸入頂點的值: c
2個頂點:(見圖747)
A b
0條邊:
深度優先搜索的結果:
A b
廣度優先搜索的結果:
A b