- 无向图
- 有向图
- 带权图
- 邻接矩阵(Adjacency Matrix)
- 邻接表(Adjacency List)
- 广度优先搜索(Breadth-First-Search),我们平常都把简称为 BFS
- 直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索
- V 表示顶点,E表示边的个数
- 时间复杂度
- 最坏情况,终止顶点t离起始顶点s很远,需要遍历完整整个图才能找到
- 这个时候,每个顶点都要进出一遍队列,每个边也都会被访问一次
- 所以,BFS的时间复杂度是O(V + E)
- 当然,对于一个连通图来说,也就是说一个图中的所有顶点是连通的,E肯定是要大于等于V - 1,所以BFS的时间复杂度可以简单写为 O(E)
- 空间复杂度
- BFS空间消耗主要是几个辅助变量 visited数组,queue队列, prev数组
- 这三个存储空间大小不会超过顶点的个数,所以是O(V)
- 深度优先搜索(Depth-First-Search),简称 DFS。最直观的例子就是“走迷宫”
- 假设你站在迷宫的某个岔路口,然后想找到出口.你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。这种走法就是一种深度优先搜索策略。
- 搜索的起始顶点是 s,终止顶点是 t,我们希望在图中寻找一条从顶点 s 到顶点 t 的路径。如果映射到迷宫那个例子,s 就是你起始所在的位置,t 就是出口。
- 这里面实线箭头表示遍历,虚线箭头表示回退。从图中我们可以看出,深度优先搜索找出来的路径,并不是顶点 s 到顶点 t 的最短路径。
- 实际上,深度优先搜索用的是一种比较著名的算法思想,回溯思想
- 时间复杂度
- 每条边最多会被访问两次,一次是遍历,一次是回退。所以,图上的DFS的时间复杂度是O(E), E表示个数
- 空间复杂度
- DFS 的消耗内存主要是 visited, stack数组。visited, stack数组的大小跟顶点的个数V成正比,所以总的空间复杂度O(V)
- 理解偏序,以选课为例子
- 假设我们学习完V1这门课后,可以选修 V2
或者
V3 - 这个
或者
表示,学习V2和V3这两门课没有特定的先后顺序 - 因此,
在我们所有可以选择的课程中,任意两门课程的关系要么是确定的(即拥有先后关系),要么是不确定的(即没有先后关系),绝对不存在互相矛盾的关系(即环路)
- 抽象而言,有向图中两个顶点之间不存在环路,至于连通与否,是无所谓的。所以,有向无环图必然是满足偏序关系的
- 假设我们学习完V1这门课后,可以选修 V2
- 我们知道不同整数之间的大小关系是确定的,即1总是小于4的,不会有人说1大于等于4吧,这就是说,这个序列是满足全序关系的
- 而对于拥有全序关系的结构(如拥有不同整数的数组),在其线性化(排序)之后的结果必然是唯一的
- 对于排序算法,评价指标之一是看该排序算法是否稳定,即值相同的元素的排序结果是否和出现的顺序一致
- 比如,我们说快排是不稳定的,这是因为最后的快排结果中相同元素的出现顺序和排序前不一致了
- 如果用偏序的概念可以这样解释这一想象:
相同值的元素之间的关系是无法确定的
,因此它们在最终的结果中的出现顺序可以是任意的 - 而对于插入排序这种稳定性排序,它们对于值相同的元素,还有一个潜在的比较方式,即比较它们的出现顺序,出现靠前的元素大于出现后出现的元素
- 因此,通过这一潜在的比较,
将偏序关系转换为了全序关系,从而保证了结果的唯一性
- 进行拓扑排序,首先找到没有前驱的顶点V1
- 在删除顶点V1以及作为起点的弧后,继续查找没有前驱的顶点,此时,V2 和 V3都符合条件,可以随机选择一个,最终选择了V2
- 然后继续重复以上的操作,直至最后找不到没有前驱的节点
- 最后得到的排序结果有两种
V1 -> V2 -> V3 -> V4 V1 -> V3 -> V2 -> V4
- 迪杰斯特拉(Dijkstra) 算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径
- 它的主要特点是以起始点为中心向外层扩展(广度优化搜索思想),直到扩展为止
- 其中的最小堆优先级队列,采用了贪心的思想。从小到大排列
- 图例
- 以demo_astar_2.cpp为例子
- 如何快速找出一条接近最短路径
- 每次找到跟起点最近的顶点,往外扩展
- 但是,这样会遇到一个问题,离起点近的顶点,可能离终点会越来越远,会存在跑偏的情况
- 当我们遍历到某个顶点的时候,从起点走到这个顶点的路径长度是确定的,记作g(i)(i表示顶点)。但是,从这个顶点到终点的路径长度是未知的。确切的值无法直到,可以用估算值来替代
- 我们可以通过这个顶点跟终点之间的直线距离,也就是欧几里得距离,来近似地估计这个顶点跟终点的路径长度,
- 可以把这个距离记作h(i)(i表示这个顶点的编号),专业的叫法是启发函数(heuristic function)
- 整体公式: f(i) = g(i) + h(i). f(i)的专业叫法是估计函数(evaluation function).这样避免“跑偏”的问题
- 每次找到跟起点最近的顶点,往外扩展
- 为什么 A* 不像 Dijkstra能找到最短距离