数据结构与算法10:图与图搜索

图的概念与定义

  • 定义:图由顶点的有穷非空集合和顶点之间边的集合组成。
  • 表示方法:G(V,E)G表示一个图,V是图中顶点vertex的集合,E是图中边edge的集合。
  • 图与树和线性表的区别:
    • 线性表中的数据元素叫做元素(element),树中的数据元素叫做结点(node),图中的数据元素叫做顶点(vertex)
    • 没有数据元素的线性表称为空表,没有结点的树叫做空树,没有空图这一说法,顶点有穷但非空。
    • 线性表中相邻的数据元素之间具有线性关系,树中相邻两层的节点具有层次关系,图中任意两个顶点之间都有可能有关系,顶点之间的逻辑关系用边edge来表示.
  • 图的基本术语:
    • 顶点:vertex
    • 边:edge
    • 度:degree,和顶点相连的边的数量。
    • 入度:in degree,指向这个顶点的边的数量。
    • 出度:out degree,以此顶点为起点的边的数量。
    • 无向图:任意两个顶点之间的边都是无向边(edge),无向边使用无序偶对(vi,vj)表示,如微信的好友关系。
    • 有向图:任意两个顶点之间的边都为有向边(弧,arc),弧使用有向偶对<vi,vj>表示,如微博的关注关系。
    • 带权图:weighted graph,每条边有一个权重,如QQ的好友亲密度。
  • 图的顶点与边的关系:
    • 对于无向图,使用边相连的两个顶点互为邻接点,边依附于两个顶点。顶点的度是与其相连的边的数目。
    • 对于有向图,弧<vi,vj>从vi指向vj,则vi邻接到vjvj邻接自vi。弧<vi,vj>vi,vj相关联,vi出度为1而vj入度为1。
    • 图中顶点间存在路径,两定点存在路径说明是连通的,若路径最终回到起始点则称为环,路径不重复叫简单路径。若任意两顶点都是连通的,称为连通图,有向则称强连通图。图中有子图,若子图极大连通就是连通分量,有向的称强连通分量。
    • 无向图中连通且n个顶点n-1条边叫生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树,一个有向图由若干棵有向树构成生成森林。

图的表示与存储

图的表示

对于n个顶点的顶点集Vm条边的边集合E,使用G=<V,E>表示此图,其中 V = v 1 , v 2 , . . . , v n V={v_1, v_2, ...,v_n} V=v1,v2,...,vn E = e 1 , e 2 , . . . , e m E={e_1, e_2, ...,e_m} E=e1,e2,...,em,而 e k = ( v i , v j ) e_k=(v_i, v_j) ek=(vi,vj)表示连接顶点 v i v_i vi v j v_j vj的边。

  • 存储原则:
    1. 不重复,不遗漏
    2. 方便存取

图有两种常见的存储方式:邻接矩阵和邻接表。

邻接矩阵

用二维矩阵A存储n个顶点两两之间边的关系。

  1. 无向图: A [ i ] [ j ] = A [ j ] [ i ] A[i][j] = A[j][i] A[i][j]=A[j][i] A [ i ] [ j ] = 1 A[i][j]=1 A[i][j]=1表示顶点 v i v_i vi v j v_j vj之间有边, A [ i ] [ j ] = 0 A[i][j]=0 A[i][j]=0表示顶点 v i v_i vi v j v_j vj之间没有边。
  2. 有向图: A [ i ] [ j ] = 1 A[i][j]=1 A[i][j]=1表示存在从顶点 v i v_i vi指向 v j v_j vj的边。
  3. 带权图: A [ i ] [ j ] A[i][j] A[i][j]表示顶点 v i v_i vi v j v_j vj之间的权重。
  • 优缺点:
    • 优点:存储方式简单,计算方便高效,时间复杂度为O(1)
    • 缺点:当顶点关系比较稀疏( m < < n 2 m<<n^2 m<<n2)时,比较浪费空间,空间复杂度为 O ( n 2 ) O(n^2) O(n2)

邻接表

每个顶点对应一条链表,对于无向图,链表中存储与当前结点相连的结点;对于有向图,链表中存储当前结点指向的结点。

  • 优缺点
    • 优点:用时间换空间,节省了存储空间,空间复杂度O(n+m)
    • 缺点:使用耗时,对缓存不友好。

图的应用

拓扑排序

