首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在给定的图中找到“足够短”的路径

在给定的图中找到“足够短”的路径
EN

Stack Overflow用户
提问于 2015-04-27 02:46:46
回答 2查看 88关注 0票数 1

我需要设计一种算法来寻找公共交通系统中的路径。理论上,只有最好(最低成本)的路径是必需的,但在现实中它是不同的。在公交系统中出行时,成本难以确定,不能简化为出行时间,等待时间、换乘时间、公交/地铁费用等都需要考虑。

首先对问题进行简化,设计出一种集时间和费用为一体的代价函数,然后用图算法找出几条路径(3~5条路径)。最后,向最终用户展示所有这些路径,并让他们做出决定。

我需要提出不止一条路径的原因是,对于不同的用户/情况,这些“时间”和“费用”是不同的,因此,提供几条路径比仅仅提供“最佳”路径要好得多。

像A*这样的算法可以很好地找到最短路径,但是如何在图中找到那些“足够短”的路径呢?或者怎样才能找到最短的N条路?

顺便说一句,我甚至不需要找到最短的路径,因为在实践中,最终用户永远不知道最短路径(除非最短路径是显而易见的),如果结果接近最短路径,他们会很高兴。

EN

回答 2

Stack Overflow用户

发布于 2015-04-27 03:14:50

明星的“成本”比你想象的要多。一个*通常是用节点来解释的,节点的成本只是一个距离。不过,我们可以把这件事放大一点。

我没看到你喜欢的语言,也许是图形?哦,好吧,这是一些c++:

代码语言:javascript
运行
复制
namespace Astar
{
  struct CostEvaluation
  {
    int distance_cost;
    int transfer_cost;
    // others

    int costToTraverseNodes( const Node& first, const Node& second ) const
    {
      int distance = // apply distance_cost to distance between nodes
      int transfer = // apply transfer_cost if there is a transfer between nodes

      return distance + transfer;
    }
  }
}

现在,A*的实际实现将使用一个CostEvaluation对象来确定路由上的成本。如果传输不重要,请将transfer_cost设置为零。

至于“足够好”的路线:我相信其他人能更好地帮助你,但我觉得你可能会遇到这样的情况:“哦,你想在一小时内到那里,但最好的路线只需要20分钟?在这里,绕着圈转40分钟就够了。”

票数 0
EN

Stack Overflow用户

发布于 2015-04-29 10:54:06

正如我在评论中所暗示的那样,可以创建一个经过修改的A*版本,该版本报告多个路由。我只是推动了我的执行工作,显然是在确认这一声明。

下面的代码是从一个“经典”A*实现开始的,我把它保存下来,这样我们就可以研究“经典”和“修改”之间的区别。

修改后的版本的基本思想是向前和向后并行地开始搜索。考虑到A*的“贪婪”很大程度上是由它的启发式函数(h(x))驱动的,这通常也会产生更稳健的结果。这是有可能的,在这种情况下,贪婪选择快速的进展在路线开始,而这条路线的终点“减速”急剧。从双方(源,目标)开始,这一影响可以减轻到比特。(如果计算到最后,它应该是最佳路线,如果不一定是同一条路线的话)。如果要计算到两个方向上的“经典”结束条件,就会产生一幅图片,如下所示,说明这两个方向产生了两条不同的路径。

现在,两个方向的“探索列表”可以用来找出当搜索例如“转发”时,下一个节点已经被“反向”搜索发现了--反之亦然。显然,这两个搜索之间的“连接点”产生了一条路由,这不一定是最优的,而是一条有效的路由。

我的实现追踪了这些中间路线,我没有费心去收集它们。这些跟踪显示了节点的id,其中两个探测列表都是"meet“以及路由的两个部分(源->接入点、接入点->目的地)。

现在,使用这些中间列表以及一些后处理,例如,通过根据启发式函数的单个维度(例如舒适度、速度、.)评估路由,应该可以找到足够好的路径选择,并在这些维度中进行不同的权衡。

完整的F#脚本大约有340行--对这个站点来说有点太长了,所以我将省略一些不必要的部分(比如我的呈现功能,创建那些位图等等)。

