寻找图中最近节点的算法

4
我有一个大型的路网图,假设我知道一个特定的位置(比如源节点),现在我的兴趣是找出所有距离这个已知位置/源节点特定范围内的最近邻居节点。比如说,我想找到所有在已知位置周围20公里范围内的位置(其他节点)。
我知道BFS或Dijkastra算法可以解决这个问题,但我觉得它们在我的应用程序中效率低下,因为我的应用程序需要反复处理这些查询。
那么有没有其他算法或技术可以实现这个目标呢?
假设这是一个带权重的图,节点表示位置,边表示两个相应位置之间的距离。
编辑:Dijkastra算法可以解决这个问题,但是如何存储结果呢?如果我对所有可能的对进行查询后存储结果,缓存大小将会是多少?如何解决空间效率问题?
我也听说过kd树、r树索引等,它们在这种情况下有用吗?
编辑:更进一步地说,我愿意使用neo4j图数据库来制作这张图。我看到neo4j有一个特殊的库叫做“neo4j spatial”,其中使用了R-Tree索引,但我想使用有向图概念而不是空间索引库。那么有没有办法做到这一点呢?

2
你需要更具体一些。你为什么觉得BFS或Dijkstra算法效率低?你要达到什么时间复杂度?这为什么要标记在“java”和“c++”中? - arunmoezhi
diskastra可以解决问题,但是存储结果怎么办呢?如果在一定数量的查询之后为所有可能的配对存储结果,缓存大小会是多少呢?如何解决这种空间效率低下的问题? - K.Nath
added it to the question. - K.Nath
1
由于您拥有道路网络,因此可以使用(纬度,经度)或其他坐标来表示节点,然后将它们投入KD树中,以便快速检索。在大多数情况下,您最终会使用欧几里得距离而不是任意图形权重,但这可能是一个很好的近似值。维基页面底部有许多链接,可用于实现KD树。 - Nicu Stiurca
@ SchighSchagh - 我在考虑用某种坐标表示所有地点。但是你能告诉我如何将欧几里得距离与边的权重相关联吗?我需要正确的方向来使用KD树,以及它们在这种情况下有多有效? - K.Nath
显示剩余2条评论
5个回答

4
你想要使用的是戴克斯特拉算法。它确实恰好做了你想要的事情——从一个源节点开始,找到所有最低成本的节点,直到该成本达到指定大小(例如20km)。

我觉得这些在我的应用程序中效率不高,因为我的应用程序需要反复处理这些查询。

你有考虑过为给定的源节点缓存结果吗?只要图形不发生变化,这些结果就不需要重新计算。
如果你的图形太大,还有一个分层图形的选项——它将图形抽象为部分,并预处理这些部分之间的路径。这里的链接具体指的是A*,但它所使用的抽象方法可以应用于任何搜索方法。 编辑:Mehrdad回答中的可达性节点是使用戴克斯特拉搜索的分层图。

同时也值得考虑是否需要图表。如果您的节点位于线性空间上,并且如果 destination.position - source.position 总是给出精确距离,那么将它们存储在列表中会更快。


2
我看到你提供了一个“分层图”的链接。你能否加上两行介绍它以及为什么你认为它在这里很有用。这样可以使你的回答更加自给自足。 - arunmoezhi
1
@arunmoezhi 已完成。谢谢。 - Jono
我考虑过缓存,但问题是:我的应用程序需要处理数千个节点(比如说超过50,000个节点)。任何节点都可能出现最短路径查询。因此,如果我需要缓存所有结果,我需要消耗大量内存,因为最短路径可能存在于任何可能的节点对中。这可能导致空间效率低下。这就是为什么我正在寻找其他更有效的算法,以便在运行时更加高效,这样如果它们一遍又一遍地运行,它们不会对整个应用程序产生太大影响。 - K.Nath
@K.Nath,我建议你阅读Mehrdad的Transit节点链接。他们使用美国道路网络,具有2400万个节点,由于高预处理和层次抽象,处理时间低于一毫秒。 - Jono

2

我并不是特别了解它们,但这里有一些我听说过的可能会有帮助的技巧:

此外,这里还有一个关于“高速公路维度”的演讲,可用于证明这些技术的时间限制。


0

针对您的初始定义,单源所有目标最短路径并设置停止条件。但是您补充说需要再次调用这些查询,这时问题就变成了全源最短路径图问题。通常使用“动态规划”解决此类问题,Floyd–Warshall algorithm是一个具有O(V^3)时间复杂度和O(V^2)空间复杂度的解决方案示例。

利用Floyd-Warshal算法,应将图根据范围限制进行分区,但是要有重叠区域以消除O(V ^ 2)空间复杂度。

例如:

200公里区域,第一个中心区域为x_1=20公里,y_1=20公里,是一个边长为40公里的正方形。第二个正方形中心在x_2=40公里,y_2=40公里。将正方形分成四个象限。对于每个分区,执行Floyd-Warshall算法。考虑到所有节点,这比O(V^2)空间复杂度更好。根据我的计算,原始算法需要2.5B个节点来存储相关信息,而此处提出的算法仅需要1M个节点即可存储,假设你有50K个节点。