对于一个任务依赖场景,拓扑排序是指输出完成任务的顺序。将其抽象为一个有向无环图DAG,用顶点表示任务,用有向边表示任务之间的依赖关系,则需要输出所有顶点组成的序列,使其满足:(1)每个顶点出现且只出现一次;(2)若任务序列中顶点 v i v_i vi v j v_j vj的前面,则图中不存在从 v j v_j vj v i v_i vi的路径。
对于每个顶点来说,其入度表示依赖的任务数,其出度表示被依赖的任务数。只有当当前任务所依赖的任务全都完成之后,当前任务才能进行;即当当前顶点的入度为0的时候,可以执行当前任务。当当前任务执行完成后,依赖于当前任务的后续任务则少了一个依赖条件。

  • 算法总体思路为:
    1. 用列表保存图中所有顶点的入度,时间复杂度为O(n+m)
    2. 维护一个动态队列,记录当前入度列表中入度为0的顶点,O(n)
    3. 当队列不为空时,执行以下操作:
      1. 队头元素出队,进行输出,O(1)
      2. 更新入度列表,当前顶点指向的所有顶点的入度减一;
      3. 更新队列,将入度为0的顶点元素入队。
  • 算法复杂度分析:
    使用长度为n的列表统计保存所有顶点的入度,时间复杂度为O(n+m);第一次创建动态队列并遍历入度列表将入度为0的顶点入队,时间复杂度为O(n);队列出队,更新入队列表,顶点入队的过程采用均摊分析:队列出队最多n次,时间复杂度O(n),更新入度列表最多n次,时间复杂度O(n),更新队列最多n次,时间复杂度O(n),则第3步的总体时间复杂度为O(n)。故算法总的时间复杂度为O(n+m),额外空间花费包括列表和队列,复杂度为O(n)
  • 例题:LeetCode 207,LeetCode 210

最短路径

单源最短路径 Dijkstra
两点之间最短路径 Floyd

最小生成树

Kruskal
Prim

图搜索

  • 图搜索:从图中某一顶点出发访问图中其余顶点,且使每个顶点仅被访问一次。

深度优先遍历(depth first search,DFS):

基于回溯的算法思想,使用递归(栈)实现。

广度优先遍历(breadth first search,BFS):

基于队列实现

  • DFS和BFS的时间复杂度为O(n+m),空间复杂度为O(n)n为顶点数量,m为边的数量。

隐式图搜索

N皇后问题

题目背景:在N*N的棋盘上摆放N个皇后,使得任意两个皇后都不能处于同一行、同一列或同一斜线上。

  • 回溯法:暴力搜索
  • 本质:深度优先,隐式图搜索。
  • 如何定义状态和关系?
  • 时间复杂度 O ( N N ) O(N^N) O(NN),状态空间
  • 例题:LeetCode 51
骑士游历问题
  • 题目:在国际象棋棋盘上,有一个骑士(中国象棋中的马)从左下角出发,是否能不重复的遍历每一个格子。
  • 暴力求解,即隐式图搜索。如何定义状态和关系?
    状态:骑士所处的位置;骑士已经走过的位置。
    如何存储骑士已经走过的位置:对于8×8的棋盘,(1)使用二维数组存储,比较浪费空间,每次递归的时候都会new一个二维数组;(2)由于棋盘的状态只有两种,借助位来表示。使用一个64位的整数来表示整个棋盘的状态,使用位与操作获取指定格子的状态,使用位或操作设置指定格子的状态。
    状态之间的关系:骑士的行走规则
    时间复杂度很高,怎么办?剪枝。
    (1)合法性剪枝:根据规则,舍弃掉不合理的走法。(此题中为走过的格子不能再走)
    (2)最优性剪枝:舍弃掉不可能产生最优解的走法。
  • 除了剪枝,还有什么办法?(求任意解和所有解)
  • 启发式A*:改变搜索顺序。此题中,认为四个角上的格子优先级最高,其次是四条边的格子,最后是中间的格子。存在的问题:不确定性。
  • 迭代加深:假设最短路径为1,将当前路径大于1的不考虑;若找不到最短路径为1的,则修改最短路径假设为2,重复上述过程。优点:不会暴力搜索所有路径,不需要判重,搜索到即为最优解。
  • 迭代加深+启发式(IDA*)
  • 种子填充法:LeetCode 200,LeetCode 130
八数码
  • 题目定义:对于3×3的方格内有编号1-8的方块,求恢复这些方块的最少步数。
  • 隐式图搜索的题目。状态:棋盘;关系:移动方块前后的棋盘状态。
  • 深度优先可以搜索得到一个结果,但不一定是最短解,不需要记录状态,可以使用哈希表判重,省空间费时间;广度优先搜索得到的结果一定是最短路径,但是需要判重,每个状态都需要记录,空间耗费严重,费空间省时间。两者都为暴力搜索。
  • 迭代加深法:搜索到即为最优解,不需要判重,不需要记录状态,略微增加时间消耗。
  • 启发式方法:将状态作为key存在哈希表中,使用哈希表对状态判重;使用当前状态与目标状态的相似性作为估值函数,使用优先级队列(堆)保存当前状态的扩展状态,从中选择估值函数最大的状态(堆顶状态)作为下一个状态。
  • 双向搜索:从起点s和终点t同时进行搜索,假设步数为ans,则所需的时间为 4 a n s / 2 4^{{ans}/2} 4ans/2,单向搜索的时间为 4 a n s 4^{ans} 4ans,减少了时间消耗。从起始点和终止点轮流扩展,哈希表判断是否相遇。
总结

图搜索包括BFS和DFS,BFS包括种子加深法和双向搜索法;DFS包括回溯法和IDA*。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值