無向圖的鄰接多重表存儲結構

// 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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章