实验四:图的实现与应用
20162317袁逸灏 实验四:图的实现与应用
实验内容
用
- 邻接矩阵
- 十字链表
实现无向图中的
- 添加结点
- 删除结点
- 添加边
- 删除边
- size()
- isEmpty()
- 广度优先迭代器
- 深度优先迭代器
方法
实验要求
- 实验1:用邻接矩阵实现无向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器
给出伪代码,产品代码,测试代码(不少于5条测试)
上方提交代码链接
附件提交测试截图
- 实验2:用十字链表实现无向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器
给出伪代码,产品代码,测试代码(不少于5条测试)
上方提交代码链接
附件提交测试截图
- 实验3:实现PP19.9
给出伪代码,产品代码,测试代码(不少于5条测试)
上方提交代码链接
附件提交测试截图
实验过程
- 实验1
邻接矩阵是存储图的一种方式,它通过一个矩阵来表示哪些点之间有联系。主要有两种表达方式。
方式1:用0和1来表示
在矩阵种,1代表相连接,0代表不相连。
方式2:用权值来表示
当两点相连的时候,矩阵的位置为权值;若两点不为邻接点,矩阵的位置为无穷大。
邻接矩阵的特点:
- 无向图的邻接矩阵对称,可压缩存储;
- 无向图中顶点 vi 的度是邻接矩阵中第 i 行 1 的个数。
- 有向图邻接矩阵不一定对称;
- 有向图中顶点 vi 的出度是邻接矩阵中第 i 行 1 的个数。 顶点 vi 的入度是邻接矩阵中第 i 列 1 的个数。
邻接矩阵的实现:
- 初步实现
- 需要一个边的类,类中要有三个变量,两个是顶点变量,一个是边的权重变量。
- 要实现保存点的功能,鉴于后面有添加点的方法,因此最好实例化一个List来保存点,这样可以使长度不受限制。
- 实现保存边的功能,鉴于该实验的要求是使用邻接矩阵来实现图的储存。因此,在保存边这一方面,需要用一个二维数组来保存,该数组大小为顶点集合的大小。
- 考虑到后面可能会使用到权值,因此需要一个大小和边的大小相同的二维数组来保存权值。
- 方法实现
- 添加顶点---addVex(T vex)
考虑到List可以动态扩容,而数组不可以。因此要考虑到将数组扩容的办法。所以需要两个列表,一个用来暂时承载保存边的二维数组的数据,一个暂时承载保存权值的二维数组的数据。当添加顶点的时候,先将二维数组中的数据放到对应的点中,通过重新实例化一个二维数组来实现扩容,然后再将数据返还给数组。
遍历顶点数组,看是否有重复的顶点存在与顶点列表中。若有,返回异常语句(下图);若没有,则将顶点加入至顶点集合中。
- 删除顶点---removeVex(T vex)
删除也涉及顶点列表长度的变化,因此对于边的二维数组和权值二维数组,也需要使用相同的方法进行扩容。
遍历数组,看是否存在该点,若不存在,返回异常语句(下图);若存在,将其从点的集合中移除。
- 添加边---addEdge(Edge edge)
因为边类中规定添加的参数有三个,其中两个是被该边连接的端点1和端点2。要遍历顶点数组,查看这两个顶点是否都存在。若不存在,返回异常语句(下图);则将这两个顶点在二维数组中的对应下标位置中的数据从0变为1。
关于存放权值的二维数组,在对应的位置也要更新为对应的权值数据。在横坐标与纵坐标相等时,即自己等于自己的时候,权值要为0。对于没有连接的两个点,要设定的一个较大的数值来表示两点不相连。(我这里用到的是10000)
- 删除边---removeEdge(Edge edge)
首先要查看目标边的两个顶点是否存在于顶点中,不存在就要返回异常语句。若存在,再看目标边是否存在,若不存在,返回异常语句(下图)。若存在,将目标边两个端点位于边数组的对应位置的数据要变为0。权值表中对应的位置要变为最大值。
- 边/点的数量---Vex/EdgeSize()
对于定点的的大小,直接返回顶点集合的大小即可。
对于边数量,需要遍历数组,看哪些位置是1的,然后记录1的个数,并将这些个数返回
- 判断是否为空---isEmpty()
这里主要需要判断的是一个顶点集合是否为空,因为顶点集合为空,其他边也不存在。
- 广度优先迭代器---BoradTraverse(T StartVex)
初步实现:
队列实例化
迭代器实例化
访问数组实例化
主要实现:
将开始遍历点放进队列,并标记为已访问,由于实例化的访问数组长度和顶点集合的大小一致,所以找到对应点的下标,再在访问数组中变为true即可。然后将点的连接点输进队列中,并将该点从队列中弹出至迭代器中,然后循环做这步骤。最后迭代器中放的就是广度优先遍历的结果。
- 深度优先迭代器---depthTraverse(T StartVex)
初步实现:
因为深度优先遍历需要用到递归的方法,所以递归出来的点的顺序需要放在一个方法外的位置中,最后再把列表给打印出来。因此需要实例化一个存放顶点迭代顺序的迭代器。
访问数组实例化
主要实现:
输入目标点
传入迭代器中
标记为已访问
找邻接点
递归方法
代码连接:
边类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/Edge.java
图类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/MakeGraph.java
测试类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/MakeGraphTest.java
- 实验2:
十字链表也是存储图的一种形式,其结构比较复杂。对于每一个顶点都会有一个入弧和出弧,入弧是指指向这个点的边,出弧指的是从这个点出的边。
拿这幅图做例子,顶点F的入弧是A-F出弧;出弧是F-G。
对于每条边,它有四个要素,
- 弧尾:这条边出发的起始点
- 弧头:这条边指向的终点
- 同弧头:弧头相同的其他边。若无,则为空
- 同弧尾:弧尾相同的其他边。若无,则为空
实现十字链表存储图:
- 初步实现:
- 创建一个边的类,其中设置的变量有:边的权值,弧尾,弧头,同弧尾的边以及同弧头的边。并且要设定好构造方法,实例化边的时候要输入:权值、弧尾、弧头。
- 创建一个顶点的类,其中设置的变量有:入弧,出弧,数据。设定好构造方法,实例化顶点的时候要输入数据。
- 用顶点类创建一个顶点的集合来存放顶点
- 用边类来创建一个边的集合来存放边
- 方法实现:
- 添加顶点---addVex(Vex<Integer,Integer> vex)
不像之前的邻接矩阵,这次的边是直接用一个列表来保存的,所以不需要对边的集合进行重组。同时,点的添加也没那么麻烦,只要直接添加进点的集合中就可以了。
- 删除顶点---removeVex(Vex<Integer,Integer> vex)
除了直接在点的集合中进行删除,还要将涉及该点的边全部进行删除。
- 添加边---addEdge(Edge
edge)
首先是直接将边添加进边的集合中。
然后获得该边的弧头和弧尾
看弧头弧或弧尾是否已有入弧或出弧。
若弧尾没有出弧,这条边会成为这个弧尾的出弧。如果该弧尾存在出弧,则寻找该弧尾出弧的同弧尾,直到最后一个同弧尾,这条边成为该弧尾出弧的新的最后的同弧尾。
若弧头没有入弧,这条边会成为这个弧头的入弧。如果该弧头存在入弧,则寻找该弧头出弧的同弧头,直到最后一个同弧头,这条边成为该弧头入弧的新的最后的同弧头。
- 删除边---removeEdge(Edge
edge)
因为保存边的是一个列表,所以比较容易删去目标边。用列表的remove功能可以实现。
- 顶点/边的数量---vex/edgeSize()
因为保存边和顶点的都是一个列表,所以比较容易得到顶点和边的数量。用列表的size方法可以实现
- 是否为空---isEmpty()
因为保存顶点的是一个列表,所以比较容易得到图是否为空,用列表的isEmpty()方法可以实现。
- 广度优先遍历
实例化队列
实例化迭代器
实例化访问数组
输入起始遍历顶点
将起始遍历顶点放入队列中,并将其标记为已访问。
通过出弧入弧以及出弧的同弧头以及入弧的同弧尾来获取起始点的邻接点,将其入队,并用相同的方法去访问邻接点的邻接点,然后入队。将已访问的点标记直到访问的点的数量等于顶点集合的长度
将队列中的元素放进迭代器中并打印出来
- 深度优先遍历
实例化列表来保存深度遍历的顶点顺序
实例化访问数组
对起始遍历点看它有没有出弧,有的话对出弧所对应的邻接点进行方法递归。检查完出弧后看有没有同弧尾,有的话对同弧尾对应的邻接点进行方法递归。同时要对访问过的点标记为已访问。遍历过程中对这些访问的点要放进保存顶点顺序的列表中。
将列表打印
代码链接
边集合类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/tenLinkedList/Edge.java
顶点集合类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/tenLinkedList/Vex.java
实验3:
要解决最短路径可以使用一个方法:Dijkstra算法
把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 ,就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
实现方法
- 最短路径
- 传入一个带权值的二元数组,以及起始点
- 创建一个访问数组,并将起始点在访问数组的对应位置标记为已访问
- 以起始点在二元数组中的下标作为行
- 然后遍历该行,寻找最小的权值,并记录对应列下标i
- 当遇到不是邻接点的时候,遍历邻接点,看哪个邻接点离目标点近
- 将最小路径的值放入数组中
- 将对应顶点标记为已访问直到所有点都是已访问
- 打印数组
- 不相通点
查看开始点与哪个点的权值为10000,这两点不相通。
图类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/MakeGraph.java
网络路径实现类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/NetWork.java
测试类:https://gitee.com/pdds2017/yyh20162317Java2nd/blob/master/src/Experiment4/NetWorkTest.java
实验知识点
- 邻接矩阵存储图
- 十字链存储图
- 最短路径的算法——Dijkstra算法