数据结构:第六章学习小结
第六章图的学习感觉比较侧重阅读和理解代码,而写代码部分占比比较小。所以这一章的总结全都是知识点的整理,是用自己的话来表达自己对代码以及做题过程步骤的理解。
一、图的存储结构
1、邻接矩阵存储
储存时要有顶点数、边数、存储N个顶点的一维数组、N*N的数组来存储权值或体现点与点之间是否有边
2、邻接表存储
邻接表存储顶点名称,同时将顶点下标存储在一个结点内,因此当邻接矩阵调用Locate方法来得到顶点下标值时(这里很重要!一定不要忘了存储时只会存名字),邻接表只需要写.node.adjvex就可以得到。但是邻接表只有在足够稀疏的时候才能够使用,不然会对空间造成极大浪费。
二、图的遍历
图有两种遍历方式 ——深度优先遍历(DFS)和广度优先遍历(BFS)
1、DFS
遍历方式类似于先序遍历,选中某一点开始,再沿此顶点的任一边走下去,若下一顶点无其他连接顶点,则往上退,直到所有的顶点都被遍历
2、BFS
遍历方式类似层次遍历,选中某一点,将与该点所连接的点都遍历完之后再遍历下一级,要注意的是,在遍历后面的点的时候一定要按照一开始选好的顺序来,也就是说你在第一次选第一个点相连的点的顺序的时候,已经确定了谁在左边,谁在中间,谁在右面,而遍历第三层的点的时候,一定也要从刚才选的最左面那个点的“子结点”开始遍历,而不能从中间或者右边的开始,重复上述步骤直至所有顶点都被遍历
值得注意的是,两种遍历使用不同存储方式的时候的时间复杂度是不同的,详见下图
邻接表 | 邻接矩阵 | |
BFS | O(N+E) | O(N^2) |
DFS | O(N+N) | O(N^2) |
三、最小生成树
最小生成树是不唯一的
有两种算法,在纯计算方面来说,对于比较稀疏的回路,我个人更喜欢第二种算法一些因为你只需要找最小的边,保证不形成回路就行,看上去很直接,但是对于比较复杂点和边数比较多的图,以及从代码方面考虑的话,我会觉得Prim算法更优越,因为它不用考虑是否想形成回路的问题,而且,不用担心在纸上计算的时候会忽略哪个点,但是Cruskal算法会需要考虑回路问题,代码比较复杂。
1、Prim算法
按点寻找:从一个点开始遍历,该点入数,组找该点的权值最小边,将与该边相连的另一个顶点也入数组,重复上述步骤直到图中所有点都在数组内。
2、Cruskal算法
按边寻找:找到最小边,为了防止形成回路,将与该边相连的两个点存入二维数组(将名称靠前的存入第一行,例bd,将b存入第一行,d存入相应列的第二行),重复此步骤,再找第三小的边,看是否与前两条边形成回路(对比数组里的点),忽略此边,重复以上步骤,如果不重复,就存入数组重复上步骤。
四、最短路径
迪杰斯特拉算法
算法和普利姆算法类似,但是普利姆算法每一个顶点处的数值都是被选中的与该点相连的边的权值,而迪杰斯特拉算法最终结点处的数值则是从起点到该点的路径权值加和,而且迪杰斯特拉算法会比普利姆算法的结构体数组多一行,用来判断该点是否已经被选入最短路径之中,用1来表示在路径中,在以后的选择中数值不会被更新,0来表示未在路径中,会在以后被选择。
总结:我个人比较喜欢这种可以在纸上写写画画,类似于用推理来得到结果的知识,但是不得不承认的是,在本章之中,我对代码掌握的不是特别透彻,读懂没问题,但是复现的过程有些困难,而且我发现做习题真的太重要了,很多看着觉得懂的东西,其实会在做题时琢磨好多遍,尤其是一些细节地方。比如我在周一听老师讲时觉得很有逻辑很清晰,但是到晚上做作业时,就会发现,已经选择的最短路径数值那里要改成∞嘛,如果不改的话,计算机不会一直觉得他是最小的然后重复计算吗?如果改的话,最后怎么知道他的最小路径是多少呢?后来看了一会才明白,那行只填写1和0的数组起着至关重要的作用,只要那里为1,再小的数值也不会被计算机选择,因为是已经确定的路径,点已入数组。所以希望老师能够多留一些这种类型的练习题~