图总结

在求职和面试中,图也是很重要的一个领域。而这里面尤其重要的是深度优先搜索、广度优先搜索、最小生成树、最短路径、拓补排序、有向图的连通性的内容。在这里,我们只抽象地对图及各种算法进行分析,尽量不涉及代码实现。

(参考:《Java数据结构和算法》)

我们首先明确图的定义。在这里,我们讨论的都是连通图,也就是至少有一条路径可以连通所有顶点。有一种情况是有些路径只能有一个方向,称为有向图。有些图边被赋予一个权值,叫带权图。


一、无向图:深度优先搜索、广度优先搜索、最小生成树

DFS和BFS都可以用递归和非递归来实现,但是逻辑都是一样的,而且非递归还更好理解。关键是要理解其决策树以及使用栈和队列的思想,在具体的应用场景中再用代码实现。

1、深度优先搜索(DFS)

(1)算法步骤

图的顶点要设置一个标志位,来表示是否已经被访问过。如果访问过的顶点,该顶点会被置位标志位。DFS通过栈来实现。它的算法步骤如下:

把图中某个顶点作为起点。先访问该顶点,标记他,然后把该顶点放入栈中;

while(栈不为空){

    if(栈顶的顶点还有没访问过的邻接顶点){

        访问栈顶的顶点的某个邻接的顶点,标记他,然后把该顶点放入栈中;

    }

    else{

        把栈顶的顶点出栈;

    }

}

可以看出,要自己定一个有规律的,访问某顶点的邻接顶点的顺序。深度优先搜索要得到距离起始点最远的顶点,然后在不能继续前进的时候返回。

(2)应用

深度优先搜索经常被用于游戏仿真中(或者现实世界中与游戏相似的情况)。在一般游戏中,可以在几个可能的动作中选择一个,每个选择导致更进一步的选择,这些选择又产生了更多的选择,这样就形成了一个代表可能性不断延伸的树形图。这就是决策树。

2、广度优先搜索,也叫宽度优先搜索(BFS)

BFS要使用队列,而不是栈。它跟DFS不同,它首先访问起始顶点的所有邻接顶点,然后再访问较远的区域。

(1)算法步骤

把图中某个顶点作为起点。先访问该顶点,标记他,然后把该顶点放入队列中;

while(队列不为空){

    if(队列头顶点还有没访问过的邻接顶点){

        访问队列头顶点的某个邻接的顶点,标记他,然后把该顶点放入队列中;

    }

    else{

        把队列头的顶点出队列;

    }

}

(2)应用

BFS首先找到与起始点相距一条边的所有顶点,然后是相距两条边的顶点,以此类推。如果要寻找起始顶点与指定顶点的最短距离,那这个属性非常有用。首先执行BFS,当找到指定顶点时,就可以说这条路径是到这个顶点的最短路径。

3、最小生成树(MST)


最小生成树,也就是把一个图多余的边去掉,生成一种图,他用最少的边连接了所有的顶点。对于某一个图来说,最小生成树并不是唯一的。此时他的边的数量一定是他的顶点的数量减一。此时,我们还不需要考虑边的权值以及方向。创建最小生成树的算法和DFS,BFS几乎是一样的,所不同的是每次读取顶点同时,要记住已经走过的边。其实用DFS生成的天然就是一个最小生成树。用DFS生成最小生成树比BFS简单多了。用DFS访问定点,它返回的定点的顺序就是一个最小生成树。只需要一个个返回顶点,然后把路径记录下来即可。

二、有向图:拓补排序、有向图的连通性

若图有方向的情况下,称为有向图。有向图在实际中有很重要的应用,例如在大学的课程选修中,经常听到上某门课的前提是修了另一门课的学分。为了拿到学位,要上高级研讨会,而只有先后上了代数和高等数学,才能上高级研讨会。


假设存在一个列表,包含了要得到学位所必修的全部课程,我们按课程的先后顺序排列它,可以得到以下这个序列:

BAEDGCFH

这种方法的排列,称为图的拓补排序。造出拓扑序列的实际意义是:如果按照拓扑序列中的顶点次序,在开始每一项活动时,能够保证它的所有前驱活动都已完成,从而使整个工程顺序进行,不会出现冲突的情况。但是注意:

(1)拓补排序不是唯一的;(2)他可以用于连通图,也可以用于非连通图;(3)有环图拓补排序是无法处理的。事实上,不包含环的图就是树。拓补排序必须在无环的有向图中处理。

1、拓补排序的算法如下:

(1)找到一个没有后继的顶点;

(2)把它放入数组中,并且从图中删除顶点以及连接到它的边;

(3)不断进行(1)和(2),如果找不到没有后继的顶点了,但是数组中的顶点数量还没达到图的顶点数量,说明图中必然存在环。

2、有向图的连通性

从前面得知,在无向图中,利用深度优先和广度优先搜索可以找到所有相互连通的点。而对有向图来说,就有点麻烦了。因为不能从任意一个顶点开始,期望找到所有其他连通的顶点,因为图是有方向的。关于有向图连通性的问题主要是,如果从一个指定顶点出发,能够到达哪些顶点?

(1)连通性表

连通性表,其实就是一张表,对于这个表上的所有顶点,从这个顶点能到达图中哪个顶点。


例如,对于这个有向图,AC, BACE, C, DEC, EC就是他的连通性表。第一个字母是起始点,接下来的字母是能够到达的顶点(直接或间接)。得到这个连通性表的方法很简单,就是对每一个顶点使用DFS即可。

(2)

对于有向图的连通性,还有一个问题:一个顶点,是否可以从另外一个顶点可以到达?对于这个问题,可以查看连通性表,但是这需要查看连通性表的某行所有表项,需要O(N)的时间复杂度。能否有一个更好的算法,可以在O(1)时间复杂度里,告知一个顶点对另一个顶点是否可以到达?这就需要使用Warshall算法。事实上,它的本质就是动态规划。

三、带权图:带权图的最小生成树、最短路径问题

1、带权图的最小生成树

其实就是找到一棵最小生成树,连接所有顶点,权值最小。要注意,最小生成树也不一定是唯一的。这在现实生活中有很大的意义,例如光纤布线等。要实现这个算法需要使用优先级队列。这里要注意两个问题:

(1)连通n个顶点,需要n-1条边;(2)尽可能选取权值小的边,不能构成回路。

有以下两个算法(https://blog.csdn.net/luoshixian099/article/details/51908175):

(1)Prim算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

  1. 图的所有顶点集合为VV;初始令集合u={s},v=Vuu={s},v=V−u;
  2. 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
  3. 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息:


(2)Kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。 
1. 把图中的所有边按代价从小到大排序; 
2. 把图中的n个顶点看成独立的n棵树组成的森林; 
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树(为什么要有这个规定,是因为如果两个顶点属于不同的树,就不会形成环了),则成为最小生成树的一条边,并将这两颗树合并作为一颗树。 

4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

在这里,边要先放入一个优先级队列里面,这样就可以拿到最小代价边。


2、最短路径问题

对于带权无向图来说,还有一个很重要的问题:给定两个顶点,如何找到一条路径,令两个顶点之间连通,而且权值最小呢?这里要用到Floyd算法。而如果是带权的有向图,要使用Dijkstra算法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值