C# 用递归求解有向图

C# 用递归求解有向图,c#,sql-server,recursion,graph,recursive-query,C#,Sql Server,Recursion,Graph,Recursive Query,我正在做一个项目,建立从一个机场到另一个机场的航线。例如,如果有人想从LAX到JFK,我想返回从LAX到JFK的所有可能路径,最多有n个连接。我一直在使用递归CTE将此示例转换为MS SQL这是文档中的最后一个示例: 我已经能够复制结果,这在一开始很好。然而,在大约20条记录上执行基于集合的递归比在几十万条记录上执行相同的任务要快得多 所以我的问题是:有谁能帮我找到一种更快的方法来找到从一个机场到另一个机场的飞行路线?我正在使用MS SQL和.NET从头开始构建这个东西,但我愿意使用任何东西来快

我正在做一个项目,建立从一个机场到另一个机场的航线。例如,如果有人想从LAX到JFK,我想返回从LAX到JFK的所有可能路径,最多有n个连接。我一直在使用递归CTE将此示例转换为MS SQL这是文档中的最后一个示例:

我已经能够复制结果,这在一开始很好。然而,在大约20条记录上执行基于集合的递归比在几十万条记录上执行相同的任务要快得多

所以我的问题是:有谁能帮我找到一种更快的方法来找到从一个机场到另一个机场的飞行路线?我正在使用MS SQL和.NET从头开始构建这个东西,但我愿意使用任何东西来快速返回基于集合的递归、任何语言的迭代等结果

理想情况下,我希望返回结果的速度与Google Flights返回数据的速度一样快

以下是我到目前为止在MS SQL中的rCTE。注意:它只是从纽约创建所有可能的路径。为了将查询范围缩小到从纽约到巴黎的所有航班,我们需要将查询的最后一行更改为from destinations WHERE arrival='Paris'

创建测试表,飞行:

CREATE TABLE FLIGHTS (DEPARTURE nvarchar(20),
                      ARRIVAL nvarchar(20), 
                      CARRIER nvarchar(15),
                      FLIGHT_NUMBER nvarchar(5), 
                      PRICE decimal(18, 0))
将数据插入测试表:

INSERT INTO FLIGHTS VALUES('New York', 'Paris', 'Atlantic', '234', 400)
INSERT INTO FLIGHTS VALUES('Chicago', 'Miami', 'NA Air', '2334', 300)
INSERT INTO FLIGHTS VALUES('New York', 'London', 'Atlantic', '5473', 350)
INSERT INTO FLIGHTS VALUES('London', 'Athens'  , 'Mediterranean', '247', 340)
INSERT INTO FLIGHTS VALUES('Athens', 'Nicosia' , 'Mediterranean', '2356', 280) 
INSERT INTO FLIGHTS VALUES('Paris', 'Madrid' , 'Euro Air',  '3256', 380)
INSERT INTO FLIGHTS VALUES('Paris', 'Cairo' , 'Euro Air', '63', 480)
INSERT INTO FLIGHTS VALUES('Chicago', 'Frankfurt', 'Atlantic', '37', 480)
INSERT INTO FLIGHTS VALUES('Frankfurt', 'Moscow', 'Asia Air', '2337', 580)
INSERT INTO FLIGHTS VALUES('Frankfurt', 'Beijing', 'Asia Air',  '77', 480) 
INSERT INTO FLIGHTS VALUES('Moscow', 'Tokyo', 'Asia Air', '437', 680)
INSERT INTO FLIGHTS VALUES('Frankfurt', 'Vienna', 'Euro Air', '59', 200)
INSERT INTO FLIGHTS VALUES('Paris', 'Rome', 'Euro Air', '534', 340)
INSERT INTO FLIGHTS VALUES('Miami', 'Lima', 'SA Air', '5234', 530)
INSERT INTO FLIGHTS VALUES('New York', 'Los Angeles', 'NA Air', '84', 330)
INSERT INTO FLIGHTS VALUES('Los Angeles', 'Tokyo', 'Pacific Air', '824', 530)
INSERT INTO FLIGHTS VALUES('Tokyo', 'Hong Kong', 'Asia Air', '94', 330)
INSERT INTO FLIGHTS VALUES('Washington', 'Toronto', 'NA Air', '104', 250)
这是rCTE:

WITH

destinations (origin, departure, arrival, flight_count, itinerary) AS    
    (
        SELECT a.departure, a.departure, a.arrival, 1, CAST(a.departure + ', ' + a.arrival AS VARCHAR(2000))
                FROM [FLIGHTS] a
                WHERE a.departure = 'New York'
         UNION ALL
         SELECT r.origin, b.departure, b.arrival, r.flight_count + 1, CAST(r.itinerary + ', ' + b.arrival AS VARCHAR(2000))
                FROM destinations r, [FLIGHTS] b
                WHERE r.arrival = b.departure
                -- prevent cycles by making sure the new arrive airport is not already listed in the itinerary
                and CAST(r.itinerary AS VARCHAR(2000)) NOT LIKE '%' + b.arrival + '%'
                -- the itinerary is a csv str so we can limit the number of hops here
                and (LEN(CAST(r.itinerary AS VARCHAR(2000))) - LEN(REPLACE(CAST(r.itinerary AS VARCHAR(2000)), ',', ''))) < 3
    )

SELECT origin, departure, arrival, flight_count, itinerary 
    FROM destinations

谢谢你的帮助

以下是一些琐碎的观察结果:

如果不使用适当的索引,您将永远无法对此进行优化,即至少需要索引密钥。 与“%”+b不同。arrival+“%”的运行速度不快,很难为此类表达式编制索引。需要一种不同的方法来检查已经列出的机场,也许可以将项目存储在临时表中。 需要另一种限制跳数的方法。因为您只需要三个跃点,所以最好使用3个连接而不是递归,这样会快得多。
这个名单可以继续下去;像Google Flights那样快速构建一些东西并不容易。

除了@Serge的建议之外,您还可以通过将其放入udf中来尝试SQL CLR。@Serge非常感谢您的想法;关于递归在这个问题上的过度使用,您是绝对正确的。我尝试了多个连接0个连接用于直飞航班,1个连接用于1站,2个连接用于2站,并且没有任何索引,关于CPU时间,直飞航班大约为0毫秒,1站188毫秒,2站7457毫秒。使用rCTE,我在6小时后手动停止查询!另外,因为我们不必担心连接的循环,所以您的2个问题就解决了。经过几周的研究,我终于找到了一个坚实的平台。非常感谢你@GayanSanjeewa的链接太棒了!非常感谢。SQLCLR肯定会在这个项目中派上用场。@BrentBarbata很高兴知道它为您解决了问题。祝你好运,万事如意!这看起来是像Neo4j这样的图形数据库的理想候选者。城市将作为节点加载,航班将作为边缘关系加载。在具有最大跳数的节点之间查找路由既简单又快速。@SteveFord我刚刚安装了neo4j。看起来棒极了!NoSQL一直以来都是我想搞乱的东西,现在似乎是一个非常好的切入点。我将尝试关系方法和graph db neo4j方法,当我走到路的尽头时,在这里报告。谢谢很高兴能提供帮助,请看一个查找两个节点之间最短路径的示例。如果您使用的是C,那么可以使用Neo4jClient加载节点和边并执行密码查询。请看一看如何查找跳转次数可变的节点。