您的位置:首页 > 理论基础 > 数据结构算法

无向图的邻接多重表结构,存储结构及基本功能实现(最全)

2016-01-08 11:17 423 查看
邻接多重表结构主要针对的是无向图进行的存储优化。详细概念可百度谷歌下。如果我们在无向图的应用中,关注的重点是顶点,那么邻接表是不错的选择,但如果我们更关注边的操作,比如对已访问过的边做标记,删除某一条边等操作,那就意味着,需要找到这条边的两个边表结点进行操作,这其实还是比较麻烦的。因此,我们也依照十字链表的方式,对表结点进行一下改造:

                        [ ivex | ilink | jvex | jlink]

其中ivex和jvex是与某条边依附的两个顶点在顶点表中下标。ilink指向依附点ivex的下一条边,jlink指向依附点jvex的下一条边,这就是邻接多重表结构。关于邻接多重表的一些相关操作,多数网站上都没有具体的介绍,本人的代码主要参考严魏敏的
<数据结构>,但是本书中提供的代码有一定的问题(尤其是删除节点及其相关的边这一函数,弄了很长时间才把问题找出来并改正,神坑啊),以下是改正后的代码,我又添加了一些基本功能,希望可以给大家参考下:
在MulAdjGraph.h文件中:

/********************************* 声明区 ****************************/

typedef enum{unmarked,marked}MarkedIf;
typedef int VertexType; // 图节点标识号
typedef int QElemType;

/********************************* 数据结构体 ****************************/

typedef struct ArcType
{
int ivex,jvex; // 该边依附的两个顶点的位置
struct ArcType *ilink,*jlink; // 分别指向依附这两个顶点的下一条边
MarkedIf mark; // 访问标记
float weight; // 权重
inline ArcType(): ivex(-1),jvex(-1),mark(unmarked),weight(0) {}
}ArcType;

typedef struct VertexNode
{
VertexType data;
bool isVisited; // 访问标记
ArcType *firstarc; // 指向第一条依附该顶点的边
inline VertexNode():data(-1),isVisited(false) {}
}VertexNode;

typedef struct MulAdjGraph
{
VertexNode *adjmulist; // 多重邻接表
int ivexNum,iarcNum; // 无向图的当前顶点数和边数
int MaxVertexNum; // 表中最大的节点数量

MulAdjGraph(): ivexNum(0), iarcNum(0),MaxVertexNum(0) {}
}MulAdjGraph;

// 单链队列--队列的链式存储结构
typedef struct QNode
{
QElemType data; //数据域
struct QNode *next; //指针域
}QNode,*QueuePtr;

typedef struct
{
QueuePtr front,//队头指针,指针域指向队头元素
rear; //队尾指针,指向队尾元素
}LinkQueue;

/********************************* 基本操作函数 ****************************/

// 若G中存在顶点u,则返回该顶点在无向图中位置;否则返回-1
int LocateVex( const MulAdjGraph &G,const VertexType &u );

// 采用邻接多重表存储结构,构造无向图G
int CreateGraph(MulAdjGraph &G);

// 返回v的值
inline VertexType* GetVex(const MulAdjGraph &G,const int &v);

// 对v赋新值value
int PutVex(MulAdjGraph &G,const VertexType v,const VertexType &value);

// 返回v的第一个邻接顶点的序号。若顶点在G中没有邻接顶点,则返回-1
int FirstAdjVex(const MulAdjGraph &G,const VertexType &v);

// 返回v的(相对于w的)下一个邻接顶点的序号。若w是v的最后一个邻接点,则返回-1
int NextAdjVex(const MulAdjGraph &G,const VertexType &v,const VertexType &w);

// 在G中增添新顶点v(不增添与顶点相关的弧,留待InsertArc()去做)
int InsertVex(MulAdjGraph &G,const VertexType v);

// 在G中删除弧<v,w>
int DeleteArc(MulAdjGraph &G,const VertexType &v,const VertexType &w);

// 删除G中顶点v及其相关的边
int DeleteVex(MulAdjGraph &G,const VertexType &v);

void DestroyGraph(MulAdjGraph &G);

// 在G中增添弧<v,w>
int InsertArc(MulAdjGraph &G,const VertexType &v,const VertexType &w);