代码语言:javascript
运行
复制
module AStar =
    module Internals =
        let makeRoute (explo : Map<int,(int * float)>) at tgt =
            let rec loop at acc =
                let dst,c = explo.[at] 
                match at,dst with
                | (_,b) when b = tgt -> (at,c) :: acc
                | (_,b) -> loop b ((at,c) :: acc)
            [(tgt,0.0)] @ loop at [] 

        let makeRouteBackward (exploBW : Map<int, (int * float)>) at tgt =
            let rec loop at acc = 
                let src,c = exploBW.[at]
                match at,src with
                | (_,b) when b = tgt -> acc @ [(at,c)]
                | (_,b) -> loop b (acc @ [at,c])
            let r = loop at [] @ [(tgt,0.0)]
            let rev = List.rev r
            List.zip r rev |> List.map (fun ((id1,c1),(id2,c2)) -> id1,c2) 

    let classic neighbors h cost start goal =
        let prioSelect (lopen : (int * float) list) =
            let sorted = List.sortBy (fun (id,p) -> p) lopen //|> List.rev
            (fst (List.head sorted), List.tail sorted)
        let rec search (lopen : (int * float) list) (routes : Map<int,int * float>) =
            let rec searchNeighbors cur nl o (r : Map<int,(int * float)>) =
                match nl with
                | [] -> o,r
                | next::others -> 
                    let newCost = (snd (r.[cur])) + cost cur next
                    if (not (Map.containsKey next r)) || (newCost < snd r.[next])
                    then
                        let r1 = r |> Map.remove next |> Map.add next (cur,newCost)
                        let prio = newCost + h next goal
                        //printfn "current = %d -- next = %d -- newCost = %f -- prio = %f -- h = %f" cur next newCost prio (h next goal)
                        let o1 = (next,prio) :: o
                        searchNeighbors cur others o1 r1
                    else
                        searchNeighbors cur others o r
            match lopen with 
            | [] -> []
            | _::_ ->
                let current,rest = prioSelect lopen
                if current = goal then Internals.makeRoute routes current start
                else
                    let lopen1,routes1 = searchNeighbors current (neighbors current) rest routes
                    search lopen1 routes1
        search [start,0.] (Map.ofList [start,(start,0.0)])

    let twoWay sources targets hforward hbackward costforward costbackward (start : int) (goal : int) (n : int) rr =
        let prioSelect (lopen : (int * float) list) =
            let sorted = List.sortBy (fun (id,p) -> p) lopen //|> List.rev
            (fst (List.head sorted), List.tail sorted)
        let searchforward lopen exploredF exploredB nfound acc =
            let rec searchNeighbors cur nl o (r : Map<int,(int * float)>) =
                match nl with
                | [] -> o,r
                | next::others -> 
                    //printfn "fwd: current = %d -- next = %d -- nl = %A  -- r = %A" cur next nl r
                    let newCost = (snd (r.[cur])) + costforward cur next
                    if (not (Map.containsKey next r)) || (newCost < snd r.[next])
                    then
                        let r1 = r |> Map.remove next |> Map.add next (cur,newCost)
                        let prio = newCost + hforward next goal
                        let o1 = (next,prio) :: o
                        if Map.containsKey next exploredB then
                            rr (next, Internals.makeRoute r1 next start, Internals.makeRouteBackward exploredB next goal)
                        searchNeighbors cur others o1 r1
                    else
                        searchNeighbors cur others o r
            match lopen with 
            | [] -> (lopen,exploredF,0,acc)
            | _::_ ->
                let current,rest = prioSelect lopen
                if current = goal then
                   (rest,exploredF,nfound+1,acc @ [Internals.makeRoute exploredF current start] )
                else
                    let lopen1,explored1 = searchNeighbors current (targets current) rest exploredF
                    (lopen1, explored1, nfound, acc)
        let searchbackward lopen exploredB exploredF nfound acc = 
            let rec searchNeighbors cur nl o (r : Map<int,(int * float)>) =
                match nl with
                | [] -> o,r
                | next::others -> 
                    //printfn "bwd: current = %d -- next = %d -- nl = %A  -- r = %A" cur next nl r
                    let newCost = (snd (r.[cur])) + costbackward cur next
                    if (not (Map.containsKey next r)) || (newCost < snd r.[next])
                    then
                        let r1 = r |> Map.remove next |> Map.add next (cur,newCost)
                        let prio = newCost + hbackward next start
                        let o1 = (next,prio) :: o
                        searchNeighbors cur others o1 r1
                    else
                        searchNeighbors cur others o r
            match lopen with 
            | [] -> (lopen,exploredB,0,acc)
            | _::_ ->
                let current,rest = prioSelect lopen
                if current = start then
                   //(rest,explored,nfound+1,acc @ [Internals.makeRoute explored current goal []])
                   (rest,exploredB,nfound+1,acc @ [Internals.makeRouteBackward exploredB current goal] )
                else
                    let lopen1,explored1 = searchNeighbors current (sources current) rest exploredB
                    (lopen1, explored1, nfound, acc)
        let rec driver openF openB exploredF exploredB nfoundF nfoundB accF accB =
            let openF1, exploredF1,nfoundF1,accF1 = searchforward openF exploredF exploredB nfoundF accF
            let openB1, exploredB1,nfoundB1,accB1 = searchbackward openB exploredB exploredF nfoundB accB

            match (nfoundF1+nfoundB1), List.isEmpty openF1, List.isEmpty openB1 with
            | (s,false,false) when s < n -> 
                driver openF1 openB1 exploredF1 exploredB1 nfoundF1 nfoundB1 accF1 accB1
            | _ -> 
                accF1 @ accB1
        driver [start,0.0] [goal,0.0] (Map.ofList [start,(start,0.0)]) (Map.ofList [goal,(goal,0.0)]) 0 0 [] []


