我正在考虑以下问题(非常粗略的描述):
假设我们有一个图表,其中边缘被分配了一些非负成本,一个起始节点s
和一些成本常数C
。找出:
N
,可从s
到达,其中从起始节点s
到N
中任何节点的最短路径的开销不大于{ {1}}。C
- 最短路径的费用。基本上Dijkstra有成本约束。
我的主要问题是:图论中这个问题的正确术语是什么?
我一直在关注“辅助功能”或"reachability",但这些似乎是错误的关键字。
最终我正在寻找一种算法,该算法可以在一个(不可修改的)但非常大(可能约1亿个边缘)图形上并行地有效地回答许多这样的查询。我想查看文献,但需要提供正确的关键词提示。
更新:我的实际问题如下。
假设我们获得了一个大陆规模的道路网络(建模为有向图,在边缘和节点上有一些属性,如果它是行人路或高速公路)。边缘成本是旅行时间。
我想回答用户查询,例如:从某个给定位置(图形节点)开始,哪些节点可在1小时内到达?
我也希望对许多用户(如果可能的话,> 10000,如果可能的话)非常快速地(<200-300ms,如果可能的话)回答这些问题。
我认为应该至少有两种可能的优化:
我自己有一些想法,但我首先要查看文献,以避免重新发明轮子。
答案 0 :(得分:4)
我一直在关注“可访问性”或“可达性”,但这些看起来像 错误的关键词。
是的,你是对的。这些是错误的关键字。
首先,让我更准确地重新定义问题。给定图 G(V,E),节点 s 和 a 常量 c ,我们想找到集合 R = {(n,d)| s和n之间的距离是d <= c} 。
此问题是可达性问题的一般化版本,其中 c 是无穷大的,考虑到大型图表会更加困难。
现在作为预计算的一部分,要查找集合 R ,您必须确定 s 与所有其他节点之间的最短路径的长度。这是伪装的所有对最短路径(APSP)问题。
因此,预计算的第一步是在研究库中搜索适合您正在处理的图形类型的真正快速的APSP算法。该算法及其实现决定了预计算运行时间。
第二步是尽可能多地从算法结果中找到一种方式存储,并且尽可能高效,因为您在此处选择的数据结构和算法将决定查询的运行时间。考虑到十亿个顶点,由算法计算的最短路径的数量将是大约10 ^ 18(从,到,距离)三元组。如果每个值的大小为4个字节,那么如果我们将所有这些数据存储在分布式哈希表中(需要额外的存储空间),则需要大约7艾字节。在这种情况下,我们可以实现最多几毫秒的查询时间。
否则,如果您无法存储所有这些数据,则必须压缩数据和/或丢弃其中的一些数据。现在这是一个不同的问题。您可能希望将图形划分为许多小直径的子图(直径必须通过实验确定),然后仅存储子图中心的节点的最短路径信息,并且在查询时您必须重新运行非常有效的SSSP的实现(单源最短路径)。还有许多其他优化技术可以轻松跨越书籍。无论你做什么,达到<200ms的查询时间都是非常具有挑战性的。
在DRAM和本地磁盘中缓存查询结果是一个好主意。如果大部分查询都相同,这可以提供很多帮助。
关于用户数量,由于图表是静态的,因此您可以并行化所有查询。您可以利用高度并行的CPU和GPU。 &gt;并行10000个查询并不简单,但您可以利用图中接近的查询,并可能将多个略有不同的查询合并为一个,然后过滤掉结果。
最后,可以优化您编写的用于处理查询的代码。深入理解编译器优化和计算机体系结构有助于大大缩短查询时间。
答案 1 :(得分:4)
您正在处理的问题的正确术语是&#34;最短路径算法系列&#34;。可达性问题(即Warshal)处理问题&#34;节点A和B之间是否存在路径?&#34;并且它有二进制答案,在这种情况下你不需要权重,你只需要寻找边缘。但在你的情况下,你需要考虑在每种情况下从节点A到节点B的时间。
现在,没有&#34;确切的&#34;适合这个问题,因为Dijktra,Floyd,BFS或DFS上的一个小变化可以用来解决这个问题,但由于图表的大小,你有一个额外的复杂性,这对于理解很重要如何构建您的解决方案。使用哪种算法取决于您具有的时间限制和可用硬件的特定组合。
您的问题有两种算法解决方案(我假设您已将所有边缘存储在某处,并且您可以以某种方式查询此数据库):
在一个理想的(想象的)世界中,你会运行一个Floyd的算法,将结果矩阵保存在像Redis这样的东西中,你可以在不到10毫秒的时间内完成你的请求,如果客户端数量增长,您可以根据需要添加更多redis服务器,因为图表不太可能经常更改(因为在您的特定问题中,您有道路信息)。问题在于解决方案的空间复杂性,最好的事情是所有内容都是预先计算的,这意味着您对请求的响应时间很短。 为了能够实现这方面的一些变化,你需要大量的空间,即使是存储在磁盘上的数据库的redis集群(是磁盘,而不是内存),所有带SSD的服务器都应该足够,这个选项在数量上可以很好地扩展并发客户端的增长和时间响应非常快。但是天气与否可以使用此解决方案取决于图表中的节点数量:即使您需要使用每个边缘预先计算距离,您只需要空间来存储NxN矩阵,N是图表中节点的数量,如果此矩阵适合redis,那么您可以使用此算法并预先计算所有&#34;距离&#34; (在您的情况下,这是所有节点之间的成本a.k.a和#34;旅行时间&#34;)的总和。稍后当您收到请求时,您需要查询生成的矩阵以获取行程时间小于给定值的所有节点,在以redis格式存储此矩阵时,还有一个额外的优化,可以让您获取相当多的节点数快速使用排序集。
然后你有第二个解决方案,即修改Dijktra,BFS或DFS,根据成本和它来修剪搜索。在这种情况下,由于空间复杂度较高,您已经放弃了Floyd,这意味着您的图形在节点和边缘都非常大。在这种情况下,解决方案使用任何算法的变体几乎相同,这使得存储图形的方式不同。可以修改所有3种算法,以便在您想要的时间内有效响应,但是为了支持超过10k的客户端,您用来存储图形的数据库会产生差异。在这里你有另外两个选择:
希望它有所帮助!
答案 2 :(得分:3)
加权图中的最短路径距离为图形提供度量空间的结构。在公制空间术语中,您试图找到以s为中心的半径c的闭合球。也许有一些关于将图处理为计算上易处理的离散度量空间的研究。偏心的概念也可以发挥作用 - 节点的偏心是从该点到任何其他点的最大距离。看起来你正试图找到具有属性的最大子图,该子图中的s的偏心率由c限定。
Dijkstra的算法可以清楚地修改,以提供您正在寻找的东西。如果在任何时候Dijkstra的算法会让你在被访问节点集合中找到一个顶点(已知最终距离的节点),但结果距离超过c,则抛弃该节点而不是添加它到访问过的节点列表。这实际上将修剪从s可到达的节点树。应该合理有效。
答案 3 :(得分:2)
由于您正在寻找预先计算的值和临时结果的优化,我建议您查看类似Floyd-Warshall算法的内容,该算法用于查找所有对最短路径。计算一次后,从第一个结果开始,可以从任何其他节点开始,这样就可以获得所需的所有临时结果。
不幸的是,关于时间复杂度和空间复杂度为O(V ^ 2)的算法是O(V ^ 3),因此它相当昂贵。鉴于您的示例问题听起来非常大,关于节点数量,您可能不希望在整个数据集上运行该算法,除非您想要通过存储预先使用大量内存 - 计算结果。根据人们正在进行的搜索类型,您可以将道路网络分成更小的部分,然后在较小的数据集上运行Floyd-Warshall算法,这样您就不必存储整个结果每时每刻。这只适用于人们寻找小距离的情况,例如1小时之外的地方。如果人们在1000英里范围内搜索任何东西,将其分解成碎片并不会有帮助,并且计算这个不可能在您提供的时间限制内实时完成。尽管你可以做到这一点非常有效,但是搜索范围很大的所有内容都需要花费大量时间来计算(可能比你的时间限制要多得多)。
实际上,最好的解决方案取决于人们如何使用它以及搜索的分散程度。
即使人们正在寻找距离超过100英里的地方,高速公路很可能成为最有效的路线,至少对于大部分旅行来说都是如此,因此可能会忽略长距离的优化较小的道路除了在旅程的开始和结束附近之外,这将显着减少长距离的节点数量,使得Floyd-Warshall消耗的计算时间和内存更加合理。不幸的是,它不会帮助你,因为你仍然需要找到小于给定距离的所有节点。
如果您担心服务器的计算速度和压力,您可能需要限制接受的距离。
答案 4 :(得分:1)
这些算法可行。第一个是快速的软环境,从一个地方到另一个地方的成本是相当大的。第二个不太容易受到影响。
让 G 成为一个节点, v 一个节点,从我们开始, C 成本。
//in case, there is no cycle of cost 0, but in metrical environment there won't be one
GLOBAL SET result = {};
def AchievableNodes(G,v,C):
for each w: e = (v,w) in Edges:
if c(e) < C:
result.addIfNotInResult(w)
AchievableNodes(G, w, C-c(e))
AchievableNodes(G, root, C)
print(result)
GLOBAL ARRAY result[|V|] - initialized with 0s.
def AchievableNodes(G, v, C):
for each w: e = (v,w) in Edges:
if c(e) < C:
if result[w] > result[v] + c(e):
result[w] = result[v] + c(e)
AchievableNodes(G, w, C-c(e))
AchievableNodes(G, v, C)
print(result)
实际上,我强烈建议在某处存储结果。然后你可以渐进地将其改进到恒定时间。