《31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.pdf》由会员分享,可在线阅读,更多相关《31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.pdf(12页珍藏版)》请在三一文库上搜索。
1、31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? 上一节我们讲了图的表示方法,讲到如何用有向图、无向图来表示一个社交网络。在社交网络中,有一个六度分割理论,具体是说,你与世界上的另一个人间隔 的关系不会超过六度,也就是说平均只需要六步就可以联系到任何两个互不相识的人。 一个用户的一度连接用户很好理解,就是他的好友,二度连接用户就是他好友的好友,三度连接用
2、户就是他好友的好友的好友。在社交网络中,我们往往通过用 户之间的连接关系,来实现推荐“可能认识的人”这么一个功能。今天的开篇问题就是,给你一个用户,如何找出这个用户的所有三度(其中包含一度、二度和三 度)好友关系? 这就要用到今天要讲的深度优先和广度优先搜索算法。 什么是“搜索”算法? 我们知道,算法是作用于具体数据结构之上的,深度优先搜索算法和广度优先搜索算法都是基于“图”这种数据结构的。这是因为,图这种数据结构的表达能力很 强,大部分涉及搜索的场景都可以抽象成“图”。 图上的搜索算法,最直接的理解就是,在图中找出从一个顶点出发,到另一个顶点的路径。具体方法有很多,比如今天要讲的两种最简单、
3、最“暴力”的深度优 先、广度优先搜索,还有A*、IDA*等启发式搜索算法。 我们上一节讲过,图有两种主要存储方法,邻接表和邻接矩阵。今天我会用邻接表来存储图。 我这里先给出图的代码实现。需要说明一下,深度优先搜索算法和广度优先搜索算法,既可以用在无向图,也可以用在有向图上。在今天的讲解中,我都针对无 向图来讲解。 public class Graph / 无向图 private int v; / 顶点的个数 private LinkedList adj; / 邻接表 public Graph(int v) this.v = v; adj = new LinkedListv; for (int
4、i=0; i(); public void addEdge(int s, int t) / 无向图一条边存两次 adjs.add(t); adjt.add(s); 广度优先搜索(BFS) 广度优先搜索(Breadth-First-Search),我们平常都把简称为BFS。直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后 是次近的,依次往外搜索。理解起来并不难,所以我画了一张示意图,你可以看下。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络
5、中的三度好友关系?.html2019/1/15 15:36:08 尽管广度优先搜索的原理挺简单,但代码实现还是稍微有点复杂度。所以,我们重点讲一下它的代码实现。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 这里面,bfs()函数就是基于之前定义的,图的广度优先搜索的代码实现。其中s表示起始顶点,t表示终止顶点。我们搜索一条从s到t的路径。实际上,这样求得的 路径就是从s到t的最短路径。 public void
6、 bfs(int s, int t) if (s = t) return; boolean visited = new booleanv; visiteds=true; Queue queue = new LinkedList(); queue.add(s); int prev = new intv; for (int i = 0; i t的路径 if (prevt != -1 System.out.print(t + “ “); 这段代码不是很好理解,里面有三个重要的辅助变量visited、queue、prev。只要理解这三个变量,读懂这段代码估计就没什么问题了。 visited是用来记录已
7、经被访问的顶点,用来避免顶点被重复访问。如果顶点q被访问,那相应的visitedq会被设置为true。 queue是一个队列,用来存储已经被访问、但相连的顶点还没有被访问的顶点。因为广度优先搜索是逐层访问的,也就是说,我们只有把第k层的顶点都访问完成 之后,才能访问第k+1层的顶点。当我们访问到第k层的顶点的时候,我们需要把第k层的顶点记录下来,稍后才能通过第k层的顶点来找第k+1层的顶点。所以,我 们用这个队列来实现记录的功能。 prev用来记录搜索路径。当我们从顶点s开始,广度优先搜索到顶点t后,prev数组中存储的就是搜索的路径。不过,这个路径是反向存储的。prevw存储的是,顶 点w是
8、从哪个前驱顶点遍历过来的。比如,我们通过顶点2的邻接表访问到顶点3,那prev3就等于2。为了正向打印出路径,我们需要递归地来打印,你可以看 下print()函数的实现方式。 为了方便你理解,我画了一个广度优先搜索的分解图,你可以结合着代码以及我的讲解一块儿看。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime
9、/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 掌握了广优先搜索算法的原理,我们来看下,广度优先搜索的时间、空间复杂度是多少呢? 最坏情况下,终止顶点t离起始顶点s很远,需要遍历完整个图才能找到。这个时候,每个顶点都要进出一遍队列,每个边也都会被访问一次,所以,广度优先搜索 的时间复杂度
10、是O(V+E),其中,V表示顶点的个数,E表示边的个数。当然,对于一个连通图来说,也就是说一个图中的所有顶点都是连通的,E肯定要大于等 于V-1,所以,广度优先搜索的时间复杂度也可以简写为O(E)。 广度优先搜索的空间消耗主要在几个辅助变量visited数组、queue队列、prev数组上。这三个存储空间的大小都不会超过顶点的个数,所以空间复杂度是O(V)。 DFS 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08
11、 深度优先搜索() 深度优先搜索(Depth-First-Search),简称DFS。最直观的例子就是“走迷宫”。 假设你站在迷宫的某个岔路口,然后想找到出口。你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续 走,直到最终找到出口。这种走法就是一种深度优先搜索策略。 走迷宫的例子很容易能看懂,我们现在再来看下,如何在图中应用深度优先搜索,来找某个顶点到另一个顶点的路径。 你可以看我画的这幅图。搜索的起始顶点是s,终止顶点是t,我们希望在图中寻找一条从顶点s到顶点t的路径。如果映射到迷宫那个例子,s就是你起始所在的位 置,t就是出口。 我用深度递归算
12、法,把整个搜索的路径标记出来了。这里面实线箭头表示遍历,虚线箭头表示回退。从图中我们可以看出,深度优先搜索找出来的路径,并不是 顶点s到顶点t的最短路径。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 实际上,深度优先搜索用的是一种比较著名的算法思想,回溯思想。这种思想解决问题的过程,非常适合用递归来实现。回溯思想我们后面会有专门的一节来 讲,我们现在还回到深度优先搜索算法上。 我把上面的过程用递归来翻译出来,
13、就是下面这个样子。我们发现,深度优先搜索代码实现也用到了prev、visited变量以及print()函数,它们跟广度优先搜索代码 实现里的作用是一样的。不过,深度优先搜索代码实现里,有个比较特殊的变量found,它的作用是,当我们已经找到终止顶点t之后,我们就不再递归地继续查找 了。 boolean found = false; / 全局变量或者类成员变量 public void dfs(int s, int t) found = false; boolean visited = new booleanv; int prev = new intv; for (int i = 0; i v;
14、+i) previ = -1; recurDfs(s, t, visited, prev); print(prev, s, t); private void recurDfs(int w, int t, boolean visited, int prev) if (found = true) return; visitedw = true; if (w = t) found = true; return; for (int i = 0; i adjw.size(); +i) int q = adjw.get(i); if (!visitedq) prevq = w; recurDfs(q, t
15、, visited, prev); 理解了深度优先搜索算法之后,我们来看,深度度优先搜索的时、空间间复杂度是多少呢? 从我前面画的图可以看出,每条边最多会被访问两次,一次是遍历,一次是回退。所以,图上的深度优先搜索算法的时间复杂度是O(E),E表示边的个数。 深度优先搜索算法的消耗内存主要是visited、prev数组和递归调用栈。visited、prev数组的大小跟顶点的个数V成正比,递归调用栈的最大深度不会超过顶点的个 数,所以总的空间复杂度就是O(V)。 解答开篇 了解了深度优先搜索和广度优先搜索的原理之后,开篇的问题是不是变得很简单了呢?我们现在来一起看下,如何找出社交网络中某个用户的
16、三度好友关系? 上一节我们讲过,社交网络可以用图来表示。这个问题就非常适合用图的广度优先搜索算法来解决,因为广度优先搜索是层层往外推进的。首先,遍历与起始顶 点最近的一层顶点,也就是用户的一度好友,然后再遍历与用户距离的边数为2的顶点,也就是二度好友关系,以及与用户距离的边数为3的顶点,也就是三度好 友关系。 我们只需要稍加改造一下广度优先搜索代码,用一个数组来记录每个顶点与起始顶点的距离,非常容易就可以找出三度好友关系。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三
17、度好友关系?.html2019/1/15 15:36:08 内容小结 广度优先搜索和深度优先搜索是图上的两种最常用、最基本的搜索算法,比起其他高级的搜索算法,比如A*、IDA*等,要简单粗暴,没有什么优化,所以,也被 叫作暴力搜索算法。所以,这两种搜索算法仅适用于状态空间不大,也就是说图不大的搜索。 广度优先搜索,通俗的理解就是,地毯式层层推进,从起始顶点开始,依次往外遍历。广度优先搜索需要借助队列来实现,遍历得到的路径就是,起始顶点到终 止顶点的最短路径。深度优先搜索用的是回溯思想,非常适合用递归实现。换种说法,深度优先搜索是借助栈来实现的。在执行效率方面,深度优先和广度优先 搜索的时间复杂
18、度都是O(E),空间复杂度是O(V)。 课后思考 1. 我们通过广度优先搜索算法解决了开篇的问题,你可以思考一下,能否用深度优先搜索来解决呢? 2. 学习数据结构最难的不是理解和掌握原理,而是能灵活地将各种场景和问题抽象成对应的数据结构和算法。今天的内容中提到,迷宫可以抽象成图,走迷宫 可以抽象成搜索算法,你能具体讲讲,如何将迷宫抽象成一个图吗?或者换个说法,如何在计算机中存储一个迷宫? 欢迎留言和我分享,也欢迎点击“请朋友读”,把今天的内容分享给你的好友,和他一起讨论、学习。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与
19、算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15 15:36:08 精选留言: Jerry银银 2018-12-03 04:58:32 朗读者原谅我的有强迫症:queue这个单词读错了,付上正确的音标如下kju 我觉得避免这种问题,有个方法就是,朗读之前,逐个查询一下单词的正确发音,一篇文章中的单词屈指可数,这个工作量按理说应该不大。 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交网络中的三度好友关系?.html2019/1/15
20、15:36:08 但是这个简单的举措,能大大提高听文章的体验,不然听起来总觉得很怪 往大了说影响,毕竟咱们极客时间做的是知识,做的是学问 24赞 作者回复2018-12-05 01:51:56 PtricK 2018-12-03 01:16:19 思考题: 1. 可以。DFS递归时传多一个离初始节点的距离值,访问节点时,距离超过3的不再继续递归 2. 初始化两个顶点为迷宫起点和终点,从起点开始,遇到分叉点,为每个分支都新建一个节点,并和前一节点连接,递归每个分支直到终点 16赞 The Sword of Damocles 2018-12-16 02:54:41 看的费劲的同学可以先去网上找找二
21、叉树的深度、广度优先遍历看看。图的搜索和这个类似。 深度:借助一个栈 广度:借助一个队列 老师的代码没有注释,变量名称也比较简洁,虽然下文有解释,但是来回上下翻实在是看的费劲。建议能稍微优化一下。 11赞 五岳寻仙 2018-12-03 01:16:06 课后思考题: 1. 深度优先用于寻找3度好友,可以设定搜索的深度,到3就向上回溯。正如文中提到的,可能不是最短路径,所以会涉及到更新结点度的问题。 2. 关于迷宫存储问题。类似于欧拉七桥问题,需要将迷宫抽象成图,每个分叉路口作为顶点,顶点之间连成边,构成一张无向图,可以存储在邻接矩阵或邻 接表中。 5赞 李东勇 2018-12-18 13:5
22、1:11 老师, 我觉得深度优先搜索的代码中有一个可以改进的地方, 可以在21行之后加一句: if (found = true) return; 这样, 在一个顶点的for循环之中, 如果已 经找到了t, 就可以跳出这个for循环了。目前的逻辑是, 这个for循环中剩下的还会继续执行, 每次都调用一次recurDfs函数, 但recurDfs函数在第一行就return 了。 3赞 作者回复2018-12-19 02:00:40 嗯嗯 31|深度和广度优先搜索:如何找出社交网络中的三度好友关系? file:/F/temp/geektime/数据结构与算法之美/31深度和广度优先搜索:如何找出社交
23、网络中的三度好友关系?.html2019/1/15 15:36:08 韩 2018-12-03 00:35:34 我的想法:迷宫可以用带权无向图存储,每个多岔路口是一个顶点,相邻的多岔路口是一条边。带权是为了记录两个顶点间的距离。 但用上面这种方式,会丢失一些拐点信息。可以结合业务场景,如果需要这些信息,就把拐点也作为一个顶点存储。 3赞 轩 2019-01-08 03:19:43 老师的图如果看不懂,可以看看这个https:/ 1赞 qinggeouye 2018-12-30 15:49:19 思考题 2、图的特点:顶点和边(顶点之间的连接关系)。 迷宫,选一个入口作为起始顶点,遇到岔路口,将岔路口作为顶点,并建立连接关系(边),终止条件应该是:到出口 或者 到下一个路口之后不能再前进 为止。这里仍然用深度优先搜索的方法实现。 1赞 + + 2018-12-05 02:44:51 广度优先 有点像树中的层序遍历啊 也是借助一个队列来实现的 1赞 猫头鹰爱拿铁 2018-12-03 09:25:35 我感觉用深度优先查找人脉的算法可能会把小于这个度数的数据查找出来吧,例如查找度数为3的顶点,用文中广度优先搜索的那个数据,使用深度优先的 算法会把0,1,4,3也给查出来。 1赞
链接地址:https://www.31doc.com/p-5529955.html