数据结构课程设计报告书.docx
《数据结构课程设计报告书.docx》由会员分享,可在线阅读,更多相关《数据结构课程设计报告书.docx(28页珍藏版)》请在冰豆网上搜索。
![数据结构课程设计报告书.docx](https://file1.bdocx.com/fileroot1/2023-1/22/f8827be0-d6fb-4ef2-85aa-5b55024fbcd8/f8827be0-d6fb-4ef2-85aa-5b55024fbcd81.gif)
数据结构课程设计报告书
课程设计正文
1.问题分析和任务定义
1.1问题分析
图的有关知识在很多领域有着广泛的应用,如电路分析、寻找最短路径、鉴定化合物等。
在很多情况下是要在寻找两个顶点之间的最短路径。
本课程设计是判断无向图中任意两个顶点之间是否存在一条长度为k的简单路径。
1.2任务定义
(1)采用邻接表作为存储结构
(2)编写程序判断无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径。
(3)测试用例自己设计
2.开发平台
WindowsXP,C++语言,VC++6.0
3.数据类型和系统定义
3.1概要设计
3.1.1抽象数据类型图的定义
ADTGraphlnk
{
数据对象V:
V是具有相同特性的数据元素的集合,称为顶点集。
数据关系R:
R={VR}
VR={(v,w)|v,w
V,(v,w)表示v和w之间存在路径}
基本操作:
NumberOfVertices()
初始条件:
图已存在。
操作结果:
返回图中的顶点数。
NumberOfEdges()
初始条件:
图已存在。
操作结果:
返回图中的边数。
getValue(inti)
初始条件:
图已存在。
操作结果:
返回图中第i(从0开始)号位置上的顶点。
getFirstNeighbor(intv)
初始条件:
图已存在,v是图中某个顶点。
操作结果:
返回v的第一个邻接顶点。
getNextNeighbor(intv,intw)
初始条件:
图已存在,v和w是图中的两个顶点。
操作结果:
返回v的第一个邻接顶点w的下一个邻接顶点。
getVertexPos(constTvertex)
初始条件:
图已存在,vertex与图中顶点具有相同的特征。
操作结果:
如图中存在顶点vertex,返回该顶点在图中的位置;否则返回-1。
insertVertex(constT&vertex)
初始条件:
图已存在,vertex和图中顶点有相同的特征。
操作结果:
如果vertex不在图中,则插入;否则不插入。
removeVertex(intv)
初始条件:
图已存在,v是图中某个顶点。
操作结果:
删除顶点v及与其关联的边。
insertEdge(intv1,intv2)
初始条件:
图已存在,v和w是图中的两个顶点。
操作结果:
在图中插入边(v1,v2)。
removeEdge(intv1,intv2)
初始条件:
图已存在,(v1,v2)是图中一条边。
操作结果:
删除图中的边(v1,v2)
FineRoute(Tc1,Tc2,intk)
初始条件:
图已存在,c1,c2是图中两个顶点。
操作结果:
寻找c1,c2之间长度为k的简单路径,若找到,则返回1,并给出路径;否则,返回-1,并输出相关信息。
friendistream&operator>>(istream&in,Graphlnk&G);输入
friendostream&operator<<(ostream&out,Graphlnk&G);输出
}ADTGraphlnk
3.1.2主程序
intmain()
{
输入顶点数和边数;
输入顶点信息;
输入边信息;
输出图的顶点数,边数,顶点,边;
寻找两顶点之间长度为k的简单路径;
删除图中一个顶点;
输出图的顶点数,边数,顶点,边;
寻找两顶点之间长度为k的简单路径;
往图中插入一条边;
输出图的顶点数,边数,顶点,边;
寻找两顶点之间的路径;
}
3.2详细设计
3.2.1存储结构
本设计采用邻接表作为图中数据的存储结构,程序中使用指针定义一个顶点表*NodeTable,同时它也是每一条边链表的头结点,其data域中存放的是各顶点在图中的位置,其adj域中则存放的是指向第一条边结点的指针。
边链表结点中dest域中存放的是与此顶点相关联的边的另一顶点位置,link域中存放的是与此顶点相关联的下一条边的指针。
例如:
3.2.2类的说明
程序中需要的全程量
constintDefaultSize=10;
顶点类型
template//模板
structVertex//顶点的定义
{
Tdata;//顶点的名字
Edge*adj;//边链表的投指针
};
边类型
template
structEdge//边结点的定义
{
intdest;//边的另一顶点位置
Edge*link;//下一条边链指针
Edge(){}//构造函数
Edge(intnum):
dest(num),link(NULL){}//构造函数
};
无向图类型
template
classGraphlnk//图的类定义
{
public:
Graphlnk(intsz=DefaultVertices);
//构造函数
~Graphlnk();
//析构函数
TgetValue(inti)
{return(i>=0&&iNodeTable[i].data:
NULL;}
//取位置为i的顶点中的值
intNumberOfVertices(){returnNumVertices;}
//返回图中的顶点数
intNumberOfEdges(){returnNumEdges;}
//返回图中的边数
intgetFirstNeighbor(intv);
//取顶点v的第一个邻接顶点
intgetNextNeighbor(intv,intw);
//取顶点v的第一个邻接顶点的下一个邻接顶点
intgetVertexPos(constTvertex);
//给出顶点v在图中的位置
intFind(intv1,intv2,intk,intvisited[],intpath[],intd);
//寻找路径
boolinsertVertex(constT&vertex);
//在图中插入一个顶点vertex
boolremoveVertex(intv);
//在图中删除一个顶点v
boolinsertEdge(intv1,intv2);
//在图中插入一条边(v1,v2)
boolremoveEdge(intv1,intv2);
//在图中删除一条边(v1,v2)
voidFindRoute(Tc1,Tc2,intk);
//在图中寻找顶点v1,v2之间长度为k的简单路径
protected:
intmaxVertices;
//图中的最大顶点数
intNumVertices;
//图中的顶点数
intNumEdges;
//图中的边数
private:
Vertex*NodeTable;//顶点表
friendistream&operator>>(istream&in,Graphlnk&G);
//输入
friendostream&operator<<(ostream&out,Graphlnk&G);
//输出
};
3.2.3主要函数的伪代码算法
insertVertex(constT&vertex)
//在图的顶点表中插入一个新顶点vertex。
若插入成功,
//函数返回true,否则返回false
{
if(表满)不能插入,返回false;
for(inti=0;i在图中寻找该顶点,如果已经存在,不能插入,返回false;
图中无此顶点,将其插在顶点表的最后;
图中顶点数加1;
返回true;
}
removeVertex(intv)
//在图中删除一个指定点v,v是顶点号。
若删除成功,
//函数返回true,否则返回false。
{
if(表空或顶点号超出范围)无法删除,返回false
Edge*p,*s,*t;
intk;
while(NodeTable[v].adj!
=NULL)
{
顶点v的邻接顶点k;
找对称存放的边结点;
删除对称存放的边结点;
清除顶点v的边链表结点;
与顶点v相关联的边数减1;
}
图中顶点个数减1;
用图中最后一个顶点来填补被删顶点
}
insertEdge(intv1,intv2)
//在图中插入一条边(v1,v2),若此边存在或参数不合理,
//函数返回false,否则函数返回true
{
if(顶点v1,v2都在图中)
{
Edge*q,*p=NodeTable[v1].adj;
//v1对应的边链表头指针
寻找邻接顶点v2;
找到此边,不插入;
否则创建新结点;
链入v1边链表;
链入v2边链表;
}
顶点v1或v2不在图中,无法插入边,返回false;
}
removeEdge(intv1,intv2)
{
if(顶点v1,v2都在图中)
{
Edge*p=NodeTable[v1].adj,*q=NULL,*s=p;
在v1对应边链表中找被删边;
找到被删边结点;
{
如果该结点是边链表首节点
p指向p下一个节点;
如果不是首结点,重新链接;
删除结点p;
}
如果没有找到被删边结点,返回false;
p=NodeTable[v2].adj;//v2对应边链表中删除
找到被删边结点;
{
如果该结点是边链表首节点
p指向p下一个节点;
如果不是首结点,重新链接;
删除结点p;
}
返回true;
}
结点v1或v2不在图中,返回false;
}
Find(intv1,intv2,intk,intvisited[],intpath[],intd)
//寻找顶点v1和v2之间长度为k的简单路径
{
intnode,i;
Edge*p;
从顶点v1出发,并将visited[v1]标记为1,表示此顶点
已经被问过;
走过的路径的条数d(从0开始)加1;
记录下走过第d条路径时,经过的顶点;
如果从顶点v1出发,走过k条路径到达顶点v2,
函数返回1
p=NodeTable[v1].adj;//顶点v1对应边链表的头指针
while(p!
=NULL)
{
寻找此边的另一顶点;
If(另一顶点没有被访问过)
调用FindRoute函数递归寻找;
与v1相关联的下一条边结点;
}
顶点回溯;
边回溯;
未找到,返回0;
}
getFirstNeighbor(intv)
//给出顶点位置为v的第一个邻接顶点的位置,如果找不到,
//则函数返回-1
{
if(顶点v存在)
{
Edge*p=NodeTable[v].adj;
//对应边链表第一个边结点
如果第一个邻接顶点存在,返回其在图中的的位置;
}
顶点v或第一个邻接顶点不存在,返回-1;
}
getNextNeighbor(intv,intw)
//给出顶点位置为v的邻接顶点w的下一个邻接顶点的位置,
//如果没有下一个邻接顶点,则函数返回-1
{
if(顶点v存在1)
{
Edge*p=NodeTable[v].adj,*q=NULL;
//对应边链表第一个边结点
寻找邻接顶点w;
寻找顶点w对应边链表的第一个边结点;
不为空返回顶点在图中的位置;
}
顶点v或下一个邻接顶点不存在,返回-1;
}
istream&operator>>(istream&in,Graphlnk&G)
//输入
{
inti,j,k,n,m;
Te1,e2;
输入顶点数n和边数m;
如果m>n*(n-1)/2,给出出错信息,并要求用户重新输入;
for(i=0;i{
输入顶点;
if(此顶点已在图中)
给出出错信息,并要求用户重新输入;
将顶点插入图中;
}
while(i{
输入边;
if(顶点不在图中或者此边已在图中)
给出出错信息,并要求用户重新输入;
将边插入图中;
}
returnin;
}
ostream&operator<<(ostream&out,Graphlnk&G)
//输出
{
输出图中顶点数;
输出图中边数;
输出图中所有的顶点;
输出图中所有的边;
returnout;
}
3.2.4测试用例设计
输入顶点数和边数:
610
输入顶点:
ABCDEF
输入边:
ABACADAEAFBCBDEFDECE
4.调试报告
4.1遇到的问题及解决方案
(1)开始时,将getVertexPos()函数定义为Graphlnk类的私有函数,当Graphlnk类的对象G调用它时,编译出现如下错误:
errorC2248:
'getVertexPos':
cannotaccessprivatememberdeclaredinclass'Graphlnk'。
将getVertexPos()的访问权限改为公有后,不再出现此错误。
这是因为类中定义的私有成员函数只能在类中使用,即使是该类的对象也不能访问该类的私有成员,包括数据成员和成员函数。
(2)由于无向图采用邻接表作为其存储结构时,由于(Vi,Vj)与(Vj,Vi)是同一条边,在邻接表中,一个在顶点i对应的边链表的中,另一个在顶点j对应的边链表中。
所以同一条边在邻接表中出现了两次。
当使用重载操作符<<输出图时,同一条边会输出两次,这就造成了输出的边比图中实际存在的边多出了一倍。
这是我们不希望看到的。
解决的方法是,出边时,首先判断一下该边是否已经输出过。
判断的方法是:
比较该结点的在图中的位置v1与和此顶点相关联的边的另一顶点在图中位置v2的大小。
如果v1如果v1>v2,则说明边(v1,v2)已经输出了,v1对应的边链表的结点指针后移一位。
重复上述过程,知道所有的边输出且仅输出一次。
这里要说明的是无向简单图中无环无平行边,所以v1不可能等于v2。
下面举例说明一下:
顶点A在图中的位置为0,顶点B在图中的位置为1,顶点C在图中的位置为2,顶点D在图中的位置为3。
输出图时,当输出与A相关联的边时,由于A在图中的位置小于B和D在图中的位置,所以直接输出边(A,B),(A,D);当输出与B点相关联的边时,由于B在图中的位置大于A在图中的位置,所以跳过边(B,A),直接输出边(B,C);后面的同理。
(3)使用重载操作符>>输入图时,首先输入图的顶点数n和边数m,然后是输入顶点,调用insertVertex()函数依次将其插入图的顶点表中。
如果插入不成功,并且图中的顶点数小于最大顶点数,说明此顶点图中已有,输入下一个顶点。
如果插入不成功,并且图中的顶点数已达到最大顶点数,说明图的顶点表已满。
输入边时,只要边中有一个顶点不在图中,或则此边已在图中,则插入失败。
4.2对设计的分析
4.2.1对存储结构的分析
(1)由于邻接表的指针开销较大。
所以对于稀疏图,即边的数目m远远小于顶点数目的平方时,与邻接矩阵相比较,使用邻接表可以获得较高的空间效率。
(2)在时间效率方面,邻接表往往优于邻接矩阵。
因为访问图中某个顶点的所有邻接顶点操作使用最频繁,如果是邻接表,只需检查此顶点对应的边链表,就能很快找到所有与此顶点相邻接的全部顶点;而在邻接矩阵中,必需检查某一行全部矩阵元素。
(3)采用邻接表作为无向图的存储结构时,有一个缺点,就是每条边都被存储了两遍。
这是因为只要(Vi,Vj)出现在顶点i的边链表中(Vj,Vi)就必然出现在顶点j的边链表中。
反之亦然。
实际上他们就是同一条边。
4.2.2对寻找路径的算法的分析。
Find(intv1,intv2,intk,intvisited[],intpath[],intd)
{
intnode,i;
Edge*p;
visited[v1]=1;
//从顶点v1出发,并将visited[v1]标记为1,表示此顶点
//已经被访问过
d++;//d表示走过的路径的条数,从0开始
path[d]=v1;//path[d]表示走过第d条路径时,经过的顶点
if(v1==v2&&d==k)return1;
//从顶点v1出发,走过k条路径到达顶点v2,返回1
p=NodeTable[v1].adj;//顶点v1对应边链表的头指针
while(p!
=NULL)
{
node=p->dest;//此边的另一顶点位置
if(visited[node]==0)//此顶点没有被访问过
{
if((i=Find(node,v2,k,visited,path,d))==1)returni;
//递归寻找
}
p=p->link;//下一条边结点
}
visited[v1]=0;//顶点回溯
d--;//边回溯
return0;
}
设计的该寻找路径的算法实际上是对深度优先遍历和广度优先遍历的一个综合。
先使用深度优先遍历,从顶点v1出发,寻找v1的第一个邻接顶点vj,再寻找vj的第一个邻接顶点,通过递归一直找到顶点v2,每访问一个结点就通过visited[]做上一个标记,并通过path[]记下走过的路径。
如果走过的路径长度不等于k,则顶点和路径都回退一步。
再利用广度优先遍历,知道找到长度为k的简单路径。
找到函数返回1,否则返回0。
5.经验和体会
经过这次课程设计,通过对程序的编制,调试和运行,使我更好的掌握了图的一些基本性质和一些与图有关的问题的解决方法,熟悉了各种调用的数据类型,在调试和运行过程中使我更加的了解和熟悉程序运行的环境,提高了我对程序调试分析的能力和对错误的纠正能力。
我对数据的逻辑结构和物理结构之间的区别和联系有了更深刻的了解。
我们学习数据结构应该经过无数次的练习和总结,来提高自己的动手能力。
踏踏实实地做题和实验,不断提高自己的编程能力和算法设计能力。
我们在设计算法时,要考虑其时间复杂度和空间复杂度,通过不断的比较和分析,来改进和优化算法。
6.运行结果
此无向图的顶点数为6,边数为10
其顶点有:
ABCDEF
其边有:
(A,F)(A,E)(A,D)(A,C)(A,B)(B,D)(B,C)
(C,E)(D,E)(E,F)
顶点A与顶点C之间存在长度为3的简单路径,
路径为:
A-F-E-C
顶点A与顶点C之间存在长度为4的简单路径,
路径为:
A-E-D-B-C
顶点A与顶点C之间不存在长度为7的简单路径
顶点A与顶点D之间存在长度为3的简单路径,
路径为:
A-F-E-C
顶点A与顶点D之间存在长度为5的简单路径,
路径为:
A-F-E-C-B-D
输入您要删除的顶点:
E
删除顶点E后,此无向图的顶点数为5,边数为6
其顶点有:
ABCDF
其边有:
(A,F)(A,D)(A,C)(A,B)(B,D)(B,C)
顶点A与顶点C之间存在长度为3的简单路径,
路径为:
A-D-B-C
请输入您要插入的边:
DF
插入边(D,F)后,此无向图的顶点数为5,边数为7
其顶点有:
ABCDF
其边有:
(A,F)(A,D)(A,C)(A,B)(B,D)(B,C)(D,F)
顶点B与顶点C之间存在长度为4的路径,
路径为:
B-D-F-A-C
7.源程序
#include
#include
constintDefaultSize=20;//默认最大定点数(=n)
template
structEdge//边结点的定义
{
intdest;//边的另一顶点位置
Edge*link;//下一条边链指针
Edge(){}//构造函数
Edge(intnum):
dest(num),link(NULL){}