Graph 查找“;“足够短”;给定图中的路径
我需要设计一个算法来找到公共交通系统中的路径。理论上只需要最好(最低成本)的路径,但实际上是不同的。在公共交通系统中出行时,很难定义成本,不能简化为出行时间,等待时间、换乘时间、公交车/地铁费用等都需要考虑 首先,我需要简化问题,设计一个成本函数,它是所有这些“时间”和“费用”的组合,然后使用图算法找到一些路径(3~5条路径)。最后,向最终用户展示所有这些路径,并让他们做出决策 我需要提供多条路径的原因是,对于不同的用户/情况,这些“时间”和“费用”是不同的,因此提供一些路径比只提供“最佳”路径要好 像A*这样的算法很适合寻找最短路径,但如何在图中找到那些“足够短”的路径呢?或者如何找到最短的N条路径 顺便说一句,我甚至不需要找到最短路径,因为在实践中,最终用户永远不知道最短路径(除非最短路径是显而易见的),如果结果接近最短路径,他们会很高兴。A*star的“成本”比你想象的要多。A*通常用节点来解释,节点的成本只是一个距离。然而,我们可以稍微加强这一点 我没有看到你喜欢的语言,也许是图形?哦,这里有一些c++:Graph 查找“;“足够短”;给定图中的路径,graph,shortest-path,Graph,Shortest Path,我需要设计一个算法来找到公共交通系统中的路径。理论上只需要最好(最低成本)的路径,但实际上是不同的。在公共交通系统中出行时,很难定义成本,不能简化为出行时间,等待时间、换乘时间、公交车/地铁费用等都需要考虑 首先,我需要简化问题,设计一个成本函数,它是所有这些“时间”和“费用”的组合,然后使用图算法找到一些路径(3~5条路径)。最后,向最终用户展示所有这些路径,并让他们做出决策 我需要提供多条路径的原因是,对于不同的用户/情况,这些“时间”和“费用”是不同的,因此提供一些路径比只提供“最佳”路径
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对象来确定路线上的成本。如果转移不重要,请将转移成本设置为零
至于“足够好”的路线:我相信其他人会更好地帮助你,但我觉得你可能会遇到这样的情况:程序会说“哦,你想在一小时内到达那里,但最好的路线只需要二十分钟?在这里,绕圈子四十分钟,这就足够了”.正如我在评论中所暗示的,可以创建一个报告多条路线的修改后的a*版本。我只是将我的实现推进到了一个地步,它显然证实了这一说法 下面的代码以一个“classic”a*实现开始,我保留了这个实现,以便研究“classic”和“modified”之间的差异 修改版本的基本思想是并行地向前和向后开始搜索。考虑到A*的“贪婪性”很大程度上是由其启发式函数(h(x))驱动的,这通常也会产生更稳健的结果。可以构造这样的情况,贪婪者选择在路线开始时快速前进,而走向终点的路线则急剧“减速”。从两侧(源、目标)开始,这种影响可以减轻到比特。(如果计算到终点,则应始终为最佳路线,如果不一定为同一路线。如果计算到两个方向的“经典”终点条件,则可能会产生如下图片,显示两个方向产生两条不同的路径 现在,可以使用两个方向的“探索列表”来确定在搜索(例如“向前”)时,下一个节点已经被“向后”搜索探索了,或者反之亦然。显然,两个搜索之间的“连接点”产生了一条路线,这条路线不一定是最优的,但却是有效的路线 我的实现跟踪了这些中间路线,我没有费心收集它们。跟踪显示了两个探索列表“相遇”的节点的id以及由此产生的路线的两部分(源->集合点,集合点->目的地) 现在,使用这些中间列表以及一些后处理,例如,通过根据启发式函数的单个维度(例如舒适度、速度等)评估路线,应该可以找到足够好的路线选择,与这些维度中的不同权衡相关联 完整的F#脚本大约有340行-对于这个站点来说有点太长了,所以我将省略一些不重要的部分(比如我的渲染功能、创建那些位图等等)
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"
模块AStar=
模块内部构件=
让makeRoute(爆炸:地图)在tgt=
让rec在acc处循环=
设dst,c=expo.[at]
在,dst与
|当b=tgt->(at,c)时::acc
|(u,b)->回路b((at,c)::acc)
[(tgt,0.0)]@循环在[]
让makeRouteBackward(exploBW:Map)在tgt处=
让rec在acc处循环=
设src,c=exploBW。[at]
匹配于,src与
|当b=tgt->acc@[(at,c)]
|(u,b)-->环路b(acc@[at,c])
设r=在[]@[(tgt,0.0)]处循环
让rev=List.rev r
List.zip r rev |>List.map(乐趣((id1,c1)、(id2,c2))->id1,c2)
让h成本开始目标=
让prioSelect(lopen:(int*float)列表)=
让sorted=List.sortBy(fun(id,p)->p)lopen/|>List.rev
(fst(List.head排序)、List.tail排序)
让记录搜索(lopen:(int*float)列表)(路线:地图)=
让rec搜索邻居cur nl o(r:Map)=
将nl与
|[]->o,r
|下一步::其他->
让newCost=(snd(r[cur])+成本cur next
if(not(Map.containsKey next r))| |(newCostMap.remove next |>Map.add next(cur,newCost)
让prio=newCost+h下一个目标
//printfn“当前=%d--next=%d--newCost=%f--prio=%f--h=%f”cur next newCost prio(h next goal)
设o1=(ne