40. 数据结构笔记之四十图的邻接多重链表表示实现
“一个人的价值 , 应当看他贡献什么 , 而不应当看他取得什么。-- 爱因斯坦”
1. 邻接多重表
邻接多重表(AdjacencyMultilist)主要用于存储无向图。因为,如果用邻接表存储无向图,每条边的两个边结点分别在以该边所依附的两个顶点为头结点的链表中,这给图的某些操作带来不便。例如,对已访问过的边做标记,或者要删除图中某一条边等,都需要找到表示同一条边的两个结点。因此,在进行这一类操作的无向图的问题中采用邻接多重表作存储结构更为适宜。
邻接多重表的存储结构和十字链表类似,也是由顶点表和边表组成,每一条边用一个结点表示,其顶点表结点结构和边表结点结构如图1
所示。
图2
其中,顶点表由两个域组成,vertex域存储和该顶点相关的信息firstedge 域指示第一条依附于该顶点的边;边表结点由六个域组成,mark 为标记域,可用以标记该条边是否被搜索过;ivex 和jvex 为该边依附的两个顶点在图中的位置;ilink 指向下一条依附于顶点ivex的边;jlink 指向下一条依附于顶点jvex 的边,info 为指向和边相关的各种信息的指针域。
如下图3邻接多重表。
在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。可见,对无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同。在邻接多重表上,各种基本操作的实现亦和邻接表相似。
2. 代码实现
2.1 定义结构
/*定义图的邻接多重表结构*/
typedefstructebox
{
int ivex,jvex;//该边依附的两个顶点位置
structebox*ilink,*jlink;//分别指向依附这两个顶点的下一条边
}ebox;
typedefstructvexbox
{
chardata[20];
ebox*firstedge;//指向第一条依附该顶点的边
}vexbox;
typedefstruct
{
vexboxvexes[max_vertex_num];
intvexnum,edgenum;//无向图的当前顶点数和边数
}amlgraph;
2.2 Main函数
调用函数creategraph函数创建图。
输入遍历的起点,得到起点的位置。
调用dfstraverse函数输出深度优先遍历,
然后调用bfstraverse输出广度优先遍历。
关于具体如何实现深度遍历和广度遍历,看下回分析。
实现如下图4
2.3 LocateVex
返回是第几个点。
如果不是顶点中的其中一个点则返回-1.
2.4 creategraph
代码的主要函数
采用邻接多重表存储结构,构造无向图G
先打开cmnet.txt文件进行读取,如果打开失败则退出。
先读取图的顶点个数,边的数量。
然后根据顶点数量,读入每个顶点的数据,并设置该顶点相关的第一个边为NULL。
然后根据边的数量,读入边的两个点。
然后创建一个边表节点,将ivex指向当前边的第一个点,jvex指向当前边的第二个点。
然后将该边两头的点ilink和jlink都插在各自的表头。
最后关闭数据文件。
void creategraph(amlgraph&G)
{
charva[20],vb[20];
inti,j,k;
ebox*p;
FILE*graphlist;
graphlist=fopen("cmnet.txt","r");//打开数据文件,并以graphlist表示
if(graphlist == NULL )
{
printf("errorcan not read cmnet.txt." );
getchar();
exit(0);
}
fscanf(graphlist,"%d",&G.vexnum);//读入顶点个数
fscanf(graphlist,"%d",&G.edgenum);//读入边数
for(k=1;k<=G.vexnum;++k) //初始化顶点
{
fscanf(graphlist,"%s",&G.vexes[k].data);
G.vexes[k].firstedge=NULL;
}
for(k=1;k<=G.edgenum;++k) //从文件读入边构造图
{
fscanf(graphlist,"%s%s",&va,&vb);
p=(ebox*)malloc(sizeof(ebox));
i=LocateVex(G,va);
p->ivex=i;
j=LocateVex(G,vb);
p->jvex=j;
p->ilink=G.vexes[i].firstedge;//插在表头
G.vexes[i].firstedge=p;
p->jlink=G.vexes[j].firstedge;//插在表头
G.vexes[j].firstedge=p;
}
fclose(graphlist);//关闭数据文件
}
2.5 cmnet.txt文件测试内容
5
5
1
2
3
4
5
1
2
1
3
2
3
2
4
3
4
3. 源码
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define max_vertex_num 30
#define OK 1
#define ERROR 0
typedef int status;
/*定义图的邻接多重表结构*/
typedef struct ebox
{
int ivex,jvex;//该边依附的两个顶点位置
struct ebox *ilink,*jlink;//分别指向依附这两个顶点的下一条边
}ebox;
typedef struct vexbox
{
char data[20];
ebox *firstedge;//指向第一条依附该顶点的边
}vexbox;
typedef struct
{
vexbox vexes[max_vertex_num];
int vexnum,edgenum;//无向图的当前顶点数和边数
}amlgraph;
/*若G中存在顶点u,则返回该顶点在图中位置*/
int LocateVex(amlgraph G, char u[20])
{
int i;
for(i=1;i<=G.vexnum;++i)
{
if(strcmp(u,G.vexes[i].data)==0)
return i;
}
return -1;
}
/* 采用邻接多重表存储结构,构造无向图G */
void creategraph(amlgraph &G)
{
char va[20],vb[20];
int i,j,k;
ebox *p;
FILE *graphlist;
graphlist=fopen("cmnet.txt","r"); // 打开数据文件,并以graphlist表示
if( graphlist == NULL )
{
printf( "error can not read cmnet.txt." );
getchar();
exit(0);
}
fscanf(graphlist,"%d",&G.vexnum);//读入顶点个数
fscanf(graphlist,"%d",&G.edgenum);//读入边数
for( k=1;k<=G.vexnum;++k) //初始化顶点
{
fscanf(graphlist,"%s",&G.vexes[k].data);
G.vexes[k].firstedge=NULL;
}
for( k=1;k<=G.edgenum;++k) //从文件读入边构造图
{
fscanf(graphlist,"%s%s",&va,&vb);
p=(ebox*)malloc(sizeof(ebox));
i=LocateVex(G,va);
p->ivex=i;
j=LocateVex(G,vb);
p->jvex=j;
p->ilink=G.vexes[i].firstedge; // 插在表头
G.vexes[i].firstedge=p;
p->jlink=G.vexes[j].firstedge; // 插在表头
G.vexes[j].firstedge=p;
}
fclose(graphlist); // 关闭数据文件
}
/*寻找V的第一个邻接点W*/
int firstadjvex(amlgraph G,int v)
{
if(v<0) //G中不存在顶点v
return -1;
if(G.vexes[v].firstedge) //v有邻接顶点
if(G.vexes[v].firstedge->ivex==v)
return G.vexes[v].firstedge->jvex;
else
return G.vexes[v].firstedge->ivex;
else
return -1;
}
/*返回V的下一个邻接点(相对于W)*/
int nextadjvex(amlgraph G,int v,int w)
{
ebox *p;
if(v<0||w<0) // v或w不是G的顶点
return -1;
p=G.vexes[v].firstedge; // p指向顶点v的第1条边
while(p)
if(p->ivex==v&&p->jvex!=w) // 不是邻接顶点w(情况1)
p=p->ilink; // 找下一个邻接顶点
else if(p->jvex==v&&p->ivex!=w) // 不是邻接顶点w(情况2)
p=p->jlink; // 找下一个邻接顶点
else // 是邻接顶点w
break;
if(p&&p->ivex==v&&p->jvex==w) // 找到邻接顶点w(情况1)
{
p=p->ilink;
if(p&&p->ivex==v)
return p->jvex;
else if(p&&p->jvex==v)
return p->ivex;
}
if(p&&p->ivex==w&&p->jvex==v) // 找到邻接顶点w(情况2)
{
p=p->jlink;
if(p&&p->ivex==v)
return p->jvex;
else if(p&&p->jvex==v)
return p->ivex;
}
return -1;
}
/*标志数组*/
int visitedvexes[max_vertex_num+1];
/*访问顶点*/
void visitvex(amlgraph G,int v)
{
printf("%s ",G.vexes[v].data);
}
/*从第V个顶点出发递归地深度优先遍历图*/
void dfs(amlgraph G,int v)
{
int w,p=1;
visitedvexes[v]=1;
visitvex(G,v); //输出顶点的值
for(w=firstadjvex(G,v);w>0;w=nextadjvex(G,v,w))
if(!visitedvexes[w]) //对没有访问过的定点调用dfs
dfs(G,w);
}
/*深度优先遍历图G*/
void dfstraverse(amlgraph G,int v)
{
int i;
for(i=1;i<=G.vexnum;i++)
visitedvexes[i]=0; //访问标志数组元素初始化为0
dfs(G,v);
}
/*队列的链式存储结构*/
typedef struct qnode
{
int data;
struct qnode *next;
}qnode,*queueptr;
typedef struct
{ queueptr front;
queueptr rear;/*队头、队尾指针*/
}linkqueue;
/*构造空队列*/
status initqueue(linkqueue &q)
{
q.front=q.rear=(queueptr)malloc(sizeof(qnode));
if(!q.front)
exit(0);
q.front->next=NULL;
return OK;
}
/*q为空返回TRUE,否则返回FALSE*/
status queueempty(linkqueue q)
{
if(q.front==q.rear)
return OK;
else
return ERROR;
}
/*插入元素e为q的新队尾元素*/
status enqueue(linkqueue &q,int e)
{
queueptr p=(queueptr)malloc(sizeof(qnode));
if(!p)//存储分配失败
exit(0);
p->data=e;
p->next=NULL;
q.rear->next=p;
q.rear=p;
return OK;
}
/*若队列不空,删除q的队头元素,用e返回其值*/
status dequeue(linkqueue &q)
{ int e;
queueptr p;
if(q.front==q.rear)
return ERROR;
p=q.front->next;
e=p->data;
q.front->next=p->next;
if(q.rear==p)
q.rear=q.front;
free(p);
return e;
}
/*销毁队列Q(无论空否均可)*/
status DestroyQueue(linkqueue &Q)
{
while(Q.front)
{
Q.rear=Q.front->next;
free(Q.front);
Q.front=Q.rear;
}
return OK;
}
/*广度优先遍历*/
void bfstraverse(amlgraph G,int v)
{
int i=1,u,w;
linkqueue q;
for(i=1;i<=G.vexnum;i++)
visitedvexes[i]=0;
initqueue(q);//置空的队列
visitedvexes[v]=1; //把v的标志置1
visitvex(G,v); //访问v
enqueue(q,v);//v入队列q
while(!queueempty(q))
{
u=dequeue(q);//队头元素出队并置为u
for(w=firstadjvex(G,u);w>0;w=nextadjvex(G,u,w))
if(!visitedvexes[w])//w为u的尚未访问的邻接顶点
{
visitedvexes[w]=1;
visitvex(G,w);
enqueue(q,w);
} //if
} //while
}
void main()
{
int v;
char vd[20];
amlgraph G;
creategraph(G);
printf("交通图创建完毕\n ");
printf(" \n\n请输入遍历的起点: ");
scanf("%s",&vd);
v=LocateVex(G,vd);//返回输入顶点的位置
printf("\n\n深度优先遍历: \n");
dfstraverse(G,v);
printf("\n\n广度优先遍历:\n");
bfstraverse(G,v);
}