20172313 2018-2019-1 《程序设计与数据结构》第九周学习总结
20172313 2018-2019-1 《程序设计与数据结构》第九周学习总结
教材学习内容总结
无向图
- 无向图:与树类似,图也由结点和这些结点之间的连接构成。这些结点是顶点,而结点之间的链接是边。无向图是一种边为无序结点对的图。于是,记做(A,B)的边就意味着A与B之间有一条从两个方向都可以游历的连接。边记作(A,B)和记作(B,A)的含义是完全一样的。
- 如果图中的两个顶点之间有一条连通边,则称这两个顶点是邻接的。邻接顶点有时也称为邻居。
- 联通一个顶点及其自身的边称为自循环或环。
上图中,A和B邻接,而A和D不邻接。边(A,A)表示的是连接A到自身的一个环。
- 完全图:如果一个无向图含有最多条边,那么它为完全图。例如:
- 完全图有n(n-1)条边。(此时假设没有边自循环。)
- 连接图中两个顶点的边的序列,可以由多条边组成。(图中的路径是双向的。)
- 路径长度:路径中所含边的数目(顶点个数减1)。
- 连通图:无向图中任意两个顶点之间都存在一条路径。(完全图一定是连通图,连通图不一定是完全图。)
- 环路:一种首顶点和末顶点相同且没有重边的路径。
- 无环图:没有环路的图。
- 无向图是一种连通的无环无向图,其中一个元素被指定为树根。
有向图
- 有向图:又称双向图,是一种边为有序顶点对的图。(边(A,B)和边(B,A)方向不同)
- 有向路径:连接两个顶点有向边的序列。
- 注意:注意连通图的有向图和无向图之间的不同:
- 上图第一个图是连通的,第二个图并不是连通的,因为没有任何路径能从其他顶点游历到顶点A。
- 拓扑序:有向图中没有环路,且有一条从A到B的边,则可以吧顶点A安排在顶点B之前,这种排列得到的顶点次序。
- 有向树是一个有向图,其中指定一个元素为根,则具有下列特性:
- 不存在其他顶点到树根的连接。
- 每个非树根元素恰好有一个连接。
- 树根到每个其他顶点都有一条路径。
网络
- 网络:又称加权图,每条边都对应一个权值(数据信息)的图,可以是有向的也可以是无向的。(城市之间的航线、票价等)
- 网络的边由起始顶点、终止定点和权重构成的三元组来表示。
常用的图算法
- 深度优先遍历:和树的先序遍历比较类似。假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
- 无向图示例如下:
- ①访问A。
- ②访问(A的邻接点)C。在第1步访问A之后,接下来应该访问的是A的邻接点,即"C,D,F"中的一个。但在本文的实现中,顶点ABCDEFG是按照顺序存储,C在"D和F"的前面,因此,先访问C。
- ③访问(C的邻接点)B。
在第2步访问C之后,接下来应该访问C的邻接点,即"B和D"中一个(A已经被访问过,就不算在内)。而由于B在D之前,先访问B。 - ④访问(C的邻接点)D。在第3步访问了C的邻接点B之后,B没有未被访问的邻接点;因此,返回到访问C的另一个邻接点D。
- ⑤访问(A的邻接点)F。前面已经访问了A,并且访问完了"A的邻接点B的所有邻接点(包括递归的邻接点在内)";因此,此时返回到访问A的另一个邻接点F。
- ⑥访问(F的邻接点)G。
- ⑦访问(G的邻接点)E。
- 因此访问顺序是:A -> C -> B -> D -> F -> G -> E
- 有向图示例如下:
- ①访问A。
- ②访问B。在访问了A之后,接下来应该访问的是A的出边的另一个顶点,即顶点B。
- ③访问C。在访问了B之后,接下来应该访问的是B的出边的另一个顶点,即顶点C,E,F。在本文实现的图中,顶点ABCDEFG按照顺序存储,因此先访问C。
- ④访问E。接下来访问C的出边的另一个顶点,即顶点E。
- ⑤访问D。接下来访问E的出边的另一个顶点,即顶点B,D。顶点B已经被访问过,因此访问顶点D。
- ⑥访问F。接下应该回溯"访问A的出边的另一个顶点F"。
- ⑦访问G。
- 因此访问顺序是:A -> B -> C -> E -> D -> F -> G
- 无向图示例如下:
- 广度优先遍历
- 又称为"宽度优先搜索"或"横向优先搜索",简称BFS。从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2...的顶点。
- 无向图示例如下:
- ①访问A。
- ②依次访问C,D,F。在访问了A之后,接下来访问A的邻接点。前面已经说过,在本文实现中,顶点ABCDEFG按照顺序存储的,C在"D和F"的前面,因此,先访问C。再访问完C之后,再依次访问D,F。
- ③依次访问B,G。在第2步访问完C,D,F之后,再依次访问它们的邻接点。首先访问C的邻接点B,再访问F的邻接点G。
- ④访问E。在第3步访问完B,G之后,再依次访问它们的邻接点。只有G有邻接点E,因此访问G的邻接点E。
- 因此访问顺序是:A -> C -> D -> F -> B -> G -> E
- 有向图示例如下:
- ①访问A。
- ②访问B。
- ③依次访问C,E,F。在访问了B之后,接下来访问B的出边的另一个顶点,即C,E,F。前面已经说过,在本文实现中,顶点ABCDEFG按照顺序存储的,因此会先访问C,再依次访问E,F。
- ④依次访问D,G。在访问完C,E,F之后,再依次访问它们的出边的另一个顶点。还是按照C,E,F的顺序访问,C的已经全部访问过了,那么就只剩下E,F;先访问E的邻接点D,再访问F的邻接点G。
- 因此访问顺序是:**A -> B -> C -> E -> F -> D -> G **
- 测试连通性:从任意结点开始的广度优先遍历中得到的顶点数等于图中所含顶点数。
- 最小生成树
- 生成树:生成树是一棵含有图中所有顶点和部分边(但可能不是所有边)的树。
- 最小生成树:所含边权值之和小于其他生成树的边的权值之和。计算最小生成树一般有两种算法:Kruskal和Prim算法
- Kruskal算法:此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。具体操作如下:
- ① 把图中的所有边按代价从小到大排序;
- ② 把图中的n个顶点看成独立的n棵树组成的森林;
- ③ 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
- ④重复③,直到所有顶点都在一颗树内或者有n-1条边为止。
- Kruskal算法:此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。具体操作如下:
- Prim算法:此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,直到最小生成树含有原始图的所有顶点时结束。具体操作如下:
- ①图的所有顶点集合为V;初始令集合u={s},v=V−u;
- ②在两个集合u,v能够组成的边中,选择一条代价最小的边(u0,v0),加入到最小生成树中,并把v0并入到集合u中。
- ③重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
- 判断最短路径
- 第一种方法:判定起始顶点和目标顶点之间是否存在最短路径(两个顶点之间边数最少的路径)。
- 第二种方法:Dijkstra算法:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。具体操作如下;
- ①初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
- ②从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
- ③以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
- ④重复步骤b和c直到所有顶点都包含在S中。
- 示例如下,求从顶点v1到其他各个顶点的最短路径
首先第一步,我们先声明一个dis数组,该数组初始化的值为:
这时顶点集合S中只有一个元素:S={v1},既然是求V1顶点到其余各个顶点的最短路径,那么我们就先找到V1能够直接到达的点,从图中可知是V2和V3,我们把dis[m]设为从顶点d[0]处到V(m+1)的路径查毒,同时把所有其他(顶点不能直接到达的)顶点的路径长度设为无穷大。这时在数组中表示就为dis[1]=12,dis[2]=6。因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的权重都是正数,所以不可能通过第三个顶点中转,这时V1到V3的最短路程就是dis[2]值。将V3加入S中。
我们现在已经确定了一个点的最短路径,下面我们来看V3能到哪些点,从图中看V3只能到V5且V1——V3——V5的距离为17,显而易见要比∞要小,所以数组更新如下。
此时V1和V3都已经存入S中,我们又从除dis[2]和dis[0]外的其他值中寻找最小值,此时dis[1]最小,同理,V1到V2的最短距离就是dis[1]的值,将V2加入U中,从图中看V2能到V3和V4,前面分析过,V1到V3的距离是最短的,所以不再看V2到V4,V1——V2——V4的距离为15,显而易见要比∞要小,所以数组更新如下。
此时V1、V2、V3都已经存入S中,我们又从除dis[0]、dis[1]、dis[2]外的其他值中寻找最小值,此时dis[3]最小,将V4加入U中,从图中看V4只能到V5,有前面可知V1——V4的最短距离为15,所以V1——V4——V5的距离为16,比17小,所以对dis[4]进行替换
,数组更新如下
然后再次寻找最小值,将V5加入U中,计算完毕。
图的实现策略
- 邻接列表:将每个顶点的邻接点串成一个单链表。
- 邻接矩阵: 逻辑结构分为两部分:Vexs[](存储顶点)和Arcs[][](邻接矩阵)集合。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。无向图的邻接矩阵示例如下:
教材学习中的问题和解决过程
- 问题1:在学习教材的时候,最小生成树的方法中有这样一行代码“resultGraph[][] = Double.Positive_INFINITY”,不是很理解这里的POSITIVE_INFINITY代表什么意思。
- 问题1解决方案:我们首先来思考这样一个问题,在进行浮点数运算的时候,有时我们会遇到除数为0的情况,那我们该如何解决呢?所以引入了无限这个概念,POSITIVE_INFINITY正是可以用来代表无限。示例如下
double i = Double.POSITIVE_INFINITY; // i表示为无限大
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
- 问题2:不是很理解求最短路径时的Dijkstra算法。
- 问题2解决方案:前文有详细叙述。
- 问题3:不是很理解求最小生成树的Prim算法。
- 问题3解决方案:前文有详细叙述。
代码调试中的问题和解决过程
- 问题1:在做pp15_1的时候,对方法进行测试,陷入了死循环。
- 问题1解决方案:首先,我先要确定是哪个方法导致了死循环,由于我调用了toString方法,但屏幕上并没有出现任何数值,这就说明要么是toString出现了问题,要么在它之前就出现了错误。从前往后分析,把所有的操作都注释掉,依次判断,发现了一个奇怪的现象,当第一次调用addEdge方法的时候是不会出现死循环的,在第二次的时候才会出现。虽然不知道具体在哪个地方出现了问题,但可以肯定的确是addEdge出了毛病。用debug对代码进行分析之后发现了问题,由于邻接列表的数据结构,利用列表中的元素的指针指向其他元素的时候不能直接指向该列表中的元素,而是要用一个temp的其值进行储存,再用指针指向它,这样问题就得以解决了。
- 问题2:在做pp15_7计算带权重的有向图的最短路径的时候无法得到期望值。
- 问题2解决方案:在输入的时候我想获得A到C的权重和最小的路径,却得到的值为49。因为我的思路是把原先判断两个顶点之间是否有边的adjMatrix的数组改成了存放两顶点之间边权重的数组。如果两顶点之间没有边,将边的值设置为-1,如果有边但没有存权重就把它设置为无穷大。出现的值是49,可能是因为在计算的时候加上了-1,所以我就想应该要在哪里添加判断条件。
我在上图所示的地方加上了判断条件,但结果并没有发生改变。判断条件是temp < dist[j],为dis[]加限定条件只是限制了数组值的范围,并不会导致出现49的情况。还要对adjMaxtrix[][]进行限制。如下图所示,问题就得以解决了
for (int j = 0; j < numVertices; j++) {
if (adjMatrix[k][j] != -1&&dist[j]!= -1) {
double temp = (adjMatrix[k][j] == Double.POSITIVE_INFINITY
? Double.POSITIVE_INFINITY : (min + adjMatrix[k][j]));
if (flag[j] == false && (temp < dist[j])) {
dist[j] = temp;
previous[j] = k;
}
}
}
代码托管
上周考试错题总结
这周没有错题哦~
结对及互评
- 博客中值得学习的或问题:
- 排版精美,对教材的总结细致,善于发现问题,对于问题研究得很细致,解答也很周全。
- 代码中值得学习的或问题:
- 代码写的很规范,思路很清晰,继续加油!
点评过的同学博客和代码
其他(感悟、思考等,可选)
刚刚感觉上周的内容简单了一些,本章的内容又给了我一闷棍,本章的难度不仅难在算法的理解上,还难在代码的实现上,光是学习算法就花费了不少时间,关键代码的理解也不容易。但这周学下来总体上说收获还是挺大的,这周的学习状态也还算可以,希望自己能够继续保持这样的状态,继续进步吧!
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
第一周 | 200/200 | 1/1 | 5/20 | |
第二周 | 981/1181 | 1/2 | 15/20 | |
第三周 | 1694/2875 | 1/3 | 15/35 | |
第四周 | 3129/6004 | 1/4 | 15/50 | |
第五周 | 1294/7298 | 1/5 | 15/65 | |
第六周 | 1426/8724 | 1/6 | 20/85 | |
第七周 | 2071/10795 | 1/7 | 20/105 | |
第八周 | 3393/14188 | 1/8 | 20/125 | |
第九周 | 3217/17045 | 1/9 | 20/145 |
-
计划学习时间:20小时
-
实际学习时间:20小时