So finding the maximum element in a list takes O(n) time complexity (if the list has n elements). I tried to implement an algorithm that looks faster.
(define (clever-max lst)
(define (odd-half a-list)
(cond ((null? a-list) (list))
((null? (cdr a-list))
(cons (car a-list) (list)))
(else
(cons (car a-list)
(odd-half (cdr (cdr a-list)))))))
(define (even-half a-list)
(if (null? a-list)
(list)
(odd-half (cdr a-list))))
(cond ((null? lst) (error "no elements in list!"))
((null? (cdr lst)) (car lst))
(else
(let ((l1 (even-half lst))
(l2 (odd-half lst)))
(max (clever-max l1) (clever-max l2))))))
Is this actually faster?! What would you say the asymptotic time complexity is (tight bound)?
Given a list of data which you know nothing about, there is no way to find the maximum element without examining each element and thus taking
O(n)time because if you don’t check it, you might miss it. So no, your algorithm isn’t faster thanO(n)it is in factO(n log n)as you are basically just running merge sort.Here is more data on the Selection problem
I thought about it and realized I should probably do something a bit more than just state this as fact. So I’ve coded up a quick speed test. Now full disclosure, I’m not a Scheme programmer, so this is in Common Lisp but I think I converted your algorithm faithfully.
Now you may be asking your self why you I am only using 10000 for the list size, because really that is fairly small for asymptotic calculations. The truth is there that sbcl recognizes that the first function is tail recursive and therefore abstracts it into a loop whereas it doesn’t with the second so that’s about as big as I could get without killing my stack. Though as you can see from the results below this is large enough to illustrate the point.
Even at this level we are talking about a massive slowdown. It takes an increase to about one million items before the direct check each items once method slows down to the 10k evaluation of your algorithm.
Final Thoughts and conclusions
So the next question that has to be asked is why the second algorithm really is taking that much longer. Without going into to much detail about tail recursion elimination there are a few things that really jump out.
The first one being
cons. Now yes,consisO(1)but it’s still another operation for the system to go through. And it requires the system to allocate and free memory ( have to fire up the garbage collector ). The second thing that really jumps out is that you are basically running a merge sort, except rather than just grabbing the lower and upper half of the list you are grabbing the even and odd nodes ( that also will take longer because you have to iterate every time to build the lists ). What you have here is anO(n log n)algorithm at best ( mind you, it’s merge sort which is really good for sorting ) but it carries a lot of extra overhead.