// Location : x,y coordinate or lat/long - whatever.
// Edges: (id,cost) list
type Node = { Id : int; Location : int * int; Edges : (int * float) list; EdgesBackward : (int * float) list}

type Graph = Map<int,Node>

let addNode node graph =
    Map.add (node.Id) node graph

let newNode idgen x y  = 
    { Id = idgen(); Location = (x,y); Edges = []; EdgesBackward = [] }

let addEdge id cost node =
    { node with Node.Edges = node.Edges @ [(id,cost)]; }

let addEdgeBackward id cost node =
    { node with Node.EdgesBackward = node.EdgesBackward @ [(id,cost)]; }

let idgen startvalue =
    let next = ref startvalue
    fun () -> 
        let id = !next
        next := !next + 1
        id

let appendNode node nodeList = nodeList @ [node]

let sq x = x*x
let distance p1 p2 =
    let x1,y1 = p1
    let x2,y2 = p2
    sqrt( float (sq (x2-x1) + sq (y2-y1)) )

let solve (g : Graph) s e =
    let ns id =
        g.[id].Edges |> List.map (fun (id,c) -> id)
    let h at goal =
        float (distance (g.[at].Location) (g.[goal].Location))
    let c a b =
        g.[a].Edges |> List.pick (fun (id,cost) -> if id = b then Some(cost) else None)
    [AStar.classic ns h c s e]  // give it the same return type as solveTwoWay to make stuff below easier and shorter

let solveTwoWay (g : Graph) s e n =
    let edges id =
        let nl = g.[id].Edges |> List.map (fun (id,c) -> id)
        //printfn "2way edges id = %d list = %A" id nl
        nl
    let edgesBackward id =
        let nl = g.[id].EdgesBackward |> List.map (fun (id,c) -> id)
        //printfn "2way backwards edges id = %d list = %A" id nl
        nl
    let hforward at goal =
        float (distance (g.[at].Location) (g.[goal].Location))
    let hbackward at start =
        float (distance (g.[at].Location) (g.[start].Location))
    let costF a b =
        g.[a].Edges |> List.pick (fun (id,cost) -> if id = b then Some(cost) else None)
    let costB a b =
        g.[a].EdgesBackward |> List.pick (fun (id,cost) -> if id = b then Some(cost) else None)
    let  debugView arg =
        let id,r1,r2 = arg
        printfn "meeting at %d: r1 = %A r2 = %A" id r1 r2

    AStar.twoWay edgesBackward edges hforward hbackward costF costB s e n debugView
