2015-07-28 3 views
4

Я начинаю изучать Haskell, и я читал this страницу на вики Haskell, который сообщает эту реализацию QSort:Улучшение наивные реализации QSort

qsort :: (Ord a) => [a] -> [a] 
qsort []  = [] 
qsort (x:xs) = qsort less ++ [x] ++ qsort more 
    where less = filter (<x) xs 
      more = filter (>=x) xs 

с последующим предупреждением, что это не самый эффективный способ сделать это и связать article, который показывает невероятно подробную версию того же алгоритма. Просто посмотрев на это, я, хотя это было не, что я изучал Haskell, и я хотел сделать лучшую версию начального qsort, не жертвуя своей элегантностью. Я в основном сосредоточены на устранении необходимости запускать filter дважды каждый вызов, и это то, что я придумал:

filter' :: (a -> Bool) -> [a] -> ([a], [a]) 
filter' _ [] = ([], []) 
filter' f a = filterInternal a ([], []) 
    where 
    filterInternal [] p = p 
    filterInternal (x:xs) (l, t) 
     | f x  = filterInternal xs (x:l, t) 
     | otherwise = filterInternal xs (l, x:t) 

qsort' :: (Ord a) => [a] -> [a] 
qsort' [] = [] 
qsort' (x:xs) = qsort' less ++ [x] ++ qsort' more 
    where 
    (more, less) = filter' (>x) xs 

Но я не уверен, что это действительно лучше. Я имею в виду, он работает, но как он сравнивается с исходной версией?

+2

Не все ли нужно делать FP, чтобы избежать побочных эффектов и, в дополнение, к локальным алгоритмам? –

+4

«Проблема» кажется, что некоторые люди считают, что вы не можете назвать это «быстрой сортировкой», если это не алгоритм на месте. По какой-то причине. – MathematicalOrchid

+1

[1] [Автор этого сообщения] (http://stackoverflow.com/users/649287/augustss) просто развлекался реализацией быстродействующих сетей на месте, что очень похоже на C. [2] A вторая неэффективность быстродействующей сортировки с одним слоем использует '(++)' для создания промежуточных списков. [3] Для эффективной реализации без изменчивости вы можете посмотреть, что ['qsort' закомментирован в' Data.List'] (https://hackage.haskell.org/package/base-4.8.0.0/docs/src /Data-OldList.html#sort), который изначально был написан Леннартом Августсоном. – duplode

ответ

0

Вот решение, которое я придумал, размышляя примерно по одной и той же проблеме (просьба указать на любые оговорки!). Я не слишком много думал о сложности пространства (не должно быть слишком страшно, хотя), просто время. Вещь, которая действительно убивает наивную qsort, является операцией O(n)(++). Итак, давайте использовать новую структуру данных (которая будет складываться), что дает нам быструю конкатенацию.

{-# LANGUAGE DeriveFoldable #-} 

import Data.Foldable 
import Data.List 

data Seq a = (Seq a) :++: (Seq a) | Leaf a | Empty deriving (Foldable) 

Затем модифицированный qsort', который возвращает Seq a будет

qsort' :: Ord a => [a] -> Seq a 
qsort' []  = Empty 
qsort' (x:xs) = qsort less :++: Leaf x :++: qsort more 
    where (less, more) = partition (<x) xs 

qsort И сам тогда просто qsort = toList . qsort'.

Примечание: исправление с участием partition получает вас постоянный фактор лучше, но ++ против :++: означает, что qsort теперь может быть лучше, чем O(n^2). Кроме того, большинство вариантов реализации там лучше, чем это. Дело в том, чтобы попытаться максимально точно отразить «наивную» реализацию.

+5

Реальная проблема не есть даже '(++)', но тот факт, что '(++)' не может свободно переписываться между рекурсивными вызовами на 'qsort'.Эта проблема может быть исправлена ​​без определения новой структуры данных со стандартным трюком «разностного списка». Идея состоит в том, что мы манипулируем частично-прикладными '(++)' вызовами (типа '[a] -> [a]'), а не плоскими списками (типа '[a]'); таким образом: 'qsort :: Ord a => [a] -> ([a] -> [a]); qsort [] = id; qsort (x: xs) = qsort less. (Икс:) . qsort больше где {...} '. ('id = ([] ++)' и '(x :) = ([x] ++)'.) Это переводит все операции '(++)' вправо, эффективное направление. –

+1

Эквивалентно предложению @ DanielWagner, используйте ['Data.DList'] (https://hackage.haskell.org/package/dlist-0.3/docs/Data-DList.html) для создания промежуточных списков, что обеспечивает приятное абстрактного интерфейса, поэтому вам даже не нужно видеть какие-либо подробности об этом. – luqui

+1

Фактически время, затраченное на '(++)', не влияет на асимптотическую сложность даже исходного 'qsort'. На каждом уровне количество времени, затрачиваемого на рекомбинацию фрагментов с помощью '(++)', - это не более того, что уже было принято, чтобы разделить фрагменты на 'partition'. –

Смежные вопросы