A Simple Implementation Technique for Priority Search Queues RALF HINZE Institute of Information and Computing Sciences Utrecht University Email: ralf@cs.uu.nl Homepage: http://www.cs.uu.nl/~ralf/ April, 2001 (Pick the slides at.../~ralf/talks.html#t22.)
Aim of the talk advertize priority search queues describe a new implementation technique for priority search queues promote views 1
Recap: views A view allows any type to be viewed as a free data type. The following view (minimum view) allows any list to be viewed as an ordered list. view (Ord a) [a ] = Empty Min a [a ] where [ ] Empty a 1 : Empty Min a 1 [ ] a 1 : Min a 2 as a 1 a 2 Min a 1 (a 2 : as) otherwise Min a 2 (a 1 : as). A view declaration for a type T consists of an anonymous data type, the view type, and an anonymous function, the view transformation, that shows how to map elements of T to the view type. 2
Recap: views (continued) The view constructors, Empty and Min, can now be used to pattern match elements of type [a ] (where a is an instance of Ord). selection-sort :: (Ord a) [a ] [a ] selection-sort Empty = [ ] selection-sort (Min a as) = a : selection-sort as. 3
Priority search queues: signature Priority search queues are conceptually finite maps that support efficient access to the binding with the minimum value, where a binding is an argument-value pair and a finite map is a finite set of bindings. Bindings are represented by the following data type: data k p = k p key :: (k p) k key (k p) = k prio :: (k p) p prio (k p) = p. 4
data PSQ k p -- constructors :: PSQ k p { } :: (k p) PSQ k p insert :: (k p) PSQ k p PSQ k p from-ord-list :: [k p ] PSQ k p -- destructors view PSQ k p = Empty Min (k p) (PSQ k p) delete :: k PSQ k p PSQ k p -- observers lookup :: k PSQ k p Maybe p to-ord-list :: PSQ k p [k p ] -- modifier adjust :: (p p) k PSQ k p PSQ k p 5
Application: single-source shortest path Dijkstra s algorithm maintains a queue that maps each vertex to its estimated distance from the source and works by repeatedly removing the vertex with minimal distance and updating the distances of its adjacent vertices. The update operation is typically called decrease: decrease :: (k p) PSQ k p PSQ k p decrease (k p) q = adjust (min p) k q decrease-list :: [k p ] PSQ k p PSQ k p decrease-list bs q = foldr decrease q bs. 6
Application: single-source shortest path (continued) type Weight = Vertex Vertex Double dijkstra :: Graph Weight Vertex [Vertex Double ] dijkstra g w s = loop (decrease (s 0) q 0 ) where q 0 = from-ord-list [v + v vertices g ] loop Empty = [ ] loop (Min (u d) q) = (u d) : loop (decrease-list bs q) where bs = [v d + w u v v adjacent g u ] 7
Implementation: tournament trees Lennart 1 Lennart 1 Paul 3 John 2 Lennart 1 Paul 3 Simon 5 Erik 4 John 2 Lennart 1 Mark 6 Paul 3 Richard 7 Simon 5 Thomas 8 8
Heaps priority search trees Lennart 1 John 2 Paul 3 Erik 4 Mark 6 Richard 7 Simon 5 Thomas 8 9
Semi-heaps priority search pennants Lennart 1 Paul 3 John 2 Simon 5 Erik 4 Mark 6 Richard 7 Thomas 8 10
Priority search pennants: adding split keys L1 T P 3 M J2 J S5 R E4 E M6 L R7 P T 8 S 11
Priority search pennants: data types The Haskell data type for priority search pennants is a direct implementation of these ideas. data PSQ k p = Void Winner (k p) (LTree k p) k data LTree k p = Start Loser (k p) (LTree k p) k (LTree k p) NB. Winner b t m = Loser b t m Start. The maximum key is accessed using the function max-key. max-key :: PSQ k p k max-key (Winner b t m) = m 12
Priority search pennants: invariants Semi-heap conditions: 1) Every priority in the pennant must be less than or equal to the priority of the winner. 2) For all nodes in the loser tree, the priority of the loser s binding must be less than or equal to the priorities of the bindings of the subtree, from which the loser originates. The loser originates from the left subtree if its key is less than or equal to the split key, otherwise it originates from the right subtree. Search-tree condition: For all nodes, the keys in the left subtree must be less than or equal to the split key and the keys in the right subtree must be greater than the split key. Key condition: The maximum key and the split keys must also occur as keys of bindings. Finite map condition: The pennant must not contain two bindings with the same key. 13
Constructors: and { } :: PSQ k p = Void { } :: (k p) PSQ k p {b} = Winner b Start (key b). 14
Playing a match b 1 m 2 b 2 m 2 b 2 m 1 b 1 b 2 = b 1 m 1 b 2 m 2 b 1 >b = 2 b 1 m 1 t 3 t 3 t 1 t 2 t 1 t 2 NB. b 1 b 2 is shorthand for prio b 1 prio b 2. 15
Playing a match (continued) ( ) :: PSQ k p PSQ k p PSQ k p Void t = t t Void = t Winner b t m Winner b t m prio b prio b = Winner b (Loser b t m t ) m otherwise = Winner b (Loser b t m t ) m 16
Constructors: from-ord-list from-ord-list :: [k p ] PSQ k p from-ord-list = foldm ( ) map (λb {b}) NB. foldm folds a list in a binary-sub-division fashion. 17
Destructors view PSQ k p = Empty Min (k p) (PSQ k p) where Void Empty Winner b t m Min b (second-best t m) The function second-best determines the second-best player by replaying the tournament without the champion. second-best :: LTree k p k PSQ k p second-best Start m = Void second-best (Loser b t k u) m key b k = Winner b t k second-best u m otherwise = second-best t k Winner b u m 18
A second view: priority search pennants as tournaments view PSQ k p = {k p} PSQ k p PSQ k p where Void Winner b Start m {b} Winner b (Loser b t l k t r ) m key b k Winner b t l k Winner b t r m otherwise Winner b t l k Winner b t r m NB. We have taken the liberty of using, { } and also as constructors. 19
Observers: to-ord-list to-ord-list :: PSQ k p [k p ] to-ord-list = [ ] to-ord-list {b} = [b ] to-ord-list (t l t r ) = to-ord-list t l + to-ord-list t r 20
Observers: lookup lookup :: k PSQ k p Maybe p lookup k = Nothing lookup k {b} k key b = Just (prio b) otherwise = Nothing lookup k (t l t r ) k max-key t l = lookup k t l otherwise = lookup k t r 21
Modifier: adjust adjust :: (p p) k PSQ k p PSQ k p adjust f k = adjust f k {b} k key b = {k f (prio b)} otherwise = {b} adjust f k (t l t r ) k max-key t l = adjust f k t l t r otherwise = t l adjust f k t r 22
Constructors: insert insert :: (k p) PSQ k p PSQ k p insert b = {b} insert b {b } key b < key b = {b} {b } key b key b = {b} -- update key b > key b = {b } {b} insert b (t l t r ) key b max-key t l = insert b t l t r otherwise = t l insert b t r 23
Destructors: delete delete :: k PSQ k p PSQ k p delete k = delete k {b} k key b = otherwise = {b} delete k (t l t r ) k max-key t l = delete k t l t r otherwise = t l delete k t r 24
Adding a balancing scheme One of the strengths of priority search pennants as compared to priority search trees is that a balancing scheme can be easily added. Most balancing schemes use rotations to restore balancing invariants. However, rotations do not preserve the semi-heap property: F 2 E D5 B D5 B t 3 = t 1 F 2 E t 1 t 2 t 2 t 3 25
Single rotation b 1 k 2 b 2 k 1 b 2 k 1 t 3 = t 1 b 1 k 2 t 1 t 2 (b 2 b 1 ) t 2 t 3 b 2 (b 1 ) b 2 k 1 b 1 k 2 t 3 = t 1 b 1 k 1 b 2 k 2 t 1 t 2 t 2 t 3 (b 1 b 2 ) b 1 (b 2 ) 26
Single rotation (continued) b 1 k 2 b 2 k 1 b 2 k 1 b 1 k 2 t 3 = t 1 t 1 t 2 t 2 t 3 (b 2 ) b 1 b 2 ( b 1 ) b 1 k 1 b 1 k 2 b 2 k 1 b 2 k 2 b 2 k 1 b 1 k 2 t 1 b 1 b 2 = t 3 b 1 >b = 2 t 1 t 2 t 3 t 1 t 2 t 2 t 3 (b 2 b 1 ) ( b 2 ) b 1 (b 2 b 1 ) 27
Summary Priority search queues are a versatile ADT. They can be easily implemented by priority search pennants using an arbitrary balancing scheme. Views were very helpful: they provide a convenient interface to the ADT and they enhance both the readability and the modularity of the code. 28
Appendix foldm :: (a a a) a [a ] a foldm ( ) e as null as = e otherwise = fst (rec (length as) as) where rec 1 (a : as) = (a, as) rec n as = (a 1 a 2, as 2 ) where m = n div 2 (a 1, as 1 ) = rec (n m) as (a 2, as 2 ) = rec m as 1 29
A programming challenge Improve the following code which implements the first-fit heuristics for the bin packing problem. pack-first-fit :: [Item ] [Bin ] pack-first-fit = foldl first-fit [ ] first-fit :: [Bin ] Item [Bin ] first-fit [ ] i = [i ] first-fit (b : bs) i b + i 1 = b + i : bs otherwise = b : first-fit bs i 30
Solution to the programming challenge Priority search queues not only support dictionary and priority queue operations. As a little extra they also allow for so-called range queries: at-most :: p PSQ k p [k p ] at-most-range :: p (k, k) PSQ k p [k p ]. The call at-most p t q returns a list of bindings ordered by key whose priorities are at most p t ; at-most-range p t (k l, k r ) q returns a list of bindings ordered by key whose priorities are at most p t and whose keys lie between k l and k r. Priority search pennants support both operations in Θ(r(log n log r)) time where r is the length of the output list. 31
Solution to the programming challenge (continued) Using at-most we can quickly determine the first bin that can accommodate a given item (the bins are numbered consecutively). type No = Int pack-first-fit :: [Item ] [Bin ] pack-first-fit is = [prio b b to-ord-list q ] where (q, ) = foldl first-fit (, 0) is first-fit :: (PSQ No Bin, No) Item (PSQ No Bin, No) first-fit (q, n) i = case at-most (1 i) q of [ ] (insert (n i) q, n + 1) (k ) : (adjust (+i) k q, n) 32