创建结果矩阵后,您将立即访问范围内最近的节点。


0

使用图形数据库方法,我们可以使用infiniteGraph图形数据库和DO查询语言。我们创建一个权重计算器,然后在查询中使用它,查询应该如下所示:

CREATE WEIGHT CALCULATOR shortestRoute {
            minimum:    0,
            default:    0, 
            edges: {
                ()-[r:Road]->(): r.distance
            }
};
    
Match m = max weight 8.0 shortestRoute 
            ((:Town {name == 'A'})-[*..10]->(t:Town)) 
            GROUP BY t.name 
            RETURN t.name as Name;

在此查询中,我们指定了“最大重量”为8.0以及要使用的WEIGHT CALCULATOR。然后,我们指定起始城镇{name == 'A'}和我们想要走出的度数[*..10]。然后,我们指定终点(t:Town),没有谓词,它是标签为“t”的类型为Town的节点。我们按t.name分组,并将t.name作为NAME返回。
此查询中使用的图形为:

enter image description here

查询结果如下:

DO> Match m = max weight 8.0 shortestRoute ((:Town {name == 'A'})-[*..10]->(t:Town)) GROUP BY t.name RETURN t.name as Name;

{
  _Projection
  {
    Name:'B'
  },
  _Projection
  {
    Name:'D'
  },
  _Projection
  {
    Name:'E'
  },
  _Projection
  {
    Name:'F'
  }
}

设置(模式和示例数据)如下:

UPDATE SCHEMA {
    
    CREATE CLASS Town  {
        name                : STRING,
                
        roadsIn             : List { Element: Reference { EdgeClass: Road, EdgeAttribute: from }, CollectionTypeName: TreeListOfReferences },
        roadsOut            : List { Element: Reference { EdgeClass: Road, EdgeAttribute: to }, CollectionTypeName: TreeListOfReferences }      
    }
    
    CREATE CLASS Road {
        name                : String,
    
        from                : Reference { referenced: Town, Inverse: roadsOut },
        to                  : Reference { referenced: Town, Inverse: roadsIn },
                
        distance            : REAL { Storage: B32 },
        avgTravelTime       : REAL { Storage: B32 },
        stopLightCount      : INTEGER { Encoding: Signed, Storage: B16 }
    }
};


let townA = create Town { name: "A" };
let townB = create Town { name: "B" };
let townC = create Town { name: "C" };
let townD = create Town { name: "D" };
let townE = create Town { name: "E" };
let townF = create Town { name: "F" };
let townG = create Town { name: "G" };
let townH = create Town { name: "H" };

let ab = create Road { name: "AB", distance: 4.0, stopLightCount: 1, from: $townA, to: $townB };

let bc = create Road { name: "BC", distance: 5.0, stopLightCount: 2, from: $townB, to: $townC };

let cd = create Road { name: "CD", distance: 6.0, stopLightCount: 3, from: $townC, to: $townD };

let cH = create Road { name: "CH", distance: 10.0, stopLightCount: 0, from: $townC, to: $townH };

let ad = create Road { name: "AD", distance: 3.0, stopLightCount: 0, from: $townA, to: $townD };

let ae = create Road { name: "AE", distance: 4.0, stopLightCount: 3, from: $townA, to: $townE };

let ed = create Road { name: "ED", distance: 2.0, stopLightCount: 1, from: $townE, to: $townD };

let ef = create Road { name: "EF", distance: 4.0, stopLightCount: 7, from: $townE, to: $townF };

let fg = create Road { name: "FG", distance: 3.0, stopLightCount: 0, from: $townF, to: $townG };

let dg = create Road { name: "DG", distance: 8.0, stopLightCount: 6, from: $townD, to: $townG };

let dH = create Road { name: "DH", distance: 8.0, stopLightCount: 9, from: $townD, to: $townH };

let gH = create Road { name: "GH", distance: 8.0, stopLightCount: 4, from: $townG, to: $townH };

查询对可能的结果路径进行快速/早期修剪,因此仅加载其需要的数据以确定结果。


-1
如果图由邻接矩阵或邻接表表示,则只需扫描一行(对于矩阵)或列表(对于邻接表),因此此操作并不复杂。
对于具有n个节点的图,邻接矩阵称为graph [][], 将是大小为n * n的, 如果源节点是s, 然后简单地扫描graph [s] [i],其中i从0到n-1,并检查graph [s] [i]<=DISTANCE

  1. 图表通常用你列出的两种数据结构之一来表示。那么你的观点是什么?
  2. 通过只扫描一行,你只能得到一跳邻居。
- arunmoezhi
这不是一个可接受的答案。我需要找出所有在范围内的位置。这并不意味着我想要找到所有仅通过一条边直接连接到源节点的节点。距离是关键因素,而不是两个节点之间有多少条边。 - K.Nath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接