//搜索弧<v,w>是否存在
bool SearchArc(const MulAdjGraph &G,const VertexType &v,const VertexType &w);

//找到顶点v的邻接顶点,函数返回符合条件的顶点数目
int FindNeighbour(const MulAdjGraph &G,const VertexType &v,VertexType *s);

extern int(*VisitFunc)(const VertexType &v);

void DFS(MulAdjGraph &G,int v);

// 算法7.4
// 从第1个顶点起,深度优先遍历图G,并对每个顶点调用函数Visit
void DFSTraverse(MulAdjGraph &G,int(*visit)(const VertexType&));

// 从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数
// Visit一次且仅一次。使用辅助队列Q和访问标志数组visite
void BFSTraverse(MulAdjGraph &G,int(*Visit)(const VertexType&));

// 置边的访问标记为未被访问
void MarkUnvizited(MulAdjGraph &G);

// 输出无向图的邻接多重表G
void Display(MulAdjGraph &G);

int visit(const VertexType &v);在MulAdjGraph.cpp文件中
<p>#include "MulAdjGraph.h"</p>#include <stdio.h>
#include <stdlib.h>

// 若G中存在顶点u,则返回该顶点在无向图中位置;否则返回-1
int LocateVex( const MulAdjGraph &G,const VertexType &u )
{
int i;
for(i=0;i<G.ivexNum;++i)
if( u==G.adjmulist[i].data )
return i;
return -1;
}

// 采用邻接多重表存储结构,构造无向图G
int CreateGraph(MulAdjGraph &G)
{
int i,j,OverInfo,IncInfo;
VertexType first,second;

printf("输入无向图G的顶点数,边数,边是否含权重信息(是:1,否:0):");
scanf("%d,%d,%d",&G.ivexNum,&G.iarcNum,&OverInfo);
G.MaxVertexNum = G.ivexNum;
G.adjmulist = new VertexNode[G.MaxVertexNum];
printf("请输入%d个顶点的标识号(标识号为int数据类型):\n",G.ivexNum);
for(i=0;i<G.ivexNum;++i) // 构造顶点向量
{
scanf("%d",&G.adjmulist[i].data);
G.adjmulist[i].firstarc = NULL;
}
printf("请输入由两点构成的边%d条(以空格作为间隔):\n",G.iarcNum);
for(int k=0;k<G.iarcNum;++k) // 构造表结点链表
{
scanf("%d %d%*c",&first,&second); // %*c吃掉回车符
i=LocateVex(G,first); // 一端
j=LocateVex(G,second); // 另一端
if ( i == -1 || j == -1 ) return 0;

ArcType* pArc = new ArcType;
pArc->ivex = i;//赋值,权值,访问属性暂时不用
pArc->jvex = j;

pArc->ilink=G.adjmulist[i].firstarc;// 插在表头
G.adjmulist[i].firstarc=pArc;
pArc->jlink=G.adjmulist[j].firstarc;// 插在表头
G.adjmulist[j].firstarc=pArc;
if (OverInfo)
{
printf("该边是否有权重(1:有 0:无): ");
scanf("%d%*c",&IncInfo); // 吃掉回车符
if(IncInfo) // 边有相关信息
{
printf("请输入该边的权值:");
scanf("%f%*c",&pArc->weight); // 吃掉回车符
}
}
}

return 1;
}

// 返回v的值
inline VertexType* GetVex(const MulAdjGraph &G,const int &v)
{
if(v>=G.ivexNum||v<0) exit(0);
return &G.adjmulist[v].data;
}

// 对v赋新值value
int PutVex(MulAdjGraph &G,const VertexType v,const VertexType &value)
{
int i;
i=LocateVex(G,v);
if(i<0) // v不是G的顶点
return 0;
G.adjmulist[i].data=value;
return 1;
}

// 返回v的第一个邻接顶点的序号。若顶点在G中没有邻接顶点,则返回-1
int FirstAdjVex(const MulAdjGraph &G,const VertexType &v)
{
int i;
i=LocateVex(G,v);
if(i<0) return -1;
if(G.adjmulist[i].firstarc) // v有邻接顶点
if(G.adjmulist[i].firstarc->ivex==i)
return G.adjmulist[i].firstarc->jvex;
else
return G.adjmulist[i].firstarc->ivex;
else
return -1;
}

