**课程名称:数据结构与算法
课程类型:必修
实验项目:图型结构的建立与搜索
实验题目:图的存储结构的建立与搜索
实验日期:2017/12/3**
一、实验目的
图的搜索(遍历)算法是图型结构相关算法的基础,本实验要求编写程序演示无向图三种典型存储结构的建立和搜索(遍历)过程。让我们在这个过程中熟悉这些操作之间的关系和内在联系。
二、实验要求及实验环境
1.分别实现无向图的邻接矩阵、邻接表和邻接多重表存储结构的建立算法,分析和比较各建立算法的时间复杂度以及存储结构的空间占用情况;
2.实现无向图的邻接矩阵、邻接表和邻接多重表三种存储结构的相互转换的算法;
3.在上述三种存储结构上,分别实现无向图的深度优先搜索(递归和非递归)和广度优先搜索算法。并以适当的方式存储和显示相应的搜索结果(深度优先或广度优先生成森林(或生成树)、深度优先或广度优先序列和编号);
4.分析搜索算法的时间复杂度;
5.以文件形式输入图的顶点和边,并显示相应的结果。要求顶点不少于 10 个,边不少于 13 个;
6.软件功能结构安排合理,界面友好,便于使用。
实验环境:
Win10/code blocks
三、设计思想
(本程序中的用到的所有数据类型的定义,主程序的流程图及各程序模块之间的调用关系)
1.逻辑设计
define NumVertices 25//顶点数目最大值
//using namespace std;
/*
无向图的邻接矩阵
*/
typedef char VertexData[5];//保存顶点名称数组
typedef int EdgeData;//保存该点的值
typedef struct
{
VertexData vertex[NumVertices];//顶点表,里面是所有顶点名称
EdgeData edge[NumVertices][NumVertices]; //邻接矩阵—边表, 可视为顶点之间的关系
int n, e; //图的顶点数与边数
} MTGraphSquare;
/*
无向图的邻接表
*/
typedef struct node //边表结点
{
int adjvex; //邻接点域(下标)
EdgeData cost; //边上的权值
struct node *next; //下一边链接指针
} EdgeNode;
typedef struct //顶点表结点
{
VertexData vertex; //顶点数据域
EdgeNode *firstedge;//边链表头指针
} VertexNode;
typedef struct //图的邻接表
{
VertexNode vexlist[NumVertices];// 顶点表,里面是所有顶点名称
int n, e; //顶点个数与边数
} AdjGraph;
typedef bool VisitIf;//是否访问过
typedef struct InfoType//信息域
{
VertexData vertex1;
VertexData vertex;
int weight;
} InfoType;
typedef struct EBox//节点box
{
VisitIf mark; //边访问标记
int ivex, jvex; //该边依附的两个顶点的位置
struct EBox * ilink, * jlink; //分别指向依附这两个顶点的下一条边
InfoType info; //该边信息指针
} EBox;
typedef struct VexBox//顶点box
{
VertexData data;
EBox * firstedge; //指向第一条依附于该顶点的边
} VexBox;
typedef struct
{
VexBox adjmulist[MAX_VERTEX_NUM];
int vexnum,edgenum; //无向图的当前顶点数和边数
} AMLGraph;
bool visited[50]= {false}; //是否进行过访问
下面是相应的图示。
2.物理设计
int AMLGraphDFSTraverseFeidiguiBUG2(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
int AMLGraphDFSTraverseFeidiguiBUG3(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
Status AMLGraphDFSTraverseFeidigui(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
void AMLGraphBFSTraverse(AMLGraph *G)
int NextAdjVex(AMLGraph *G,VertexData v,VertexData w)//返回v的(相对于w的)下一个邻接顶点
int FirstAdjVex(AMLGraph *G,VertexData v)//寻找v的第一个邻接顶点
void AMLGraphDFSTraverse(AMLGraph *G)
void AMLGraphDFS(AMLGraph *G,int v)
//深度优先(非递归)
//LGraph: 邻接表
//InitVertex: 遍历起始顶点
void AdjGraphDFS2(AdjGraph *LGraph, int InitVertex)
void AdjGraphBFS(AdjGraph *G,int s)
//广度优先遍历 (递归)
//缺点递归最大为G.numv次
//深度优先遍历主程序
void AdjGraphDFSTraverse(AdjGraph *G)
void AdjGraphDFS1(AdjGraph *G,int i)
void MTGraphSquareDFSTraverse2(MTGraphSquare *G)
void MTGraphSquareDFS2(MTGraphSquare *G, int i)
void MTGraphSquareBFSTraverse(MTGraphSquare *G, char vName[5])
int MTGraphSquareLocateVex(MTGraphSquare *G, char name[5])
//v相对于w的下一个邻接点
int MTGraphSquareNextAdjVex(MTGraphSquare *G, int v, int w)
//广度优先遍历
//v的第一个邻接点
int MTGraphSquareFirstAdjVex(MTGraphSquare *G, int v)
void MTGraphSquareDFSTravel(MTGraphSquare *G)
void MTGraphSquareDFS(MTGraphSquare *G, int i)//以vi为出发点对邻接矩阵表示的图G进行深度优先搜索
//总而言之,就是对于表的遍历和建立两个联合起来。
AdjGraph TransAMLraphSquareToAdjGraph(AMLGraph *T)
MTGraphSquare TransAMLGraphSquareToMTGraph(AMLGraph *G)
AMLGraph TransMTGraphSquareToAMLGraph(MTGraphSquare *T)
AMLGraph TransAdjGraphSquareToAMLGraph(AdjGraph *T)
MTGraphSquare TransAdjGraphSquareToMTGraph(AdjGraph *G)
AdjGraph TransMTGraphSquareToAdjGraph(MTGraphSquare *T)//邻接矩阵初始化
void PrintInitUDGAMLFalse(AMLGraph *G)//无法逆向访问
void PrintInitUDGAMLTrue(AMLGraph *G)
void InitUDGAML(AMLGraph *G) //用邻接多重表存储,构造无向图G
void DisplayAML(AMLGraph *G)
void MarkUnvizitedAML(AMLGraph *G)
/* bo7-4.c 无向图的邻接多重表存储(存储结构由c7-4.h定义)的基本函数类型(16个) */
int LocateVex(AMLGraph *G,VertexData u)
void PrintAdjGraph (AdjGraph *G)
void CreateAdjGraph (AdjGraph *G)//邻接表初始化
void PrintMTGraphSquare(MTGraphSquare *T)//邻接矩阵打印
void InitMTGraphSquare(MTGraphSquare *T)//邻接矩阵初始
从main函数开始启动程序,根据输入的choice来判断要进入哪一个函数。根据界面的提示:
printf(“Please input your choice.INPUT 18 to exit
”);
printf(“0:初始化邻接矩阵(MTGraph)
”);
printf(“1:深度优先递归邻接矩阵(MTGraph)
”);
printf(“2:广度优先递归邻接矩阵(MTGraph)
”);
printf(“3:深度优先非递归邻接矩阵(MTGraph)
”);
printf(“4:邻接矩阵到邻接多重表的转换(MTGraph)
”);
printf(“5:邻接矩阵到邻接表的转换(MTGraph)
”);
printf(“6:初始化邻接表(AdjGraph)
”);
printf(“7:深度优先递归邻接表(AdjGraph)
”);
printf(“8:广度优先递归邻接表(AdjGraph)
”);
printf(“9:深度优先非递归邻接表(AdjGraph)
”);
printf(“10:邻接表到邻接矩阵的转换(AdjGraph)
”);
printf(“11:邻接表到邻接多重表的转换(AdjGraph)
”);
printf(“12:初始化邻接多重表(AMLGraph)
”);
printf(“13:深度优先递归邻接多重表(AMLGraph)
”);
printf(“14:广度优先递归邻接多重表(AMLGraph)
”);
printf(“15:深度优先非递归邻接多重表(AMLGraph)
”);
printf(“16:邻接多重表到邻接矩阵的转换(AMLGraph)
”);
printf(“17:邻接多重表到邻接表的转换(AMLGraph)
”);
printf(“INPUT 18 to exit
”);
scanf(“%d”,&choice);
getchar();
这些函数互相直接几乎没有调用关系,但是每次开始都会清除一次bool visited[]数组,使得全为false。便于访问表的时候记录是否访问过该位置。
其实这些存储结构之间的转换都是很相似的,访问方式和顺序也是相似的,但是具体的存储结构的不同,要具体去写还是会在实际中遇到很多问题。六个表之间的转换其实步骤都是相似的,只是把数据都给读出来,然后按照相应的表储存。
示例数据保存的图如下面的图表示。
dfs搜索的时间复杂度分析:
邻接表:O(n+e) ;邻接矩阵:O(n2);多重邻接表O(n+e)
dfs遍历的大致流程如下图:
void DFS1 (AdjGraph* G, int i)
//以vi为出发点时对邻接表表示的图G进行先深搜索
{ EdgeNode *p;
cout<
define OK 1
define TRUE 1
define ERROR 0
define FALSE
typedef int SElemType;
typedef int QElemType;
typedef struct//栈结构
{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
Status InitStack(SqStack &S)
{
S.base=(SElemType *)malloc(100*sizeof(SElemType));
if(!S.base)
exit(0);
S.top=S.base;
S.stacksize=100;
return OK;
}
Status DestroyStack(SqStack &S)
{
free(S.base);
S.base=NULL;
S.top=NULL;
S.stacksize=0;
return OK;
}
Status Push(SqStack &S,SElemType e)
{
if(S.top-S.base>=S.stacksize)
{
S.base=(SElemType*)realloc(S.base,(S.stacksize+100)*sizeof(SElemType));
if(!S.base)
exit(0);
S.top=S.base+S.stacksize;
S.stacksize+=100;
}
*S.top++=e;
return OK;
}
Status Pop(SqStack &S,SElemType &e)
{
if(S.top==S.base)
return ERROR;
e=*–S.top;
return OK;
}
Status StackEmpty(SqStack S)
{
if(S.top==S.base)
return OK;
else
return ERROR;
}
Status AMLGraphDFSTraverseFeidigui(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
{
int v,w,u;
SqStack S,S2;
InitStack(S);
InitStack(S2);
w=LocateVex(G,start);
for(v=0;vvexnum;v++)
visited[v]=0;
for(v=0;vvexnum;v++)
if(!visited[(v+w)%G->vexnum])
{
Push(S,(v+w)%G->vexnum);
while(!StackEmpty(S))
{
Pop(S,u);
if(!visited[u])
{
visited[u]=1;
printf(“%s ”,G->adjmulist[u].data);
for(w=FirstAdjVex(G,G->adjmulist[u].data);w>=0;
w=NextAdjVex(G,G->adjmulist[u].data,G->adjmulist[w].data))
if(!visited[w])
Push(S2,w);
while(!StackEmpty(S2))
{
Pop(S2,u);
Push(S,u);
}
}
}
}
return OK;
}
int AMLGraphDFSTraverseFeidiguiBUG3(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
{
/*test
*/
int v=0,w=0,u=0;
stack<int> S,S2;
w=LocateVex(G,start);
printf("-----%d-----
",w);
for(v=0; v<G->vexnum; v++)
if(!visited[(v+w)%G->vexnum])
{
printf("??%d?? ",(v+w)%G->vexnum);
printf("-----looping-----
");
S.push((v+w)%G->vexnum);
while(!S.empty())
{
printf("u:%d
",u);
S.pop();
if(!visited[u])
{
printf("#####looping####
");
visited[u]=true;
printf("%s",G->adjmulist[u].data);
for(w=FirstAdjVex(G,G->adjmulist[u].data); w>=0;
w=NextAdjVex(G,G->adjmulist[u].data,G->adjmulist[w].data))
if(!visited[w])
{
S2.push(w);
printf("Panduan
");
}
while(!S2.empty())
{
printf("#####S2####
");
S2.pop();
S.push(u);
}
}
}
}
return 1;
}
int AMLGraphDFSTraverseFeidiguiBUG2(AMLGraph *G,VertexData start)//从start顶点起,深度优先遍历图G(非递归算法)
{
int v=0,w=0;
stack<int> S;
w=LocateVex(G,start);
printf("-----%d-----
",w);
EBox *p;
for(v=0; v<G->vexnum; v++)
{
p=G->adjmulist[w].firstedge;
S.push(w);
visited[w]=true;
if(!visited[w])
printf("%s",G->adjmulist[w].data);
for(int i=0;visited[p->ivex]!=false;i++)
{
}
while(p!=NULL)
{
w=p->ivex;
}
}
return 1;
}
访问每个图的函数分别为:
1:邻接矩阵:
void PrintMTGraphSquare(MTGraphSquare *T)//邻接矩阵打印
{
int i,j;
printf(“邻接矩阵打印(PrintMTGraphSquare)
”);
for(i=0; in; i++)
printf(“%d “,i);
printf(“
”);
for(i=0; in; i++)
{
printf(“%d ”,i);
for(j=0; jn; j++)
{
if(T->edge[i][j]!=0||T->edge[j][i]!=0)
//cout<vertex[i];
printf(“1 “);
else
printf(“0 “);
}
printf(“
”);
}
return;
}
2.邻接表:
void PrintAdjGraph (AdjGraph *G)
{
printf(“邻接表打印(PrintAdjGraph)
”);
int flag[11][11]= {0};
int i=0;
EdgeNode p=(EdgeNode)malloc(sizeof(EdgeNode));
for(i=0; in; i++)
{
p=G->vexlist[i].firstedge;
while(p!=NULL)
{
if(flag[i][p->adjvex]==0&&flag[p->adjvex][i]==0)
{
printf(“v%d->v%d:(%d,%d):%d
”,i,p->adjvex,i,p->adjvex,p->cost);
flag[i][p->adjvex]=1;
flag[p->adjvex][i]=1;
}
p=p->next;
}
}
//cout<vertex[i];
}
3.邻接多重表:
void PrintInitUDGAMLTrue(AMLGraph *G)
{
printf(“输出无向图的邻接多重表G(PrintInitUDGAMLTrue)
”);
int i;
EBox p=(EBox)malloc(sizeof(EBox));
for(i=0; ivexnum; i++)
{
p=G->adjmulist[i].firstedge;
//printf(“%s :
”,G->adjmulist[i].data);
while(p!=NULL)
{
if(p->mark==0)
{
printf(“(%s,%s):%d
”,G->adjmulist[i].data,p->info.vertex,p->info.weight);
p->mark=1;
}
p=p->ilink;
}
}
for(i=0; ivexnum; i++)
{
p=G->adjmulist[i].firstedge;
while(p!=NULL)
{
if(p->mark==1)
p->mark=0;
p=p->ilink;
}
}
return;
}
邻接矩阵到邻接表的转换:
AdjGraph TransMTGraphSquareToAdjGraph(MTGraphSquare *T)//邻接矩阵初始化
{
printf(“邻接矩阵到邻接表的转换(TransMTGraphSquareToAdjGraph)
”);
//PrintMTGraphSquare(T);
int flag[11][11]= {0};
AdjGraph G;
int tail,head,weight;
int j,k;
G.n=T->n;
G.e=T->e;//输入顶点个数和边数
for ( int i = 0; i < G.n; i++) //建立顶点表
{
strcpy(G.vexlist[i].vertex,T->vertex[i]); //输入顶点信息
G.vexlist[i].firstedge = NULL;
} //边表置为空表
//逐条边输入,建立边表
for(j=0; j<G.n; j++)
{
for(k=0; k<G.n; k++)
{
if(T->edge[j][k]!=0&&flag[j][k]==0)
{
tail=k;
head=j;
weight=T->edge[j][k];
flag[j][k]=1;
flag[k][j]=1;
EdgeNode *p = (EdgeNode*)malloc(sizeof(EdgeNode)); //建立边结点
p->adjvex = head;
p->cost = weight; //3.3设置边结点
p->next = G.vexlist[tail].firstedge; //链入第 tail 号链表的前端
G.vexlist[tail].firstedge = p;
p = (EdgeNode*)malloc(sizeof(EdgeNode));
p->adjvex = tail;
p->cost = weight;
p->next = G.vexlist[head].firstedge; //链入第 head 号链表的前端
G.vexlist[head].firstedge = p;
//printf("(%d,%d):%d
",tail,head,weight);
//printf("%d,%d,%d
",head,tail,weight);
}
}//输入
}
PrintAdjGraph(&G);
return G;
}
接表到邻接矩阵的转换:
MTGraphSquare TransAdjGraphSquareToMTGraph(AdjGraph *G)
{
printf(“邻接表到邻接矩阵的转换(TransAdjGraphSquareToMTGraph)
”);
MTGraphSquare T;
int i,j,k,w;
T.n=G->n;
T.e=G->e;
for(i=0; i
五、经验体会与不足
其实这些存储结构之间的转换都是很相似的,访问方式和顺序也是相似的,但是具体的存储结构的不同,要具体去写还是会在实际中遇到很多问题。六个表之间的转换其实步骤都是相似的,只是把数据都给读出来,然后按照相应的表储存。
这次的实验中,遇到了一些问题,比如说bfs和dfs用非递归来写,我觉得这个部分对我来说最难,这个过程很难理解,但是最后在室友的帮助下,我顺利解决了这个问题。