quickest quickest
play

Quickest Quickest for maintaining sorted sets. Sorting Sorting - PDF document

One-Slide Summary Insert-sort is ( n 2 ) worst case (reverse list), but is ( n ) best case (sorted list). A recursive function that divides its input in half each time is often in (log n ). If we could divide our input list


  1. One-Slide Summary • Insert-sort is  ( n 2 ) worst case (reverse list), but is  ( n ) best case (sorted list). • A recursive function that divides its input in half each time is often in  (log n ). • If we could divide our input list in half rapidly, we could do a quicker sort :  ( n log n ). • Sorted binary trees are an efficient data structure Quickest Quickest for maintaining sorted sets. Sorting Sorting • British codebreakers used cribs (guesses), brute force, and analysis to break the Lorenz cipher. and and Guessed wheel settings were likely to be correct if Double Deltas Double Deltas they resulted in a message with the right linguistic properties for German (e.g., repeated letters). #2 How much work is insert-sort? def insert_sort (lst, cf): if not lst: return [] return insert_one(lst[0], insert_sort(lst[1:], cf)) def insert_one (elt, lst, cf): if not lst: return [elt] if cf(elt, lst[0]): return [elt] + lst return [lst[0]] + insert_one(elt, lst[1:], cf) How many times does insert- running time of insert- sort evaluate insert-one? one is in  ( n ) n times (once for each element) insert-sort has running time in  ( n 2 ) where n is the number of elements in the input list #4 best-first-sort vs. insert-sort Can we do better? • Both are  ( n 2 ) worst case (reverse list) insert_one(88, [1,2,3,5,6,22,63,77,89,90], ascending) • Both are  ( n 2 ) when sorting a randomly ordered list Suppose we had procedures – But insert-sort is about twice as fast first_half(lst) • insert-sort is  ( n ) best case (ordered second_half(lst) input list) that quickly divided the list in two halves? #5

  2. Evaluating quicker-sort quicker-insert using halves >>> quicker_insert(3, [1,2,4,5,7,8,9,10], ascend) Front = [1,2,4,5] Back = [7,8,9,10] def quicker_insert (elt, lst, cf): def quicker_insert (elt, lst, cf): Is 3 < 7? Yes. So if not lst: return [elt] # just like insert_one return quicker_insert(3,[1,2,4,5],ascend) + [7,8,9,10] if not lst: return [elt] # just like insert_one if len(lst) == 1: # handle 1 element Front = [1,2] Back = [4,5] return [elt]+lst if cf(elt, lst[0]) else lst+[elt] if len(lst) == 1: # handle 1 element by hand Is 3 < 4? Yes. So front = first_half(lst) return quicker_insert(3,[1,2],ascend) + [4,5] back = second_half(lst) return [elt]+lst if cf(elt, lst[0]) else lst+[elt] Front = [1] if cf(elt, back[0]): # insert into front half Back = [2] front = first_half(lst) return quicker_insert(elt, front, cf) + back Is 3 < 2? No. So else: # insert into back half Return [1] + quicker_insert(3,[2],ascend) back = second_half(lst) return front + quicker_insert(elt, back, cf) One element. Compare 3 and 2, return [2,3] if cf(elt, back[0]): # insert into front half So final result is: [1] + [2,3] + [4,5] + [7,8,9,10] return quicker_insert(elt, front, cf) + back else: # insert into back half Every time we call quicker- return front + quicker_insert(elt, back, cf) insert, the length of the list is approximately halved ! #7 #8 Liberal Arts Trivia: ? How much work is quicker-sort? • The argan tree, found primarily def quicker_insert (elt, lst, cf): Each time we call in Morocco, has a knobby, if not lst: return [elt] # just like insert_one quicker-insert, the size of if len(lst) == 1: # handle 1 element twisted trunk that allows these return [elt]+lst if cf(elt, lst[0]) else lst+[elt] lst halves. So doubling front = first_half(lst) animals to climb it easily. The the size of the list only back = second_half(lst) if cf(elt, back[0]): # insert into front half increases the number of animals eat the fruit, which has return quicker_insert(elt, front, cf) + back calls by 1. else: # insert into back half an indigestible nut inside, return front + quicker_insert(elt, back, cf) which is collected by farmers and used to make argan oil: List Size # quicker_insert applications 1 1 handy in cooking and 2 2 4 3 cosmetics, but pricey at $45 8 4 16 5 per 500 ml. 32 6 #9 #10 Liberal Arts Trivia: Scandinavian Studies • This capital of and largest city in Denmark is situated on the islands of Zealand and Amager. It is the birthplace of Neils Bohr, Søren Kierkegaard, and Victor Borge. The city's origin as a harbor and a place of commerce is reflected in its name. Its original designation, from which the contemporary Danish name is derived, was Køpmannæhafn, "merchants' harbor". The English name for the city is derived from its (similar) Low German name. #11 #12

  3. Remembering Logarithms Changing Bases log b n = x means b x = n log b n = (1/log k b ) log k n What is log 2 1024 ? If k and b are What is log 10 1024 ? constants, this is constant Is log 10 n in  (log 2 n ) ?  (log 2 n )   (log 10 n )   (log n ) No need to include a constant base within asymptotic operators. #13 #14 Number of Applications quicker-sort ? Assuming the list is well-balanced, def quicker_insert (elt, lst, cf): def quicker_sort (lst, cf): if not lst: return [elt] the number of applications of if not lst: return [ ] if len(lst) == 1: # handle 1 element by hand return quicker_insert( return [elt]+lst if cf(elt, lst[0]) else lst+[elt] quicker-insert is in  (log n ) where n lst[0], front = first_half(lst) back = second_half(lst) quicker_sort(lst[1:], cf), is the number of elements in the if cf(elt, back[0]): # insert into front half cf) return quicker_insert(elt, front, cf) + back input list. else: # insert into back half return front + quicker_insert(elt, back, cf) quicker_sort using halves would have running time in  ( n log n ) if we have first_half, second_half, and append (e.g., [1,2,3] + [4,5,6]) procedures that run in constant time. #15 #16 Orders of Growth Is there a fast first-half procedure? 14000 • No! (at least not on lists) n 2 12000 • To produce the first half of a list length 10000 n , we need to walk down the first n /2 8000 elements 6000 • So, first-half on lists has running time in  ( n/2 ) =  ( n ) 4000 2000 n log n 0 1 9 17 25 33 41 49 57 65 73 81 89 97 105 #17 #18

  4. Making it faster We need to either: 1. Reduce the number of applications of insert-one in insert-sort Impossible – need to consider each element 2. Reduce the number of applications of quicker-insert in quicker-insert Unlikely… each application already halves the list 3. Reduce the time for each application of quicker-insert Need to make first-half, second-half and append faster than  ( n ) #19 #20 Sorted Binary Trees Tree Example cf is < 5 el left 2 8 right A tree containing A tree containing all elements x such all elements x such null that cf( x, el) is true 4 7 1 that cf( x ,el) is false null null null null null null #21 #22 Representing Trees def make_tree (left, el, right): left and right are trees return [el, left, right] ( [ ] is a tree) def get_element (tree): tree must be a non-null tree return tree[0] def get_left (tree): tree must be a non-null tree return tree[1] def get_right (tree): return tree[2] tree must be a non-null tree #24

  5. Representing Trees insert-one-tree D 5 def insert_one_tree (cf, new_elt, tree): B C if not tree: If the tree is null, make a new tree 2 8 return make_tree([], new_elt, []) with new_elt as its element and element_here = get_element(tree) no left or right trees. if cf(new_elt, element_here): A return make_tree( 1 insert_one_tree(cf, new_elt, get_left(tree)), element_here, Otherwise, decide A = make_tree([], 1, []) get_right(tree)) if elt should be in B = make_tree(A, 2, []) the left or right subtree. else: C = make_tree([], 8, []) Insert it into that return make_tree( subtree, but leave the D = make_tree(B, 5, C) get_left(tree), other subtree unchanged. element_here, insert_one_tree(cf, new_elt, get_right(tree))) #25 #26 quicker-insert-one How much work is insert-one-tree? def insert_one_tree (cf, new_elt, tree): Each time we call if not tree: insert-one-tree, the size return make_tree([], new_elt, []) def quicker_insert_one (cf, lst): of the tree approximately element_here = get_element(tree) if not lst: return [ ] if cf(new_elt, element_here): halves (if it is well return make_tree( return insert_one_tree(cf, balanced). insert_one_tree(cf, new_elt, get_left(tree)), lst[0], element_here, get_right(tree)) Each application is else: quicker_insert_one(cf, lst[1:])) constant time. return make_tree( get_left(tree), element_here, insert_one_tree(cf, new_elt, get_right(tree))) The running time of insert-one-tree is in No change (other than using insert_one_tree)…but evaluates to a tree not a list!  (log n ) where n is the number of elements in Practice: You should be able to write a procedure that takes a TREE as input the input tree, which must be well-balanced. and prints out all of its elements (e.g., “from left to right”). #27 #28 Lorenz Lorenz Liberal Arts Trivia: Classics Cipher Cipher Machine Machine • This ancient Greek epic poem, traditionally attributed to Homer, is widely believed to be the oldest extant work of Western literature. It describes the events of the final year of the Trojan War. The plot follows Achilles and his anger at Agamemnon, king of Mycenae. It is written in dactylic hexameter and comprises 15,693 lines of verse. It begins: – μ νιν ειδε θε Πηληϊάδεω χιλ ος ῆ ἄ ὰ Ἀ ῆ – ο λομένην, μυρί' χαιο ς λγε' θηκεν ὐ ἣ Ἀ ῖ ἄ ἔ #29 #30

Recommend


More recommend