1010from matplotlib .transforms import Affine2D , ScaledTranslation
1111from mpl_toolkits .mplot3d import proj3d
1212import copy
13+ import heapq
14+ from collections import deque
1315from collections .abc import Iterable , Iterator
1416import tempfile
1517import webbrowser
@@ -3074,7 +3076,10 @@ def path_BFS(
30743076
30753077 # we use lists not sets since the order is instructive in verbose
30763078 # mode, really need ordered sets...
3077- frontier : list [Vertex ] = [S ]
3079+ # the frontier is a queue; ``seen`` is everything that has been on it,
3080+ # so that "frontier or explored" is a set lookup, not a list scan
3081+ frontier : deque [Vertex ] = deque ([S ])
3082+ seen : set [Vertex ] = {S }
30783083 explored : list [Vertex ] = []
30793084 parent : dict [Vertex , Vertex ] = {}
30803085 done = False
@@ -3085,7 +3090,7 @@ def path_BFS(
30853090 print ("FRONTIER:" , ", " .join ([str (v .name ) for v in frontier ]))
30863091 print ("EXPLORED:" , ", " .join ([str (v .name ) for v in explored ]))
30873092
3088- x = frontier .pop ( 0 )
3093+ x = frontier .popleft ( )
30893094 if verbose :
30903095 print (" expand" , x .name )
30913096
@@ -3099,8 +3104,9 @@ def path_BFS(
30993104 parent [n ] = x
31003105 done = True
31013106 break
3102- if n not in frontier and n not in explored :
3107+ if n not in seen :
31033108 # add it to the frontier
3109+ seen .add (n )
31043110 frontier .append (n )
31053111 if verbose :
31063112 print (" add" , n .name , "to the frontier" )
@@ -3122,8 +3128,9 @@ def path_BFS(
31223128 while x is not S :
31233129 p = parent [x ]
31243130 length += self ._require_cost (p .edgeto (x ))
3125- path .insert ( 0 , p )
3131+ path .append ( p )
31263132 x = p
3133+ path .reverse ()
31273134
31283135 if summary or verbose :
31293136 print (
@@ -3184,8 +3191,14 @@ def path_UCS(
31843191 S = self ._resolve_vertex (S , "start" )
31853192 G = self ._resolve_vertex (G , "goal" )
31863193
3187- frontier : list [Vertex ] = [S ]
3188- explored : list [Vertex ] = []
3194+ # The frontier is a dict, used as an ordered set: its order is the order
3195+ # vertices first joined it, which breaks ties between equal ``f``, and
3196+ # its value is that position. ``heap`` finds the minimum ``f``; a
3197+ # vertex that is reparented is pushed again, and the older entry is
3198+ # recognised as stale when it is popped.
3199+ frontier : dict [Vertex , int ] = {S : 0 }
3200+ heap : list [tuple [float , int , Vertex ]] = [(0 , 0 , S )]
3201+ explored : dict [Vertex , None ] = {}
31893202 parent : dict [Vertex , Vertex ] = {}
31903203 f : dict [Vertex , float ] = {S : 0 } # evaluation function
31913204
@@ -3197,8 +3210,11 @@ def path_UCS(
31973210 )
31983211 print ("EXPLORED:" , ", " .join ([str (v .name ) for v in explored ]))
31993212
3200- i = np .argmin ([f [n ] for n in frontier ]) # minimum f in frontier
3201- x = frontier .pop (i )
3213+ while True : # minimum f in frontier, skipping stale heap entries
3214+ fx , _ , x = heapq .heappop (heap )
3215+ if x in frontier and f [x ] == fx :
3216+ break
3217+ del frontier [x ]
32023218 if verbose :
32033219 print (" expand" , x .name )
32043220 if x is G :
@@ -3212,7 +3228,8 @@ def path_UCS(
32123228 # add it to the frontier
32133229 parent [n ] = x
32143230 f [n ] = fnew
3215- frontier .append (n )
3231+ frontier [n ] = len (parent ) # one entry per vertex added so far
3232+ heapq .heappush (heap , (fnew , frontier [n ], n ))
32163233 if verbose :
32173234 print (" add" , n .name , "to the frontier" )
32183235
@@ -3226,8 +3243,9 @@ def path_UCS(
32263243 )
32273244 f [n ] = fnew
32283245 parent [n ] = x
3246+ heapq .heappush (heap , (fnew , frontier [n ], n ))
32293247
3230- explored . append ( x )
3248+ explored [ x ] = None
32313249 if verbose :
32323250 print (" move" , x .name , "to the explored list" )
32333251 else :
@@ -3242,8 +3260,9 @@ def path_UCS(
32423260 while x is not S :
32433261 p = parent [x ]
32443262 length += self ._require_cost (p .edgeto (x ))
3245- path .insert ( 0 , p )
3263+ path .append ( p )
32463264 x = p
3265+ path .reverse ()
32473266
32483267 parent_names : dict [str , str ] = {}
32493268 for v , p in parent .items ():
@@ -3310,8 +3329,10 @@ def path_Astar(
33103329 S = self ._resolve_vertex (S , "start" )
33113330 G = self ._resolve_vertex (G , "goal" )
33123331
3313- frontier : list [Vertex ] = [S ]
3314- explored : list [Vertex ] = []
3332+ # see path_UCS for the frontier and heap
3333+ frontier : dict [Vertex , int ] = {S : 0 }
3334+ heap : list [tuple [float , int , Vertex ]] = [(0 , 0 , S )]
3335+ explored : dict [Vertex , None ] = {}
33153336 parent : dict [Vertex , Vertex ] = {}
33163337 g : dict [Vertex , float ] = {S : 0 } # cost to come
33173338 f : dict [Vertex , float ] = {S : 0 } # evaluation function
@@ -3324,8 +3345,11 @@ def path_Astar(
33243345 )
33253346 print ("EXPLORED:" , ", " .join ([str (v .name ) for v in explored ]))
33263347
3327- i = np .argmin ([f [n ] for n in frontier ]) # minimum f in frontier
3328- x = frontier .pop (i )
3348+ while True : # minimum f in frontier, skipping stale heap entries
3349+ fx , _ , x = heapq .heappop (heap )
3350+ if x in frontier and f [x ] == fx :
3351+ break
3352+ del frontier [x ]
33293353 if verbose :
33303354 print (" expand" , x .name )
33313355 if x is G :
@@ -3336,10 +3360,11 @@ def path_Astar(
33363360 continue # self-loop, never part of a shortest path
33373361 if n not in frontier and n not in explored :
33383362 # add it to the frontier
3339- frontier .append (n )
33403363 parent [n ] = x
3364+ frontier [n ] = len (parent ) # one entry per vertex added so far
33413365 g [n ] = g [x ] + self ._require_cost (e ) # update cost to come
33423366 f [n ] = g [n ] + n .heuristic_distance (G ) # heuristic
3367+ heapq .heappush (heap , (f [n ], frontier [n ], n ))
33433368 if verbose :
33443369 print (" add" , n .name , "to the frontier" )
33453370 elif n in frontier :
@@ -3355,8 +3380,9 @@ def path_Astar(
33553380 f [n ] = g [n ] + n .heuristic_distance (G ) # heuristic
33563381
33573382 parent [n ] = x # reparent
3383+ heapq .heappush (heap , (f [n ], frontier [n ], n ))
33583384
3359- explored . append ( x )
3385+ explored [ x ] = None
33603386 if verbose :
33613387 print (" move" , x .name , "to the explored list" )
33623388
@@ -3372,8 +3398,9 @@ def path_Astar(
33723398 while x is not S :
33733399 p = parent [x ]
33743400 length += self ._require_cost (p .edgeto (x ))
3375- path .insert ( 0 , p )
3401+ path .append ( p )
33763402 x = p
3403+ path .reverse ()
33773404
33783405 parent_names : dict [str , str ] = {}
33793406 for v , p in parent .items ():
0 commit comments