// 返回v的(相对于w的)下一个邻接顶点的序号。若w是v的最后一个邻接点,则返回-1
int NextAdjVex(const MulAdjGraph &G,const VertexType &v,const VertexType &w)
{
int i,j;
ArcType *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].firstarc; // 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;
}

// 在G中增添新顶点v(不增添与顶点相关的弧,留待InsertArc()去做)
int InsertVex(MulAdjGraph &G,const VertexType v)
{
if(G.ivexNum==G.MaxVertexNum) // 结点已满,不能插入
{
printf("结点已满,不能插入.\n");
return 0;
}
if(LocateVex(G,v)>=0) // 结点已存在,不能插入
{
printf("结点已存在,不能插入.\n");
return 0;
}
G.adjmulist[G.ivexNum].data = v;
G.adjmulist[G.ivexNum].firstarc=NULL;
G.ivexNum++;

return 1;
}

// 在G中删除弧<v,w>
int DeleteArc(MulAdjGraph &G,const VertexType &v,const VertexType &w)
{
int i,j;
ArcType *p,*q;

i=LocateVex(G,v);
j=LocateVex(G,w);
if(i<0||j<0||i==j) return 0;// 图中没有该点或弧
// 以下使指向待删除边的第1个指针绕过这条边
p=G.adjmulist[i].firstarc; // p指向顶点v的第1条边
if(p&&p->jvex==j) // 第1条边即为待删除边(情况1)
G.adjmulist[i].firstarc=p->ilink;
else if(p&&p->ivex==j) // 第1条边即为待删除边(情况2)
G.adjmulist[i].firstarc=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 0;
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].firstarc; // p指向顶点w的第1条边
if(p->jvex==i) // 第1条边即为待删除边(情况1)
{
G.adjmulist[j].firstarc=p->ilink;
delete p;
}
else if(p->ivex==i) // 第1条边即为待删除边(情况2)
{
G.adjmulist[j].firstarc=p->jlink;
delete p;
}
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;
delete p;
}
else if(p->ivex==j&&p->jvex==i) // 找到弧<v,w>(情况2)
{
if(q->ivex==j)
q->ilink=p->ilink;
else
q->jlink=p->ilink;
delete p;
}
}
--G.iarcNum;

return 1;
}

// 删除G中顶点v及其相关的边
int DeleteVex(MulAdjGraph &G,const VertexType &v)
{
int i,j;
VertexType w;
ArcType *p;

i=LocateVex(G,v); // i为待删除顶点的序号
if(i<0) return 0;
for(j=0;j<G.ivexNum;++j) // 删除与顶点v相连的边(如果有的话)
{
if(j==i)
continue;
w=*GetVex(G,j); // w是第j个顶点的值
DeleteArc(G,v,w);
}
for(j=i+1;j<G.ivexNum;++j) // 排在顶点v后面的顶点的序号减1
G.adjmulist[j-1]=G.adjmulist[j];
--G.ivexNum; // 顶点数减1
for(j=i;j<G.ivexNum;++j) // 修改顶点的序号
{
p=G.adjmulist[j].firstarc;
while(p)
{
if(p->ivex==j+1)
{
p->ivex--;
p=p->ilink;
}
else
{
p->jvex--;
p=p->jlink;
}
}
}

return 1;
}

void DestroyGraph(MulAdjGraph &G)
{
int i;

for(i=G.ivexNum-1;i>=0;--i)
DeleteVex(G,G.adjmulist[i].data);
delete [](G.adjmulist);
}

// 在G中增添弧<v,w>
int InsertArc(MulAdjGraph &G,const VertexType &v,const VertexType &w)
{
int i,j,IncInfo;

i=LocateVex(G,v); // 一端
j=LocateVex(G,w); // 另一端
if(i<0||j<0) return 0;
ArcType* pArc = new ArcType;
pArc->ivex=i;
pArc->jvex=j;
pArc->ilink=G.adjmulist[i].firstarc; // 插在表头
G.adjmulist[i].firstarc=pArc;
pArc->jlink=G.adjmulist[j].firstarc; // 插在表头
G.adjmulist[j].firstarc=pArc;
//printf("该边是否有权重(1:有 0:无): ");
//scanf("%d%*c",&IncInfo); // 吃掉回车符
//if(IncInfo) // 边有相关信息
//{
// printf("请输入该边的权值:");
// scanf("%f%*c",&pArc->weight); // 吃掉回车符
//}
G.iarcNum++;

return 1;
}