let solveProblem problem =
    let g, start, goal = problem
    g,start,goal,solve g start goal

let solveProblemTwoWay problem n =
    let g, start, goal = problem
    g,start,goal,solveTwoWay g start goal n

let save name solution =
    let graph, start, goal, routes = solution
    use  writer = System.IO.File.CreateText("""E:\temp\""" + name + """.txt""")
    fprintf writer "------------------------------------\n start = %d ----> goal = %d: %d routes found.\n" start goal (List.length routes)
    fprintf writer "Graph:\n"
    graph |> Map.iter
        (fun id node ->
            fprintf writer "Node: %A\n" node
        )
    routes |> List.iteri 
        (fun index route ->
            fprintf writer "Route %d: %A\n" index route
        )
// An example problem I used to play with:
// The graph is such, that  the nodes are connected to the right and     
// downwards and diagonally downwards only. 
// The cost is either 1.0 or sqrt(2), for the horizontal or vertical and
// the diagonal connection, respectively.
let problem2 () =
    let newNodeAN = newNode (idgen 0)
    let cond c x n =
        if c then n |> x else n
    let accessCost p =
        match p with
        | (4,4) | (4,5) | (5,4) | (5,5) -> 10.0
        | _ -> 1.0
    let right (n : Node) : Node =
        let t = 1 + fst n.Location, snd n.Location
        let c = accessCost t
        n 
        |> cond (fst n.Location < 9) (fun n -> addEdge (n.Id + 1) c n)
        |> cond (fst n.Location > 0) (fun n -> addEdgeBackward (n.Id - 1) c n)
    let down n =
        let t = fst n.Location, 1 + snd n.Location
        let c = accessCost t
        n
        |> cond (snd n.Location < 9) (fun n -> addEdge (n.Id + 10) c n)
        |> cond (snd n.Location > 0) (fun n -> addEdgeBackward (n.Id - 10) c n)
    let diagdown n =
        let t = 1 + fst n.Location, 1 + snd n.Location
        let c = (sqrt(2.0)) * accessCost t
        n
        |> cond (fst n.Location < 9 && snd n.Location < 9) (fun n -> addEdge (n.Id + 11) c n)
        |> cond (fst n.Location > 0 && snd n.Location > 0) (fun n -> addEdgeBackward (n.Id - 11) c n)

    [
        for y = 0 to 9 do
            for x = 0 to 9 do
                yield newNodeAN x y
    ]
    |> List.map 
        (fun n ->
            n 
            |> right
            |> down
            |> diagdown 
        )
    |> List.map (fun n -> (n.Id,n))
    |> Map.ofList
    , 0, 99

// Last not least, the code can be executed like this:
// And since both implementations yield the same data structures,
// they can be used interchangeably and compared to each other.
solveProblemTwoWay (problem2() 5) |> save "problem2_solution"

运行时打印的输出显示“中间路由”,然后如下所示:

..。 第48次会议: r1 = (0,0.0);(11,1.414213562);(12,2.414213562);(23,3.828427125);(34,5.242640687);(35,6.242640687);(46,7.656854249);(47,8.656854249);(48,9.656854249) r2 = (48,0.0);(58,1.414213562);(68,2.414213562);(78,3.414213562);(88,4.414213562);(99,5.414213562) 第84次会议: r1 = (0,0.0);(11,1.414213562);(21,2.414213562);(32,3.828427125);(43,5.242640687);(53,6.242640687);(64,7.656854249);(74,8.656854249);(84,9.656854249) r2 = (84,0.0);(85,1.414213562);(86,2.414213562);(87,3.414213562);(88,4.414213562);(99,5.414213562) 在95时举行会议: r1 = (0,0.0);(11,1.414213562);(21,2.414213562);(32,3.828427125);(43,5.242640687);(53,6.242640687);(64,7.656854249);(75,9.071067812);(85,10.07106781);(95,11.07106781) r2 = (95,0.0);(96,1.0);(97,2.0);(98,3.0);(99,4.0) ..。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29886374

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档