数据结构实验报告无向图邻接矩阵存储结构
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
数学与计算机学院
课程设计说明书
课程名称: 数据结构与算法课程设计
课程代码: 6014389
题目: 无向图的邻接矩阵存储结构
年级/专业/班: 2018级软件4班
学生姓名: 吴超
学号: 312018*********
开始时间: 2018年12月9日
完成时间: 2018年12月30日
课程设计成绩:
学习态度及平时技术水平与实际创新<5)说明书<计算书、图纸、分析总分
指导教师签名:年月日
数据结构课程设计任务书
学院名称:数学与计算机学院课程代码:__6014389______
专业:软件工程年级: 2018
一、设计题目
无向图的邻接矩阵存储结构
二、主要内容
图是无向带权图,对下列各题,要求写一算法实现。
1)能从键盘上输入各条边和边上的权值;
2)构造图的邻接矩阵和顶点集。
3)输出图的各顶点和邻接矩阵
4)插入一条边
5)删除一条边
6)求出各顶点的度
7)判断该图是否是连通图,若是,返回1;否则返回0.
8)使用深度遍历算法,输出遍历序列。
三、具体要求及应提交的材料
用C/C++语言编程实现上述内容,对每个问题写出一个算法实现,并按数学与计算机学院对课程设计说明书规范化要求,写出课程设计说明书,并提交下列材料:
1>课程设计说明书打印稿一份
2>课程设计说明书电子稿一份;
3>源程序电子文档一份。
四、主要技术路线提示
用一维数组存放图的顶点信息,二维数组存放各边信息。
五、进度安排
按教案计划规定,数据结构课程设计为2周,其进度及时间大致分配如下:
[1] 严蔚敏,吴伟民.数据结构.清华大学出版社出版。
[2] 严蔚敏,吴伟民. 数据结构题集(C语言版> .清华大学出版社.2003年5月。
[3]唐策善,李龙澎.数据结构(作C语言描述> .高等教育出版社.2001年9月
[4] 朱战立.数据结构(C++语言描述><第二版本).高等出版社出版.2004年4月
[5]胡学钢.数据结构(C语言版> .高等教育出版社.2004年8月
指导教师签名日期年月日
系主任审核日期年月日
目录
摘要
随着计算机的普及,涉及计算机相关的科目也越来越普遍,其中数据结构是计算机专业重要的专业基础课程与核心课程之一,为适应我国计算机科学技术的发展和应用,学好数据结构非常必要,然而要掌握数据结构的知识非常难,所以对“数据结构”的课程设计比不可少。
本说明书是对“无向图的邻接矩阵存储结构”课程设计的说明。
首先是对需求分析的简要阐述,说明系统要完成的任务和相应的分析,并给出测试数据。
其次是概要设计,说明所有抽象数据类型的定义、主程序的流程以及各程序模块之间的层次关系,以及ADT描述。
然后是详细设计,描述实现概要设计中定义的基本功操作和所有数据类型,以及函数的功能及代码实现。
再次是对系统的调试分析说明,以及遇到的问题和解决问题的方法。
然后是用户使用说明书的阐述,然后是测试的数据和结果的分析,最后是对本次课程设计的结论。
关键词:网络化;计算机;对策;图;储存。
引言
数据结构是计算机存储、组织数据的方式。
数据结构是指相互之间存在一种或多种特定关系的的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储。
数据结构往往同高效的检索算法和技术有关。
选择了数据结构,算法也随之确定,是数据而不是算法是系统构造的关键因素。
这种洞见导致了许多种方法和的出现,语言就是其中之一。
此次课程设计根据课堂讲授内容,下发任务书,要求学生完成相应系统,以消化课堂所讲解的内容;通过调试典型例题或习题积累调试C++程序从而获得数据结构的编程经验;通过完成此项课程设计,逐渐培养学生的编程能力、用计算机解决实际问题的能力,并充分理解图的矩阵储存方法。
此次课程设计题目为《无向图的邻接矩阵存储结构》,所利用工具为 Microsoft visual studio 2008.
1需求分析
随着计算机的普及,信息的存储逐渐和我们的日常生活变得密切起来,而数据的存储方式也多种多样,比如树、链表、数组、图等等。
为了充分体现图的矩阵储存结构的优势与功能,要求本系统应达到以下要求:
1.图是无向带权图
2.能从键盘上输入各条边和边上的权值;
3.构造图的邻接矩阵和顶点集。
4.输出图的各顶点和邻接矩阵
5.插入一条边
6.删除一条边
7.求出各顶点的度
8.判断该图是否是连通图,若是,返回1;否则返回0.
9.使用深度遍历算法,输出遍历序列。
1.1任务与分析
邻接矩阵是表示图形中顶点之间相邻关系的矩阵。
设G=(V,E>是具有n个顶点的图,则G 的邻接矩阵是n阶方阵。
为了实现此算法,用一维数组a[]存放图的顶点信息,二维数组b[][]存放各边信息。
顶点或者边存在,则该数组应有值,通过此来进行建立、插入、删除。
其余方法也将能通过数组的特性来实现。
1.2测试数据
1.建立图的矩阵存储结构,第一次建立连通图A1,第二次建立非连通图A2。
如下:
图1.1测试数据
2.选择输出图
3.选择插入节点,插入4
4.选择插入边,在3,4节点中插入边,权值为55
5.选择深度优先搜索
6.选择判断是否连通
7.选择求最小生成树
8.选择求各顶点的度
9.选择退出,重新运行,此次建立A2的图,再次进行测试。
2 概要设计
2.1 ADT描述
ADT Glist
{
{VR}={图的顶点和边}
VR={<v,w> | v,w∈V, <v,w>表示顶点v和w间的边;} 基本操作:
初始化空图;
输入建立图;
深度优先遍历图;
确定图中的顶点数目;
确定图中边的数目;
在图中插入一个顶点;
在图中插入一条边;
删除图中一个顶点
删除图中的一条边;
求顶点的度;
求最小生成树;
} ADT Graph。
2.2程序模块结构
图2.1:模块结构
2.2.1 结构体定义
本系统未采用结构体方法,类的定义如下:
定义顶点: nodecount,edgecount 边:已经分别存放顶点和边的两个数组: a[MaxNode]和b[MaxNode][MaxNode]。
其余成员函数均以public形式声明。
在邻接矩阵表示的图中,顶点信息用一维数组表示a[]。
在简单情况下可省略,仅以下标值代表顶点序号。
若需要,顶点信息更加丰富。
边<或弧)信息用二维数组表示b[ ][ ],这也是邻接矩阵。
包含边的权值。
在类中数据成员有4个,重要的是邻接矩阵Edge[ ][ ]、总边数edgecount和顶点数nodecount。
class Graph1
{
private:
int nodecount。
//节点
int edgecount。
//边
int a[MaxNode]。
//顶点信息组
//set<int> a。
int b[MaxNode][MaxNode]。
//权值信息组
public:
Graph1(int>。
//构造函数
int getNodeCount(>。
//当前的节点数
int getEdgeCount(>。
//当前的边数
void insertNode(int>。
//插入一个节点
void isertEdge(int ,int ,int>。
//插入一条边
void deleteEdge(int,int>。
//删除一条边
bool isliantong(>。
//判断是否连通
int getWeight(int,int>。
//获得某条边的权值
int Depth(int >。
//深度遍历准备,用于建立顶点访问数组和记录所访问顶点个数
void Depth(int v,int visited[],int &n>。
//深度遍历
void outDu(Graph1 G>。
//输出节点个数
void PrintOut(Graph1 G> 。
//输出图
void CreatG(int n,int e>。
//建立图
}。
2.3各功能模块
以下将以注释形式为每个函数的功能进行声明:
构造函数:Graph1(int> 用于初始化图
get函数:int getNodeCount(>。
得到当前的节点数
get函数:int getWeight(int,int>。
获得某条边的权值
get函数:int getEdgeCount(>。
得到当前的边数
插入函数:void insertNode(int>。
插入一个节点
插入函数:void isertEdge(int ,int ,int>。
插入一条边
删除函数:void deleteEdge(int,int>。
删除一条边
判断函数:bool isliantong(>。
判断是否连通
遍历函数1:int Depth(int >。
//深度遍历准备,用于建立顶点访问数组和记录所访问顶点个数
遍历函数2:void Depth(int v,int visited[],int &n>。
//深度遍历
求度函数:void outDu(Graph1 G>。
输出节点个数
输出函数:void PrintOut(Graph1 G> 。
输出图
构建函数:void CreatG(int n,int e>。
建立图
3 详细设计
3.1类的定义
class Graph1
{
private:
int nodecount。
//节点
int edgecount。
//边
int a[MaxNode]。
//顶点信息组
//set<int> a。
int b[MaxNode][MaxNode]。
//权值信息组
public:
Graph1(int>。
//构造函数
int getNodeCount(>。
//当前的节点数
int getEdgeCount(>。
//当前的边数
void insertNode(int>。
//插入一个节点
void isertEdge(int ,int ,int>。
//插入一条边
void deleteEdge(int,int>。
//删除一条边
void prim(int>。
//生成最小树
bool isliantong(>。
//判断是否连通
int getWeight(int,int>。
//获得某条边的权值
int Depth(int >。
//深度遍历准备,用于建立顶点访问数组和记录所访问顶点个数
void Depth(int v,int visited[],int &n>。
//深度遍历
void outDu(Graph1 G>。
//输出节点个数
void PrintOut(Graph1 G> 。
//输出图
void CreatG(int n,int e>。
//建立图
}。
3.2 初始化
初始化邻接矩阵以及有关参数,通过for循环将数组的值都初始化为0,使之成为一个空图。
Graph1::Graph1(int s=MaxNode>//构造函数
{
for(int i=0。
i<=s-1。
i++>
for(int j=0。
j<=s-1。
j++>
b[i][j]=0。
nodecount=0。
for(int k=0。
k<=s-1。
k++>
a[k]=-1。
}
3.3 图的构建操作
在主函数中要求输入需要构建的图的顶点数和边数,调用构建函数,分别用两个for语句来构建图<即输入顶点的值和边的权值),此处要求输入有一定的顺序,不能随意输入。
void Graph1::CreatG(int n,int e>
{ int i,vi,vj,w。
edgecount=e。
nodecount=n。
cout<<endl<<" 输入顶点的信息<暂设为整型):" 。
for ( i=0。
i<nodecount。
i++ >
{ cout<<"\n "<<i+1<<": "。
cin>>a[i]。
}
for ( i=0。
i<edgecount。
i++ > //输入两个顶点编号和边权值
{ cout<<endl<<" 输入边的信息<vi,vj,w):"<<endl。
cin>>vi>>vj>>w。
b[vi-1][vj-1]=w。
b[vj-1][vi-1]=w。
}
3.4 输出操作
本函数通过传过来的对象G得到相关数组,通过for循环来分别输出顶点数组和边的权值数组<邻接矩阵)。
void Graph1::PrintOut(Graph1 G>
{ int i。
cout<<"\n 输出顶点的信息:"<<endl。
for ( i=0。
i<G.getNodeCount(>。
i++ > cout<<G.a[i]<<" "。
cout<<endl<<"\n 输出邻接矩阵:" 。
for ( i=0。
i<G.getNodeCount(>。
i++ >
{ cout<<endl<<i+1<<": "。
for ( int j=0。
j<G.getNodeCount(> 。
j++ > cout<<G.b[i][j]<<" "。
cout<<endl。
}
}
3.5 get操作
用于得到图的顶点数、边数、权值
int Graph1::getNodeCount(>//当前的节点数
{
return nodecount。
}
int Graph1::getEdgeCount(>//当前的边数
{
return edgecount。
}
int Graph1::getWeight(int x,int y>//获得某条边的权值
{
return b[x-1][y-1]。
}
3.6 插入操作
插入顶点:通过将传来的值<顶点值)赋到一个当前顶点数下一个的数组元素中实现插入功能,此时顶点树加1。
插入边:原理与插入顶点相同,通过传过来的两个顶点信息与权值进行相应赋值,不过此时应该注意表示同一条边的两个数组都该赋值。
void Graph1::insertNode(int it>//插入一个节点
a[nodecount++]=it。
cout<<"当前节点数为:"<<nodecount<<endl。
}
void Graph1::isertEdge(int x,int y,int w>//插入一条边
{
b[x-1][y-1]=w。
b[y-1][x-1]=w。
cout<<"该边插入成功! "<<endl。
edgecount++。
}
3.7 删除操作
将相应的数组值赋为0从而达到删除目的。
void Graph1::deleteEdge(int x ,int y>//删除一条边
{
b[x-1][y-1]=0。
b[y-1][x-1]=0。
cout<<"边("<<x<<","<<y<<">已经成功删除!"。
edgecount--。
}
3.8 求顶点的度操作
通过两个for循环来查找各顶点,判断其是否有边<权值),有边的话又有几条边,每找到一条边则度数加一,记录到存放顶点的度数的数组M[]中,搜索完成后输出各顶点的度。
void Graph1::outDu(Graph1 G>//输出各点的度
{
int m[Max]={0},k,i。
for(k=0。
k<G.getNodeCount(>。
k++>
for(i=0。
i<G.getNodeCount(>。
i++>
{
if(G.b[k][i]!=0>
m[k]++。
}
cout<<"各点的度:"<<endl。
for(i=0。
i<G.getNodeCount(>。
i++>
{
cout<<"顶点"<<i<<"的度:"<<m[i]<<endl。
}
}
3.9 深度遍历操作
图的深度优先遍历DFS算法是沿着某初始顶点出发的一条路径,尽可能深入地前进,即每次在访问完当前顶点后,首先访问当前顶点的一个未被访问过的邻接顶点,然后去访问这个邻接点的一个未被访问过的邻接点,这是一个递归算法。
其中第一个遍历函数Depth(int node>其主要作用是构建访问数组intv[],以及调用核心遍历函数Depth(int v,int visited[],int &n>,其中的n是用来记录访问的顶点数目,用于判断图的连通性。
int Graph1::Depth(int node>
{
int n=0。
int v[MaxNode]。
for(int i=0。
i<=nodecount-1。
i++>
v[i]=0。
Depth(node,v,n>。
return n。
//n记录访问节点数,用于判断是否连通
}
void Graph1::Depth(int v,int visited[],int &n>
{ cout<<" "<<v+1<<" " 。
//访问顶点v
visited[v]=1。
//标记顶点v已访问
n++。
//n记录访问节点数
for (int col=0。
col<nodecount。
col++>
{ if (b[v][col]==0||b[v][col]==Max>
continue。
//找v一个邻接点col
if (!visited[col]> Depth(col, visited,n>。
//调用深度递归遍历
}
}
3.10 判断连通操作
通过调用遍历函数得到所能访问的顶点数n,然后判断n与当前顶点数是否相等来确认是否连通。
bool Graph1::isliantong(>//判断是否连通
{
int n=0。
n=Depth(0>。
cout<<"该图的总节点数为:"<<nodecount<<"!"<<endl。
cout<<"其中一个连通分量连通的节点数为:"<<n<<"!"<<endl。
if(n==nodecount>//访问到的节点数与顶点数是否相等
return 1。
else return 0。
}
3.11 主函数
主函数的主要功能是引导构建新图的邻接矩阵存储结构,并且制作菜单、调用各个相应的功能函数。
int main(>{
Graph1 G(10>。
int x,y,w。
int node。
cout<<"你好,请问你向图添加几个节点?几条边?请依次从键盘输入!"<<endl。
int n。
cin>>n。
int e。
cin>>e。
G.CreatG(n,e>。
cout<<"恭喜你!你的图已经建立成功!"<<endl。
//system("pause">。
while(true>
{
//system("cls">。
cout<<"***********************"<<endl。
cout<<"*请选择你要进行的操作:*"<<endl。
cout<<"*1--输出图! *"<<endl。
cout<<"*2--判断图是否连通! *"<<endl。
cout<<"*3--表示深度优先搜索! *"<<endl。
cout<<"*4--表示求各顶点的度! *"<<endl。
cout<<"*5--表示插入新节点!*"<<endl。
cout<<"*6--表示删除边! *"<<endl。
cout<<"*7--求边的权值 *"<<endl。
cout<<"*8--插入新的边! *"<<endl。
cout<<"*0--表示结束操作! *"<<endl。
cout<<"***********************"<<endl。
int choice。
cout<<endl。
cout<<"请你做出选择!"<<endl。
cin>>choice。
switch(choice>
{
case 1:
G.PrintOut(G>。
break。
case 2:
if(G.isliantong(>>
cout<<"该图是连通的!"<<endl。
else cout<<"该图不是连通的!"<<endl。
break。
case 3:
int node。
cout<<"请输入你选择的起始节点!"<<endl。
cin>>node。
cout<<"深度优先搜索结果为:"<<endl。
G.Depth(node-1>。
cout<<endl。
break。
case 4:
G.outDu(G>。
break。
case 5:
cout<<"请输入你要插入的新节点!"<<endl。
cin>>node。
G.insertNode(node>。
cout<<endl。
break。
case 6:
cout<<"请输入你要删除的边!"<<endl。
cin>>x>>y。
G.deleteEdge(x,y>。
cout<<endl。
break。
case 7:
cout<<"请输入你要查询的边的两个顶点"<<endl。
cin>>x>>y。
cout<<G.getWeight(x,y><<endl。
break。
case 8:
cout<<"请输入你要添加的"<<e<<"条边以及边上对应的权值!"<<endl。
cin>>x>>y>>w。
G.isertEdge(x,y,w>。
break。
case 0:
cout<<"感谢使用!"<<endl。
return 0。
}
//system("pause">。
}4 调试分析
4.1 测试数据
测试数据详见图1
4.2调试问题
具体功能方面,在遍历函数中,由于访问节点数组visit[]构建问题,无法达到遍历目的,后新增另一遍历功能函数,用于构建visit[],问题才得以解决,而于使用了清屏system("cls">和暂停system("pause">功能,在测试时一度出现暂停次数过多的问题,通过在判断结构中加入break后解决,在判断是否连通功能上,由于判断问题迟迟未能下手,后在遍历函数中加入了一个记录访问节点数的N,从而解决问题。
4.3 算法时间复杂度
图的创建:时间复杂度为O(n>。
深度遍历:时间复杂度为O(n>。
插入顶点和边:当插入的顶点和边为x,y时,若x>=y时间复杂度为O(x>反之为O(y>。
删除顶点和边:当删除的顶点和边为x,y时,若x>=y时间复杂度为O(x>反之为O(y>。
4.4 经验和心得体会
书上提供的定义结构体的方法很好,想不COPY书上的方法,而是改为定义一个结构体,但是重新想想,如果作为一个编程者,并不是一定要全靠自己想方法,如果是有更简便的方法当然好,但是明知道自己的方法更复杂,却还要一条道走到黑,这样太不明智了,我要做的,就是学会如何使用老师的简便方法,并使用于以后的学习中;而在自己编写过程中,虽然经常编译一次性通过,但是在接下来的测试中,基本上每解决一个功能性问题但是却马上接着出现一个新的问题;一次又一次的更改确实严重打击了我的信心和耐心,最后还是不得不承认自己的能力太弱,所以一个简单的函数都解决不了,希望自己在以后的学习中能有质的飞跃!
5 用户使用说明
本系统是关于图的矩阵存储系统,管理员和游客,要求首先创建一个图之后才能进行后续的操作,并且在输入过程中务必规范输入。
6测试结果
6.1 创建图
图6.1图的创建
6.2插入节点
图6.2节点的插入6.3 深度优先遍历
图6.3深度优先遍历6.4 求各顶点的度
图6.4定点的度6.5 输出图
图6.5图的输出6.6 判断是否连通
图6.6判断是否连通6.7 求边的权值
图6.7边的权值6.8 插入边
个人资料整理仅限学习使用
图6.8边的插入
6.9 删除边
图6.9边的删除
结论
本次课程设计“无向图的邻接矩阵存储结构”按照任务书相应的要求成功的完成了任务,由于本课程设计涉及任务明确,采用图的储存结构和算法比较方便处理数据的储存、查询、删除等操作。
但图的操作比较难,容易出错并且不易改动。