//搜索弧<v,w>是否存在
bool SearchArc(const MulAdjGraph &G,const VertexType &v,const VertexType &w )
{
int i,j;
ArcType* p;
i=LocateVex(G,v); // 一端
j=LocateVex(G,w); // 另一端
if(i<0||j<0||i==j) return false;//图中没有该弧

p=G.adjmulist[i].firstarc; // p指向顶点v的第1条边
while(p) // 向后查找弧<v,w>
{
if(p->ivex==i&&p->jvex==j) //找到弧<v,w>(情况1)
return true;
else if(p->jvex==i&&p->ivex==j) // 找到弧<v,w>(情况2)
return true;
else if (p->ivex==i)
p=p->ilink;
else if (p->jvex==i)
p=p->jlink;

}
return false;
}

//找到顶点v的邻接顶点,函数返回符合条件的顶点数目
int FindNeighbour( const MulAdjGraph &G,const VertexType &v)
{
int i,num=0;
ArcType* p;
i=LocateVex(G,v); // 一端
if (i<0) exit(0);//顶点v不存在
p=G.adjmulist[i].firstarc; // p指向顶点v的第1条边
if (p)
{
printf("顶点%d的邻接节点为:\n",v);
while(p)
{
if (p->ivex==i)
{
printf("%4d ",G.adjmulist[p->jvex].data);
++num;
p=p->ilink;
}
else//p->jvex == i
{
printf("%4d ",G.adjmulist[p->ivex].data);
++num;
p=p->jlink;
}
}
}
else
printf("该顶点无相邻顶点.\n");
return num;
}

int(*VisitFunc)(const VertexType &v);

void DFS(MulAdjGraph &G,int v)
{
int j;
ArcType *p;

VisitFunc(G.adjmulist[v].data);
G.adjmulist[v].isVisited=true;

p=G.adjmulist[v].firstarc;
while(p)
{
j=p->ivex==v?p->jvex:p->ivex;
if(!G.adjmulist[j].isVisited)
DFS(G,j);
p=p->ivex==v?p->ilink:p->jlink;
}
}

// 算法7.4
// 从第1个顶点起,深度优先遍历图G,并对每个顶点调用函数Visit
void DFSTraverse(MulAdjGraph &G,int(*visit)(const VertexType&))
{
int i;
VisitFunc=visit;
for(i=0;i<G.ivexNum;++i)
G.adjmulist[i].isVisited=false;
for(i=0;i<G.ivexNum;++i)
if(!G.adjmulist[i].isVisited)
DFS(G,i);
printf("\n");
}

// 构造一个空队列Q
int InitQueue(LinkQueue *Q)
{
(*Q).front=(*Q).rear=new QNode();
//(*Q).front=(*Q).rear=(QueuePtr)malloc(sizeof(QNode)); //动态分配一个空间
if(!(*Q).front)
exit(0);
(*Q).front->next=NULL; //队头指针指向空,无数据域,这样构成了一个空队列
return 1;
}

// 若Q为空队列,则返回1,否则返回0
int QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return 1;
else
return 0;
}

// 插入元素e为Q的新的队尾元素
int EnQueue(LinkQueue *Q,QElemType e)
{
QueuePtr p=new QNode();
//QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) // 存储分配失败
exit(0);
//生成一个以为e为数据域的队列元素
p->data=e;
p->next=NULL;
//将该新队列元素接在队尾的后面
(*Q).rear->next=p;
(*Q).rear=p;
return 1;
}

// 若队列不空,删除Q的队头元素,用e返回其值,并返回1,否则返回0
int DeQueue(LinkQueue *Q,QElemType *e)
{
QueuePtr p;
if((*Q).front==(*Q).rear)
return 0;
p=(*Q).front->next; //队头元素
*e=p->data;
(*Q).front->next=p->next;
if((*Q).rear==p)
(*Q).rear=(*Q).front;
delete p;
//free(p);
return 1;
}

