资源描述
图的遍历动态演示程序
摘要:图是一种复杂的数据结构,具有较高的学习难度。本文讲述了对图的动态演示程序的操作和程序的具体实现过程,使得我们对图的认识更深刻,学习更容易。本软件以Visual Studio 2008作为开发工具,使用邻接表法,用MFC类库实现了对图的可视化创建和图的遍历的动态演示。本文首先讲解了图的遍历动态演示程序的实现框架和设计思路,然后深入讲解了对图中结点和弧的创建、插入和删除,最后着重讲解了图的深度优先遍历和广度优先遍历动态演示的具体实现。
关键词:图; 遍历; 动态演示
The dynamic demonstrative program of traverse graph
Abstract: Graph is a complex data structure, which is hard to learn. This thesis tells people the manipulate of the dynamic demonstrate of traverse graph and the specific realization progress of the program. This study give us a deeper understanding of graph, as well as make it easier to learn it. This software realizes the visual creation of graph and the dynamic demonstration of traverse graph by using adjacent table, MFC library and Visual Studio 2008. This thesis firstly explains the realization of the dynamic demonstrate of traverse graph program, the go into the depth of the creation, insertion, deleting of node and arc, at last explains emphatically the actual realization of the Depth-First traverse of graph and the Breadth-First traverse of graph.
Key Words:graph, traverse, dynamic demonstrative
目 录
1 引言 1
1.1 开发背景 1
1.2 开发的目的以及意义 1
2 需求分析 1
2.1 功能概述 1
2.2 功能需求分析 2
2.2.1 结点的操作 2
2.2.2 弧的操作 2
2.2.3 自动生成图的支持 2
2.2.4 支持图的销毁 3
2.2.5 图的遍历类型 3
2.2.6 图的存储结构 3
2.2.7 图的遍历代码 3
2.2.8 支持图的遍历次序显示和中间辅助队列的进出队情况显示 3
2.2.9 支持对遍历速度的设置 3
2.2.10 支持暂停和单步 3
2.2.11 支持对图的实现代码的查看和运行 4
2.2.12 支持对版本和帮助的显示 4
3 总体设计 4
3.1 程序框架的搭建 4
3.1.1 工程项目的创建 4
3.1.2 窗口的显示 4
3.2 菜单的制作 6
3.2.1 创建图 6
3.2.2 设置演示速度 8
3.2.3 查看源代码的实现 8
3.2.4 运行此程序菜单的实现 9
3.2.5 打开此文件菜单和帮助菜单的实现 10
3.2.5 版本菜单的实现 10
3.2.6 退出菜单功能的实现 10
3.3图的创建和遍历核心算法的设计与实现 10
3.3.1 算法的设计 10
3.3.2 核心算法的实现 16
4 测试与总结 28
谢 辞 29
参 考 文 献 30
30
1 引言
在纷繁复杂的社会生活中,很多东西都涉及到图的应用问题。最早的图的应用可以追溯到18世纪伟大的数学家欧拉用图解决了著名的哥尼斯堡桥问题。目前,图的应用已经渗透到诸如电子线路分析、寻找最短路径、工程计划分析、人工智能、信息检索等领域。而图的遍历是运用图解决问题必须掌握的知识。
1.1 开发背景
社会生活中很多问题都涉及到“图”的知识,这些问题一般都比较复杂,比较难以解决,要解决这些问题,对“图”的学习是必须的。但目前对于“图”知识的讲解用得最多的是ppt演示,而ppt只能演示已经设计好的“图”,灵活性差,而且这些ppt的制作过程都比较繁琐。因此,设计一个能创建动态图,并且可以演示其遍历的软件非常重要。本次毕业设计我用MFC开发一个图的遍历动态演示程序,希望能让初学者能够更好、更容易地掌握图的知识。
1.2 开发的目的以及意义
为了让初学者更轻松地掌握“图”的知识,有必要进行本次毕业设计。本次的毕业设计是将书本上所学的理论知识与实际相结合,是对所学知识的一种检查,是对动手能力的一种锻炼,同时也是从学习走向真正开发的一个过渡,希望通过本次的毕业设计使自己在程序的开发和设计上有新的认识并能有所提高。
2 需求分析
2.1 功能概述
首先是对图类型的选择,图的类型分为有向图和无向图。其次是图的创建和销毁。图的创建包括对图结点和弧的添加、插入和删除,其中添加和插入可以和成一个功能,都是增加数据信息。这些功能的实现还必须在图形界面上显示出来,且用户能够方便地进行操作。最后就是图的遍历。图的遍历分为深度优先遍历和广度优先遍历,选择不同的遍历方式运行时,根据代码的执行步骤及时的在界面上反应遍历到哪个结点和图存储结构上的动态显示。这里代码显示、图存储结构的显示和图的显示要同步起来。对于遍历时速度也需要进行设置。对于整个图的创建、遍历、销毁的代码实现,应该能够显示给用户看,而且还应该可以让用户感受一下该代码的运行效果。
2.2 功能需求分析
2.2.1 结点的操作
结点用圆和圆上的字符来表示,圆上的字符表示结点的名称。当选择创建结点,在绘图区域点击鼠标的时候,在绘图区域绘制一个结点,并且在图的数据结构中添加一个结点,还要保存结点的位置。结点在创建的时候应该保证其名称的唯一性。当选择删除结点,在绘图区点击一个结点的时候,首先要检测鼠标点击位置是否有结点,如果有,则把保存该结点位置的信息以及和该结点相关的弧的位置信息删掉,并且把该结点从图的数据结构中删除,然后重绘一下图。如果没有,则不做任何操作。
2.2.2 弧的操作
根据图类型的不同,弧的表示也不同。如果是有向图,则用一个箭头表示,箭头指向的是弧头,箭头的起始端是弧尾。如果是无向图,则用一条直线表示。当选择创建弧,在画图区域点击时,画一条直线或者一个箭头,并且在图的数据结构中添加一条弧,然后将弧的位置保存一下。弧的创建应该避免重复弧的产生。当选择删除弧,并且在画图区域点击时,先判断鼠标点击的位置是否存在一条弧,如果存在,则删除图数据结构中的选中的弧,并且删除保存的相应弧的位置信息,然后重绘一下图。
2.2.3 自动生成图的支持
有时为了图方便快捷,希望一键创建图,这时应该可以点击一个按钮生成整个图,图的生成就是结点和弧的创建过程,其实现原理和注意事项参照上面的结点的操作和弧的操作中创建结点和创建弧的部分。
2.2.4 支持图的销毁
当图的创建不是很满意的时候,可以点击清空按钮将图的结点位置信息和弧的位置信息清空,并且将图的数据结构销毁,然后清空绘图区域。
2.2.5 图的遍历类型
图的遍历分为深度优先遍历和广度优先遍历,程序对这两种方式都应该支持。选择不同的遍历方式,加载不同的初始化信息。
2.2.6 图的存储结构
图的遍历动态演示就是要动态地展示图是如何遍历其存储结构的。图遍历到哪里了,下一步应该访问哪个结点等都应该能准确地显示给用户。
2.2.7 图的遍历代码
图在遍历时,应该根据代码一步一步地遍历,动态地展示代码运行到哪里了同样可以比较直观地体现图的遍历的整个流程。图的动态显示和图存储结构的动态显示应该根据图的代码执行步骤来动态显示。
2.2.8 支持图的遍历次序显示和中间辅助队列的进出队情况显示
图在遍历过程中,应显示中间辅助队列的进队和出队的情况。图遍历完成后还要显示一下遍历结果,也就是这种遍历方式的遍历次序的显示。
2.2.9 支持对遍历速度的设置
图的遍历速度不能固定,用户可以根据个人喜好来设置图的遍历速度,以便更好地体验图的遍历的整个过程。
2.2.10 支持暂停和单步
当程序在执行遍历的时候,如果想仔细查看当前的各项情况,可以暂停遍历,参看完成之后可以点击运行继续执行,也可以点击单步查看每一步的执行情况。
2.2.11 支持对图的实现代码的查看和运行
只看遍历的那部分代码有时候还不够,需要对图的创建等都有相应的了解,这时可以查看图的整个实现代码,并且运行该代码。
2.2.12 支持对版本和帮助的显示
如果想查看版本和帮助信息,可以点击相应的菜单项查看。
3 总体设计
3.1 程序框架的搭建
3.1.1 工程项目的创建
利用MFC的应用程序向导创建一个名称为GraphShow的对话框工程项目。由于涉及到很多绘图的内容,为了避免多次界面重绘带来的闪屏或者界面卡死等可能性问题,这里保持对话框不能调整大小的默认属性。
3.1.2 窗口的显示
如图1所示,此窗口的主体是向导帮我们生成的。首先我们删掉默认生成的一个Static Text控件和两个Button控件,然后向界面中添加三个Static Text控件、一个ListBox控件、两个Radio Button控件和三个Button控件(各个控件的属性如表1所示)。再者,我们添加一个Menu资源,设置好其中的各项,并把本窗体的Menu属性设置为刚添加的Menu资源的ID。这样,我们就把窗体分为了这样几个区域:图的存储结构显示区域、图的显示区域、代码显示区域、遍历结果显示区域、菜单区域和其它按钮区域。图的存储结构显示区域由一个继承自CStatic类的CDrawLink类来控制,这个类拥有一个ALGraph结构体类型的成员;图的显示区域由CGraphDraw类来控制,这个类也是继承自CStatic;代码显示区域一个CListBox类型的变量关联;遍历结果显示区域由继承自CStatic类的CShowResult类来控制;整个界面由CGraphShowDlg类来管理,它继承自CDialog类,并且拥有一个ALGraph类型的成员。
当按下执行按钮的时候,开启一个线程,这个线程遍历图的同时设置List Box中选中的行、画图中被访问的结点和图存储结构中被访问的结点。基本做到代码选中行的意思和图、存储结构的访问情况相符合。
图1 图的遍历
表格 1图的遍历窗口上各控件属性
控件类型
控件ID
控件Caption
其它
Static Text
IDC_STATIC_LINK
Static Text
IDC_STATIC_GRAPH
List Box
IDC_LIST
Static Text
IDC_STATIC_ORDER
Radio Button
IDC_RADIO_DFS
深度优先遍历
Group属性为True
Radio Button
IDC_RADIO_BFS
广度优先遍历
Button
IDC_BUTTON_RUN
执行
Button
IDC_BUTTON_STEP
单步执行
Button
IDC_BUTTON_PAUSE
暂停
3.2 菜单的制作
向工程中添加菜单资源,其资源ID为IDR_MENU。然后添加“操作”和“关于”菜单项,分别有:创建图、设置演示速度、查看源代码、退出、版本和帮助这几个子菜单项。将此菜单加入到主界面中。如图2所示。
图2 主界面菜单
向工程添加菜单资源,其ID为IDR_MENU_CODE。然后添加“操作”菜单项,其内容分别是:运行此程序、打开此文件和退出。将此菜单加入到查看源代码界面中。如图3所示。
图 3 查看源代码界面菜单
3.2.1 创建图
图4为创建图的界面。要绘制这个对话框,先向工程中添加一个Dialog资源,Dialog资源的ID为IDD_DIALOG_CREATE,Caption为“创建图”。然后向Dialog中添加两个Group Box控件、六个Radio Button控件、一个Static Text控件和三个Button控件,它们的属性设置如表2所示。
这个窗口所有功能的代码实现在CGraphCreate、 CGraphDraw、ALGraph、VNode、ArcNode这几个类或结构体中。其中ArcNode是表示弧的结构体,VNode是表示结点的结构体,ALGraph是表示邻接表法存储图的结构体。CGraphDraw是一个继承自CStatic的类,它控制画图区域,它拥有一个ALGraph结构体类型的成员。CGraphCreate是控制整个界面的类,它拥有CGraphDraw类型的成员。界面中的两组单选按钮分别CGraphCreate类的两个成员变量关联,分别是m_nType和m_nCreate,通过向CGraphDraw类传递这两个值来控制图的类型和许可的操作。
图4 创建图
表格 2创建图对话框上各控件属性
控件类型
控件ID
控件Caption
其它
Group Box
图的类型
Radio Button
IDC_RADIO_DG
有向图
Group属性为True
Radio Button
IDC_RADIO_AG
无向图
Static Text
IDC_STATIC_DRAW
Group Box
Radio Button
IDC_RADIO_CREATENODE
创建结点
Group属性为True
Radio Button
IDC_RADIO_ CREATEARC
创建弧
Radio Button
IDC_RADIO_ DELETENODE
删除结点
Radio Button
IDC_RADIO_DELETEARC
删除弧
Button
IDC_BUTTON_AUTO
自动生成图
Button
IDC_BUTTON_CLEAN
清空
Button
IDC_BUTTON_AUTOCREATE
确定
3.2.2 设置演示速度
首先向工程中添加对话框资源,设置其ID为IDD_DIALOG_SETSPEED,Caption为“设置演示速度”。然后向这个对话框中加入一个Group Box控件、两个Static Text控件、一个Slider控件和一个Button控件。最后设置Group Box的Caption为“设置速度”,两个Static Text的Caption分别为“慢”和“快”,Slider的ID为IDC_SLIDER1,Button的Caption为“确定”、ID为IDC_BUTTON_SETSPEED。结果如图4。把这个对话框和CSetSpeed类关联起来,把Slider控件和CSetSpeed类的一个整型成员变量m_nCur和一个CSliderCtrl类型的成员变量m_ctrlSpeed关联起来。在初始化对话框的时候,通过调用m_ctrlSpeed.SetRange(int,int)来设置滚动条的范围,调用m_ctrl.SetPos(int)来设置滚动条的位置。当用鼠标拖动滚动条的时候会产生WM_HSCROLL消息。在这个消息的处理里面获得滚动条的位置,给m_nCur赋值,这个值穿给主对话框对应的类后,经过一定的计算就可以控制演示的速度了。
图5 演示速度设置
3.2.3 查看源代码的实现
首先向工程中添加一个对话框资源,其ID为IDD_DIALOG_RESOURCE,Caption为“源程序”。然后在对话框中添加一个List Box控件,设置其ID为IDC_LIST_RESOURCE,Horizontal Scroll设置为True。并且把ID为IDR_MENU_CODE的菜单添加到对话框的中。把List Box和一个CListBox类型的对象m_cltbResoure关联起来。在对话框初始化的时候读取code.c这个文件,将其中的内容加入到List Box控件中。
图6 源代码查看
3.2.4 运行此程序菜单的实现
点击此菜单,调用CreateProcess函数创建一个子进程,这个子进程打开一个控制台窗口,该控制台可以实现对结点和弧的添加、插入和删除操作,以及图的深度优先遍历和广度优先遍历。如图7所示。
图7 源代码执行
3.2.5 打开此文件菜单和帮助菜单的实现
点击菜单的时候,调用ShellExecuteEx让电脑上的软件打开相应的文件。
3.2.5 版本菜单的实现
由于使用应用程序向导创建MFC Dialog类型的应用程序的时候,默认会插入一个ID为IDD_ABOUTBOX的对话框。该对话框关联的类为CAboutDlg,我们在该对话框内添加几个Static Text来实现对版本信息的显示。
3.2.6 退出菜单功能的实现
当点击退出菜单的时候,调用EndDialog关闭对话框,进而关闭整个程序。
3.3图的创建和遍历核心算法的设计与实现
3.3.1 算法的设计
图的存储,可以用邻接表法表示图。
邻接表是图的一种链式存储结构。在邻接表中,对图中每个顶点简历一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对有向图是以顶点vi为尾的弧)。每个结点由3个域组成,其中邻接点域指示与顶点vi邻接的点在图中的位置,链域指示下一条边或弧的结点;数据域存储和边或弧相关的信息,如权值等。每个链表上附设一个表头结点。在表头结点中,除了设有链域指向链表中第一个结点之外,还设有存储顶点vi的名或其他有关信息的数据域。如下图所示
表结点 头结点
data firstarc
adjvex nextarc info
这些表头结点(可以链相接)通常以顺序结构的形式存储,以便随机访问任一顶点的链表。
图的遍历是从图中某一顶点触发访遍图中其余顶点,且使每一个顶点仅被访问一次。通常有两条遍历图的路径:深度优先遍历和广度优先遍历。
图的深度优先遍历类似于书的先根遍历,是树的先根遍历的推广。假设初始化状态是图中所有顶点未曾被访问,则深度优先遍历可从图中某个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
图的广度优先遍历类似于树的按层次遍历的过程。假设从图中顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先遍历的过程是以v为起始点,由近至远,依次访问和v有路径相通且路径长度为1,2,…的顶点。
由于本次毕业设计需要动态的演示图的遍历过程,这涉及到绘图的问题。因此,在图的结点和弧创建的时候都需要记录他们的位置,并且设置他们的访问标志为未被访问。当图中的结点和弧被删除的时候,需要删除对应的结点和弧,并且把保存的对应的位置和访问信息重置。当图中的结点和弧被访问的时候要设置他们的访问标志为已经被访问。
在创建节点的时候,其算法流程如图8所示。
是
否
是
否
结点数是否已达到最大限度
鼠标点击
创建结点按钮是否被选中
创建结点
保存该结点并设置其未被访问
在鼠标点击位置画结点
结束
图 8 创建结点
当选择删除结点时,其具体的处理流程路图9所示。
是
鼠标点击
结束
是否选择了删除结点按钮
鼠标点击是否命中结点
删除保存的结点信息、删除和该结点相连接的弧的位置信息、删除命中结点,更新保存的弧的位置信息、擦除界面上结点和相关弧
否
否
是
图 9 结点的删除
在判断鼠标点击是否命中结点的时候,需要遍历所有结点,求出每一个结点的圆心到鼠标点击位置的长度,其具体的计算公式为:
长度L = sqrt(pow(y2 – y1, 2) + pow(x2 – x1, 2))
如果长度小于结点圆的半径,则为命中了该节点。
结束
设置节点未被选中
调换弧头和弧尾的位置
创建弧,保存弧的位置,并且设置其为未访问然后画弧
创建弧,保存弧的位置,并且设置其为未访问然后画弧
创建弧,保存弧的位置,并且设置其为未访问然后画弧
无向图
有向图
否
是
即将要创建的弧是否已经存在
设置该结点为选中
否
是
是否有结点上次选定了
是
否
否
是
鼠标点击
创建弧按钮是否被选中
鼠标点击是否命中了结点
在创建弧的时候,具体的操作流程如图10所示。
图 10 创建弧
创建弧时,为了画得好看,并不是从两个节点的圆心画一条线,而是从两圆心的连线与圆周的交点处画线,这两个坐标的具体求解方法如图11.
图 11 求两结点圆心连线与圆周交点辅助图
假设两个圆的圆心坐标为A(x1,y1)、B(x2,y2),则AB直线的长度为:|AB|=sqrt(pow(x2-x1,2)+pow(y2-y1,2))利用相似三角形求解,可知(y2-y1)/y=|AB|/R,求解得出y=abs(y2-y1)*R/sqrt(pow(x2-x1,2)+pow(y2-y1,2)),同理可以得出x=abs(x2-x1)*R/sqrt(pow(x2-x1,2)+pow(y2-y1,2))。则与两圆周交点的坐标为:
(x2>x1?x1+x:x1-x,y2>y1?y1+y:y1-y)
(x1>x2?x2+x:x2-x,y1>y2?y2+y:y2-y)
然后以这两个交点为起始点画直线或者画箭头即可。至于画箭头的数学推导,这里就不详细说明,只是在算法实现里面给出实现代码。
删除弧的处理流程如图11所示。
是
否
是
否
鼠标点击
是否选择了删除弧单选按钮
鼠标点击时是否命中弧
删除该弧的位置信息、删除弧、擦除弧
结束
图 11 删除弧
其中,删除弧的时候要判断鼠标点击时是否命中弧,其具体的算法为:遍历每一条弧,根据弧头和弧尾的坐标,进行如下三种情况下的判定:
当弧的两个结点位置的x坐标相差不到2个像素的时候,可以规划一个矩形区域,鼠标点击在这个矩形区域即为命中。这个矩形区域的left值为两个结点中任意一个结点的x值减3,right值为该任意结点的x值加3,top值为结点中较小的那个y值,bottom为结点中较大的那个y值。
当弧的两个结点位置的y坐标相差不到2个像素的时候,可以规划一个矩形区域,鼠标点击在这个矩形区域即为命中。这个矩形区域的top值为两个结点中任意一个结点的y值减3,bottom值为该任意结点的y值加3,left值为结点中较小的那个x值,right为结点中较大的那个x值。
排除上面两种情况后,应该这么判定弧是否被选中:首先根据弧的弧头和弧尾的坐标写出这两点之前的直线方程y = (y2 – y1)*(x – x1)/(x2 – x1)。把鼠标点击时位置的x坐标值代入到该方程中,如果得到的y值和鼠标点击时位置的y值差距在3个像素以内,并且鼠标点击的位置在弧头和弧尾组成的矩形区域内,则为命中弧。
3.3.2 核心算法的实现
图的遍历动态演示程序主要分为图的创建和图的遍历。
用邻接表法实现图,数据结构为:
#define MAX_VERTEX_NUM 26
typedef enum{DG,AG,DN,AN} GraphKind;
typedef struct ArcNode
{
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条弧的指针
InfoType *info;//该弧相关信息的指针
}ArcNode;
typedef struct VNode
{
VertexType data;//顶点信息
ArcNode *firstarc;//指向第一条依附该顶点的弧的指针
}VNode;
typedef struct ALGraph
{
vector<VNode> vertices;
int vexnum,arcnum;// 图的当前顶点数和弧数
int kind;//图的种类标志
}ALGraph;
创建结点的具体代码实现为:
void ALGraph::CreateNode()
{
VNode node;
if (vertices.size() == 0)//图中没有结点,则将结点名字标记为A。
{
node.data = "A";
}
else//图中有结点则将名字标记为上个结点的名字的ascii码加1得到的字符
{
node.data = vertices.back().data.at(0);
node.data.at(0)++;
}
vertices.push_back(node);//将结点加入到图中
}
创建弧的具体代码实现为:
void ALGraph::CreateArc(int BegPos, int EndPos)
{
ArcNode *arc = NULL;
arc = new ArcNode();//新建一条弧
arc->adjvex = EndPos;//设置该弧指向EndPo位置的结点
arc->info = NULL;//不为其设置权值。
//将本弧插入到位序为BegPos结点链表的前面
arc->nextarc = vertices.at(BegPos).firstarc; vertices.at(BegPos).firstarc = arc;
}
删除结点的具体代码实现为:
void ALGraph::DeleteNode(VertexType v)
{
int i, j;
ArcNode *p = NULL, *q = NULL;
j = LocateVex(v);//获取v结点的位序
if (j < 0)
{
return;
}
p = vertices.at(j).firstarc;
while (p)//遍历v结点的出度链表,删除链接的弧
{
q = p;
p = p->nextarc;
if (kind / 2)
{
free(q->info);
}
free(q);
arcnum--;
}
vexnum--;//结点数减少1
for (i = j; i < vexnum; i++)//把要删除结点之后的数据往前移动
{
vertices.at(i) = vertices.at(i + 1);
}
vertices.erase(vertices.end() - 1);//移动后删除最后面的元素
//遍历整个图,删除和v结点有入度关系的弧。
for (i = 0; i < vexnum; i++)
{
p = vertices.at(i).firstarc;
while (p)
{
if (p->adjvex == j)
{
if (p == vertices.at(i).firstarc)
{
vertices.at(i).firstarc = p->nextarc;
if (kind / 2)
{
free(p->info);
}
free(p);
arcnum--;
p = vertices.at(i).firstarc;
}
else
{
q->nextarc = p->nextarc;
if (kind / 2)
{
free(p->info);
}
free(p);
arcnum--;
p = q->nextarc;
}
}
else
{
if (p->adjvex > j)
{
p->adjvex--;
}
q = p;
p = p->nextarc;
}
}
}
}
删除弧的具体代码实现为:
Status ALGraph::DeleteArc(VertexType v, VertexType w)
{
ArcNode *p = NULL, *q = NULL;
int i, j;
i = LocateVex(v); /* i是顶点v(弧尾)的序号*/
j = LocateVex(w); /* j是顶点w(弧头)的序号*/
if(i < 0 || j < 0 || i == j)
return ERROR;
p = vertices[i].firstarc; /* p指向顶点v的第一条出弧*/
while(p && p->adjvex != j) /* p不空且所指之弧不是待删除弧<v,w> */
{ /* p指向下一条弧*/
q = p;
p = p->nextarc;
}
if(p && p->adjvex == j) /* 找到弧<v,w> */
{
if(p == vertices[i].firstarc) /* p所指是第条弧*/
vertices[i].firstarc = p->nextarc; /* 指向下一条弧*/
else
q->nextarc = p->nextarc; /* 指向下一条弧*/
if(kind / 2) /* 网*/
free(p->info);
arcnum--; /* 弧或边数减*/
}
return OK;
}
图广度优先遍历动态演示的代码实现:
void CGraphShowDlg::BFSTraverse(void)
{
int NodeRow, NodeCol, ArrowRow, ArrowCol;
visited.clear();//清空记录被访问情况的队列
m_listbox.SetCurSel(4);
Sleep(m_nTime);
m_listbox.SetCurSel(5);
Sleep(m_nTime);
//设置每个结点都没有被访问
for (int i = 0; i != g.vexnum; ++i)
{
visited.push_back(FALSE);
m_listbox.SetCurSel(6);
Sleep(m_nTime);
m_listbox.SetCurSel(5);
Sleep(m_nTime);
}
m_listbox.SetCurSel(7);
Sleep(m_nTime);
deque<int> Q;
m_listbox.SetCurSel(8);
Sleep(m_nTime);
ArcNode *arc = NULL;
//遍历每一个结点组成的链表
for (int v = 0; v < g.vexnum; ++v)
{
NodeRow = 0;
NodeCol = 0;
ArrowRow = 0;
ArrowCol = 0;
m_listbox.SetCurSel(9);
Sleep(m_nTime);
if (!visited.at(v))//结点没有被访问
{
m_listbox.SetCurSel(11);
Sleep(m_nTime);
visited.at(v) = TRUE;
NodeRow = v;
ArrowRow = v;
m_cDraw.ScanNode(v);//设置图中结点被访问并画图显示
m_listbox.SetCurSel(12);
m_cLink.ScanNode(NodeRow, NodeCol);//设置图存储结构的结点被访问并画图显示
m_ctrlShowResult.PushNode(g.vertices.at(v).data);//输出结点被遍历
Sleep(m_nTime);
m_listbox.SetCurSel(13);
Q.push_back(v);//结点入队列
m_ctrlShowResult.PushData(g.vertices.at(v).data);//输出显示队列的信息
Sleep(m_nTime);
m_listbox.SetCurSel(14);
Sleep(m_nTime);
while (!Q.empty())//队列不为空
{
NodeRow = Q.front();
ArrowRow = NodeRow;
NodeCol = 0;
ArrowCol = 0;
m_cLink.ScanNode(NodeRow, NodeCol++);
m_listbox.SetCurSel(16);
Sleep(m_nTime);
Q.pop_front();
m_ctrlShowResult.PopHead();
m_listbox.SetCurSel(17);
Sleep(m_nTime);
m_listbox.SetCurSel(18);
Sleep(m_nTime);
arc = g.vertices.at(NodeRow).firstarc;
//遍历一个结点的所有出度
while (arc != NULL)
{
m_listbox.SetCurSel(19);
Sleep(
展开阅读全文