Pseudo-quicksort time complexity

前端 未结 6 994
不思量自难忘°
不思量自难忘° 2020-12-03 14:31

I know that quicksort has O(n log n) average time complexity. A pseudo-quicksort (which is only a quicksort when you look at it from far enough away, with a sui

6条回答
  •  情深已故
    2020-12-03 15:07

    I can offer you a run time test on Ideone.com which seems to show more or less linearithmic run-times for both (++) based versions and the one using accumulator technique from the Landei's answer, as well as another one, using one-pass three-way partitioning. On ordered data this turns quadratic or worse for all of them.

    -- random:   100k        200k         400k         800k
    -- _O    0.35s-11MB  0.85s-29MB   1.80s-53MB   3.71s-87MB   n^1.3  1.1  1.0
    -- _P    0.36s-12MB  0.80s-20MB   1.66s-45MB   3.76s-67MB   n^1.2  1.1  1.2
    -- _A    0.31s-14MB  0.62s-20MB   1.58s-54MB   3.22s-95MB   n^1.0  1.3  1.0
    -- _3    0.20s- 9MB  0.41s-14MB   0.88s-24MB   1.92s-49MB   n^1.0  1.1  1.1
    
    -- ordered:   230     460     900     1800
    -- _P        0.09s   0.33s   1.43s    6.89s                 n^1.9  2.1  2.3
    -- _A        0.09s   0.33s   1.44s    6.90s                 n^1.9  2.1  2.3
    -- _3        0.05s   0.15s   0.63s    3.14s                 n^1.6  2.1  2.3
    
    quicksortO xs = go xs  where
      go []  =  []
      go (x:xs) = go [y | y<-xs, y=x]
    
    quicksortP xs = go xs  where
      go []  =  []
      go (x:xs) = go [y | y<-xs, y=x])
    
    quicksortA xs = go xs [] where
      go [] acc = acc
      go (x:xs) acc = go [y | y<-xs, y=x] acc)
    
    quicksort3 xs = go xs [] where
      go     (x:xs) zs = part x xs zs [] [] []
      go     []     zs = zs
      part x []     zs a b c = go a ((x : b) ++ go c zs)
      part x (y:ys) zs a b c =
          case compare y x of
                      LT -> part x ys zs (y:a) b c
                      EQ -> part x ys zs a (y:b) c
                      GT -> part x ys zs a b (y:c)
    

    The empirical run-time complexities are estimated here as O(n^a) where a = log( t2/t1 ) / log( n2/n1 ). The timings are very approximate as ideone aren't very reliable with occasional far outlyers, but for checking the time complexity it's enough.

    Thus these data seem to indicate that one-pass partition is faster by 1.5x-2x than two-pass schemes, and that using (++) is in no way slowing things down - at all. I.e. the "append operations" are not "costly" at all. The quadratic behaviour or (++)/append seems to be an urban myth — in Haskell context of course (edit: ... i.e. in the context of guarded recursion/tail recursion modulo cons; cf. this answer) (update: as user:AndrewC explains, it really is quadratic with the left folding; linear when (++) is used with the right folding; more about this here and here).


    later addition: To be stable, the three-way partitioning quicksort version should too build its parts in the top-down manner:

    q3s xs = go xs [] where
      go     (x:xs) z = part x xs  go  (x:)  (`go` z)
      go     []     z = z
      part x []      a  b  c = a [] (b (c []))
      part x (y:ys)  a  b  c =
          case compare y x of
                      LT -> part x ys  (a . (y:))  b  c
                      EQ -> part x ys  a  (b . (y:))  c
                      GT -> part x ys  a  b  (c . (y:))
    

    (performance not tested).

提交回复
热议问题