点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”
导读作者:Vardan Grigoryan
编译:ronghuaiyang
知识图谱是AI领域非常有用的一种工具,知识图谱的基础就是图论,从今天开始,给大家介绍一些图论的基础内容,今天是第8篇,Netflix和亚马逊: 反向索引的例子。
遍历: 深度优先和广度优先
如果你不熟悉这个问题,请考虑一些数据结构,以便在遍历树时存储节点。如果将树的逐层遍历与上面的其他遍历(pre、in、post order遍历)进行比较,我们最终将设计两个图的主要遍历,即深度优先搜索(DFS)和宽度优先搜索(BFS)。
深度优先搜索搜索最远的节点,广度优先搜索搜索最近的节点。
深度优先搜索 -多行动,少思考。
广度优先搜索 -在继续前进之前,先好好看看你的周围。
DFS非常类似于前、中、后顺序遍历。而如果我们想逐层打印树的节点,则需要BFS。
为此,我们需要一个队列(数据结构)来存储图的“级别”,同时打印(访问)它的“父级”。在前面的插图中,放置在队列中的节点是淡蓝色的。
基本上,逐层访问时,每个层上的节点都是从队列中获取的,并且在访问每个获取的节点时,还应该将其子节点插入队列(用于下一层)。下面的代码非常简单,足以理解BFS的主要思想。假设图是连接的,尽管可以修改它以应用于断开连接的图。
// Assuming graph is connected// and a graph node is defined by this structure// struct GraphNode {// T item;// vector children;// }// WARNING: untested codevoid BreadthFirstSearch(GraphNode* node) // start node{ if (!node) return; queue<GraphNode*> q; q.push(node); while (!q.empty()) { GraphNode* cur = q.front(); // doesn't pop q.pop(); for (auto child : cur->children) { q.push(child); } // do what you want with current node cout << cur->item; }}
基本思想很容易在基于节点的连通图表示上显示出来。只要记住,图遍历的实现在不同的表示之间是不同的。
BFS和DFS是处理图搜索问题的重要工具。虽然DFS具有优雅的递归实现,迭代实现它是合理的。对于BFS的迭代实现,我们使用队列,对于DFS,你需要一个堆栈。图中最常见的问题之一,同时也是你在本文中阅读到的最可能的原因之一,就是图顶点之间的最短路径的查找问题。这是我们最后一个思想实验。
Uber以及最短路径问题 (Dijkstra’s算法)
拥有5000万用户和700万司机,对优步的运作至关重要的最重要的事情之一,就是以高效的方式将司机和乘客匹配起来。问题始于位置。
后端应该处理数百万用户请求,将每个请求发送给附近的一个或多个(通常是更多)司机。虽然将用户请求发送到所有附近的司机更容易,有时甚至更智能,但一些预处理实际上可能会有所帮助。
除了处理传入的请求并根据用户坐标找到位置区域,然后找到距离最近的司机,我们还需要找到合适的司机。为了避免地理空间请求处理(通过比较当前汽车的坐标和用户的坐标来获取附近的汽车),假设我们已经有了用户和几个附近汽车的地图片段。
比如这样:
从汽车到用户的可能路径是黄色的。问题是计算汽车到达用户所需要的最小距离,换句话说,找到他们之间的最短路径。虽然这是更多关于谷歌地图,而不是Uber,我们将设法解决它对于这个特定的和非常简化的情况主要是因为通常有不止一个司机的车,Uber想计算具有最高的评级最近的汽车将其发送给用户。
对于这个例子,这意味着计算所有三辆车到达用户的最短路径,并决定发送哪辆车是最优的。为了让事情变得简单,我们只讨论一辆车。下面是一些可以到达用户的可能路径。
言归正传,我们把这一段用图表示出来:
这是一个无向加权图(更具体地说,是边加权图)。为了找到顶点B(汽车)和顶点A(用户)之间的最短路径,我们应该在它们之间找到一条由可能具有最小权值的边组成的路径。你可以自由地设计自己的解决方案。我们将坚持使用Dijkstra 's version。
让我们开始的节点称为初始节点。设节点Y的距离为初始节点到Y的距离。Dijkstra算法会分配一些初始距离值,并尝试逐步改进。
标记所有未访问的节点。创建所有未访问节点的集合,称为未访问集。
为每个节点分配一个临时距离值:对于初始节点将其设置为零,对于所有其他节点将其设置为无穷大。将初始节点设置为current。
对于当前节点,考虑其所有未访问的邻居,并计算它们通过当前节点的临时距离。将新计算的临时距离与当前分配的值进行比较,并分配较小的值。例如,如果当前节点A的距离为6,并且连接它与邻居B的边的长度为2,那么从B到A的距离为6 + 2 = 8。如果B之前标记的距离大于8,那么将其改为8。否则,保留当前值。
当我们考虑到当前节点的所有邻居时,将当前节点标记为已访问,并将其从未访问集中删除。已访问的节点将不再被检查。
如果目标节点已被标记为已访问(在规划两个特定节点之间的路由时),或者未访问集中节点之间的最小临时距离为无穷大(在规划完整遍历时;当初始节点和剩余未访问节点之间没有连接时发生),然后停止。算法完成。
否则,选择标号为最小临时距离的未访问节点,设置为新的“当前节点”,返回步骤3。
将此应用到我们的示例中,我们将以顶点B(汽车)作为初始节点开始。前两步:
我们的未访问集由所有顶点组成。还要注意插图左侧的表格。对于所有的顶点,它将包含从B到前面(标记为“Prev”)顶点的所有最短距离。例如从B到F的距离是20,之前的顶点是B。
我们将B标记为已访问,并将其移动到它的邻居F。
现在我们将F标记为已访问节点,并选择下一个临时距离最小的未访问节点G,也请注意左边的表格。在前面的示例中,节点C、F和G已经与前面的节点设置了它们的临时距离,这些距离将带我们找到前面提到的节点。
如算法中所述,如果目标节点已被标记为已访问(在我们的示例中,在规划两个特定节点之间的路由时),则可以停止。因此,我们的下一步将使用以下值停止算法。
所以我们有从B到A的最短距离以及通过F和G节点的路径。
这真的是优步问题的最简单的例子,与我们的冰山类比相比,我们处在冰山一角。然而,这是探索图论及其应用的现实世界的良好开端。我没有完成本文最初的计划,但是在不久的将来,很可能会继续这样做(还包括数据库索引内部结构)。
关于图表还有很多东西要讲(仍然需要学习)。把这篇文章当作另一个冰山一角。如果你读到这里,不要忘记鼓掌和分享。谢谢你!
— END—英文原文:https://medium.com/free-code-camp/i-dont-understand-graph-theory-1c96572a1401
请长按或扫描二维码关注本公众号
喜欢的话,请给我个好看吧!