//销毁队列
void DestoryQueue(LinkQueue *Q)
{
while(Q->front)
{
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
}

// 从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数
// Visit一次且仅一次。使用辅助队列Q和访问标志数组visite
void BFSTraverse(MulAdjGraph &G,int(*Visit)(const VertexType&))
{
int i,u,w;
VertexType w1,u1;
LinkQueue Q;

for(i=0;i<G.ivexNum;++i)
G.adjmulist[i].isVisited=false; // 置初值
InitQueue(&Q); // 置空的辅助队列Q
for(i=0;i<G.ivexNum;++i)
if(!G.adjmulist[i].isVisited) // v尚未访问
{
G.adjmulist[i].isVisited=true; // 设置访问标志为1(已访问)
Visit(G.adjmulist[i].data);
EnQueue(&Q,i); // v入队列
while(!QueueEmpty(Q)) // 队列不空
{
DeQueue(&Q,&u); // 队头元素出队并置为u
u1=*GetVex(G,u);
for(w=FirstAdjVex(G,u1);w>=0;w=NextAdjVex(G,u1,w1))
{
if(!G.adjmulist[w].isVisited) // w为u的尚未访问的邻接顶点的序号
{
G.adjmulist[w].isVisited=true;
Visit(G.adjmulist[w].data);
EnQueue(&Q,w);
}
w1=*GetVex(G,w);
}
}
}

DestoryQueue(&Q);
printf("\n");
}

// 置边的访问标记为未被访问
void MarkUnvizited(MulAdjGraph &G)
{
int i;
ArcType *p;

for(i=0;i<G.ivexNum;++i)
{
p=G.adjmulist[i].firstarc;
while(p)
{
p->mark=unmarked;
if(p->ivex==i)
p=p->ilink;
else
p=p->jlink;
}
}
}

// 输出无向图的邻接多重表G
void Display(MulAdjGraph &G)
{
int i;
ArcType *p;

MarkUnvizited(G); // 置边的访问标记为未被访问
printf("%d个顶点:\n",G.ivexNum);
for(i=0;i<G.ivexNum;++i)
printf("%d ",G.adjmulist[i].data);
printf("\n%d条边:\n",G.iarcNum);
for(i=0;i<G.ivexNum;++i)
{
p=G.adjmulist[i].firstarc;
while(p)
if(p->ivex==i) // 边的i端与该顶点有关
{
if(!p->mark) // 只输出一次
{
printf("%d-%d,%8.2f ",G.adjmulist[i].data,G.adjmulist[p->jvex].data,p->weight);
p->mark=marked;
}
p=p->ilink;
}
else // 边的j端与该顶点有关
{
if(!p->mark) // 只输出一次
{
printf("%d-%d,%8.2f ",G.adjmulist[p->ivex].data,G.adjmulist[i].data,p->weight);
p->mark=marked;
}
p=p->jlink;
}
printf("\n");
}
}

int visit(const VertexType &v)
{
printf("%6d",v);
return 1;
}

test.cpp文件测试代码:
#include "MulAdjGraph.h"
int main()
{
int k,n;
MulAdjGraph g;
VertexType v1,v2;

CreateGraph(g);
Display(g);

/***************************** 修改节点 *****************************/
//printf("修改节点的值,请输入原值 新值: ");
scanf("%d %d",&v1,&v2);
PutVex(g,v1,v2);
Display(g);

/**************************** 删除节点 *****************************/
printf("删除节点,请输入节点的值: ");
scanf("%d",&v1);
DeleteVex(g,v1);
Display(g);

/****************************插入新节点*****************************/
printf("插入新节点,请输入节点的值: ");
scanf("%d",&v1);
InsertVex(g,v1);
printf("插入与新节点有关的边,请输入边数: ");
scanf("%d",&n);
for(k=0;k<n;++k)
{
printf("请输入另一节点的值: ");
scanf("%d",&v2);
InsertArc(g,v1,v2);
}
Display(g);

/***************************** 搜索 ******************************/
printf("深度优先搜索的结果:\n");
DFSTraverse(g,visit);
printf("广度优先搜索的结果:\n");
BFSTraverse(g,visit);

DestroyGraph(g);

system("pause");
return 0;
}
测试截图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构 c++