เหตุใดเครื่องมือด่วนแบบมินิมอลเช่น Haskell จึงไม่ใช่ Quicksort ที่ "จริง"


118

เว็บไซต์ของ Haskell แนะนำฟังก์ชัน Quicksort 5 บรรทัดที่น่าสนใจมากดังที่แสดงด้านล่าง

quicksort [] = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
    where
        lesser = filter (< p) xs
        greater = filter (>= p) xs

พวกเขายังรวมถึง"ทรู quicksort ใน C"

// To sort array a[] of size n: qsort(a,0,n-1)

void qsort(int a[], int lo, int hi) 
{
  int h, l, p, t;

  if (lo < hi) {
    l = lo;
    h = hi;
    p = a[hi];

    do {
      while ((l < h) && (a[l] <= p)) 
          l = l+1;
      while ((h > l) && (a[h] >= p))
          h = h-1;
      if (l < h) {
          t = a[l];
          a[l] = a[h];
          a[h] = t;
      }
    } while (l < h);

    a[hi] = a[l];
    a[l] = p;

    qsort( a, lo, l-1 );
    qsort( a, l+1, hi );
  }
}

ลิงก์ด้านล่างเวอร์ชัน C จะนำไปยังหน้าที่ระบุว่า'Quicksort ที่ยกมาในบทนำไม่ใช่ Quicksort "จริง" และไม่ได้ปรับขนาดสำหรับรายการที่ยาวขึ้นเช่นเดียวกับรหัส c'

เหตุใดฟังก์ชัน Haskell ข้างต้นจึงไม่ใช่ Quicksort ที่แท้จริง ไม่สามารถปรับขนาดสำหรับรายการที่ยาวขึ้นได้อย่างไร


คุณควรเพิ่มลิงก์ไปยังหน้าที่คุณกำลังพูดถึง
Staven

14
มันไม่เข้าที่จึงค่อนข้างช้า? คำถามดีจริง!
fuz

4
@FUZxxl: รายการ Haskell ไม่เปลี่ยนรูปดังนั้นจะไม่มีการดำเนินการใด ๆ ในขณะที่ใช้ประเภทข้อมูลเริ่มต้น สำหรับความเร็ว - มันไม่จำเป็นต้องช้าลงเสมอไป GHC เป็นเทคโนโลยีคอมไพเลอร์ที่น่าประทับใจและบ่อยครั้งที่โซลูชัน haskell ที่ใช้โครงสร้างข้อมูลที่ไม่เปลี่ยนรูปนั้นมีความเร็วเทียบเท่ากับเทคโนโลยีอื่น ๆ ที่ไม่แน่นอนในภาษาอื่น ๆ
Callum Rogers

1
มันไม่ใช่ qsort จริงหรือ? โปรดจำไว้ว่า qsort มีO(N^2)รันไทม์
Thomas Eding

2
ควรสังเกตว่าตัวอย่างข้างต้นเป็นตัวอย่างเบื้องต้นของ Haskell และ Quicksort นั้นเป็นตัวเลือกที่แย่มากสำหรับการจัดเรียงรายการ การจัดเรียงใน Data.List ถูกเปลี่ยนเป็น mergesort ย้อนกลับไปในปี 2002: hackage.haskell.org/packages/archive/base/3.0.3.1/doc/html/src/…นอกจากนี้คุณยังสามารถดูการใช้งานการเรียงลำดับด่วนก่อนหน้านี้ การดำเนินการปัจจุบันคือ mergesort ที่ทำในปี 2009: hackage.haskell.org/packages/archive/base/4.4.0.0/doc/html/src/...
HaskellElephant

คำตอบ:


75

Quicksort ที่แท้จริงมีสองด้านที่สวยงาม:

  1. แบ่งและพิชิต: แบ่งปัญหาออกเป็นสองปัญหาเล็ก ๆ
  2. แบ่งองค์ประกอบเข้าที่

ตัวอย่างสั้น ๆ ของ Haskell แสดงให้เห็นถึง (1) แต่ไม่ใช่ (2) วิธีการทำ (2) อาจไม่ชัดเจนหากคุณยังไม่รู้เทคนิค!



สำหรับคำอธิบายที่ชัดเจนของการแบ่งในสถานที่ขั้นตอนการดูinteractivepython.org/courselib/static/pythonds/SortSearch/...
pvillela

57

True inplace Quicksort ใน Haskell:

import qualified Data.Vector.Generic as V 
import qualified Data.Vector.Generic.Mutable as M 

qsort :: (V.Vector v a, Ord a) => v a -> v a
qsort = V.modify go where
    go xs | M.length xs < 2 = return ()
          | otherwise = do
            p <- M.read xs (M.length xs `div` 2)
            j <- M.unstablePartition (< p) xs
            let (l, pr) = M.splitAt j xs 
            k <- M.unstablePartition (== p) pr
            go l; go $ M.drop k pr

แหล่งที่มาของunstablePartitionแสดงให้เห็นว่ามันเป็นเทคนิคการแลกเปลี่ยนในสถานที่เดียวกัน (เท่าที่ฉันสามารถบอกได้)
Dan Burton

3
โซลูชันนี้ไม่ถูกต้อง unstablePartitionจะคล้ายกับpartitionสำหรับquicksortแต่ก็ไม่ได้รับประกันองค์ประกอบที่mตำแหน่ง TH pเป็นเพียง
nymk

29

นี่คือการทับศัพท์ของรหัส C ตัวเร่งด่วน "จริง" ใน Haskell รั้งตัวเอง.

import Control.Monad
import Data.Array.IO
import Data.IORef

qsort :: IOUArray Int Int -> Int -> Int -> IO ()
qsort a lo hi = do
  (h,l,p,t) <- liftM4 (,,,) z z z z

  when (lo < hi) $ do
    l .= lo
    h .= hi
    p .=. (a!hi)

    doWhile (get l .< get h) $ do
      while ((get l .< get h) .&& ((a.!l) .<= get p)) $ do
        modifyIORef l succ
      while ((get h .> get l) .&& ((a.!h) .>= get p)) $ do
        modifyIORef h pred
      b <- get l .< get h
      when b $ do
        t .=. (a.!l)
        lVal <- get l
        hVal <- get h
        writeArray a lVal =<< a!hVal
        writeArray a hVal =<< get t

    lVal <- get l
    writeArray a hi =<< a!lVal
    writeArray a lVal =<< get p

    hi' <- fmap pred (get l)
    qsort a lo hi'
    lo' <- fmap succ (get l)
    qsort a lo' hi

มันสนุกใช่มั้ย? ที่จริงฉันตัดขนาดใหญ่นี้ออกletในตอนต้นและwhereตอนท้ายของฟังก์ชันโดยกำหนดตัวช่วยทั้งหมดเพื่อทำให้โค้ดก่อนหน้าค่อนข้างสวย

  let z :: IO (IORef Int)
      z = newIORef 0
      (.=) = writeIORef
      ref .=. action = do v <- action; ref .= v
      (!) = readArray
      (.!) a ref = readArray a =<< get ref
      get = readIORef
      (.<) = liftM2 (<)
      (.>) = liftM2 (>)
      (.<=) = liftM2 (<=)
      (.>=) = liftM2 (>=)
      (.&&) = liftM2 (&&)
  -- ...
  where doWhile cond foo = do
          foo
          b <- cond
          when b $ doWhile cond foo
        while cond foo = do
          b <- cond
          when b $ foo >> while cond foo

และนี่คือการทดสอบใบ้เพื่อดูว่าได้ผลหรือไม่

main = do
    a <- (newListArray (0,9) [10,9..1]) :: IO (IOUArray Int Int)
    printArr a
    putStrLn "Sorting..."
    qsort a 0 9
    putStrLn "Sorted."
    printArr a
  where printArr a = mapM_ (\x -> print =<< readArray a x) [0..9]

ฉันไม่ได้เขียนโค้ดที่จำเป็นบ่อยนักใน Haskell ดังนั้นฉันแน่ใจว่ามีหลายวิธีในการทำความสะอาดโค้ดนี้

แล้วไงล่ะ?

คุณจะสังเกตเห็นว่าโค้ดด้านบนนั้นยาวมาก หัวใจสำคัญของมันคือความยาวพอ ๆ กับรหัส C แม้ว่าแต่ละบรรทัดมักจะดูละเอียดกว่าเล็กน้อย นี่เป็นเพราะ C แอบทำสิ่งที่น่ารังเกียจหลายอย่างซึ่งคุณอาจจะคิดไม่ถึง ตัวอย่างเช่นa[l] = a[h];. นี้จะเข้าสู่ตัวแปรที่ไม่แน่นอนlและhแล้วเข้าถึงอาร์เรย์แน่นอนแล้วแปรรูปอาร์เรย์ที่ไม่แน่นอนa aการกลายพันธุ์ศักดิ์สิทธิ์แบทแมน! ใน Haskell การกลายพันธุ์และการเข้าถึงตัวแปรที่เปลี่ยนแปลงได้นั้นชัดเจน qsort "ปลอม" นั้นน่าดึงดูดด้วยเหตุผลหลายประการ แต่หัวหน้าในหมู่พวกเขาไม่ใช้การกลายพันธุ์ ข้อ จำกัด ที่กำหนดขึ้นเองนี้ช่วยให้เข้าใจได้ง่ายขึ้นอย่างรวดเร็ว


3
มันยอดเยี่ยมในแบบที่ไม่สบายใจ ฉันสงสัยว่า GHC สร้างรหัสจากอะไรแบบนั้น?
Ian Ross

@IanRoss: จากตัวดูดที่ไม่บริสุทธิ์? GHC สร้างรหัสที่ดีงามจริงๆ
JD

"qsort" ปลอม "นั้นน่าดึงดูดด้วยเหตุผลหลายประการ ... " ฉันกลัวว่าประสิทธิภาพของมันโดยไม่มีการจัดการในสถานที่ (ตามที่ระบุไว้แล้ว) จะแย่มาก และการใช้องค์ประกอบที่ 1 เสมอเพราะเดือยก็ไม่ช่วยเช่นกัน
dbaltor

25

ในความคิดของฉันการบอกว่า "ไม่ใช่ช่องทางลัดที่แท้จริง" นั้นเป็นการพูดเกินจริง ฉันคิดว่ามันเป็นการใช้อัลกอริทึม Quicksort ที่ถูกต้องไม่ใช่วิธีที่มีประสิทธิภาพโดยเฉพาะ


9
ฉันเคยโต้แย้งกับใครบางคนครั้งหนึ่ง: ฉันค้นหาเอกสารจริงซึ่งระบุ QuickSort และอยู่ในตำแหน่งที่แน่นอน
ivanm

2
@ivanm เชื่อมโยงหลายมิติหรือไม่เกิดขึ้น :)
Dan Burton

1
ฉันชอบที่บทความนี้มีความจำเป็นทั้งหมดและยังรวมถึงเคล็ดลับในการรับประกันการใช้พื้นที่ลอการิทึม (ที่หลายคนไม่รู้) ในขณะที่เวอร์ชันเรียกซ้ำ (ที่เป็นที่นิยมในปัจจุบัน) ใน ALGOL เป็นเพียงเชิงอรรถ เดาว่าฉันจะต้องมองหากระดาษอื่นตอนนี้ ... :)
hugomg

6
การใช้อัลกอริทึมใด ๆ ที่ "ถูกต้อง" ควรมีขอบเขตของ asymptotic เหมือนกันใช่ไหม Haskell quicksort ที่ถูกทำลายไม่ได้รักษาความซับซ้อนของหน่วยความจำใด ๆ ของอัลกอริทึมดั้งเดิม ไม่ได้ใกล้เคียง. นั่นเป็นเหตุผลว่าทำไมมันถึงช้ากว่า Quicksort ของแท้ของ Sedgewick ใน C.
JD

16

ฉันคิดว่ากรณีที่อาร์กิวเมนต์นี้พยายามสร้างขึ้นคือสาเหตุที่มักใช้ Quicksort คือมันอยู่ในตำแหน่งและค่อนข้างเป็นมิตรกับแคช เนื่องจากคุณไม่มีสิทธิประโยชน์เหล่านั้นกับรายการ Haskell raison d'êtreหลักของมันจึงหายไปและคุณอาจใช้การเรียงลำดับการผสานซึ่งรับประกันO (n log n)ในขณะที่ Quicksort คุณอาจต้องใช้การสุ่มหรือซับซ้อน แผนการแบ่งพาร์ติชันเพื่อหลีกเลี่ยงเวลาทำงานO (n 2 )ในกรณีที่เลวร้ายที่สุด


5
และ Mergesort เป็นอัลกอริธึมการเรียงลำดับที่เป็นธรรมชาติมากขึ้นสำหรับรายการที่ชอบ (ไม่เปลี่ยนรูป) ซึ่งเป็นอิสระจากความจำเป็นในการทำงานกับอาร์เรย์เสริม
hugomg

16

ด้วยการประเมินที่ขี้เกียจโปรแกรม Haskell ไม่ (แทบจะทำไม่ได้ ) ทำอย่างที่มันเป็น

พิจารณาโปรแกรมนี้:

main = putStrLn (show (quicksort [8, 6, 7, 5, 3, 0, 9]))

ในภาษาที่กระตือรือร้นแรกquicksortจะวิ่งแล้วแล้วshow putStrLnอาร์กิวเมนต์ของฟังก์ชันจะถูกคำนวณก่อนที่ฟังก์ชันนั้นจะเริ่มทำงาน

ใน Haskell นั้นตรงกันข้าม ฟังก์ชันเริ่มทำงานก่อน อาร์กิวเมนต์จะคำนวณเฉพาะเมื่อฟังก์ชันนั้นใช้จริงๆเท่านั้น และอาร์กิวเมนต์ผสมเช่นรายการจะถูกคำนวณทีละชิ้นตามที่ใช้แต่ละชิ้น

ดังนั้นครั้งแรกสิ่งที่เกิดขึ้นในโปรแกรมนี้คือการที่putStrLnจะเริ่มต้นทำงาน

การใช้งานของ GHCputStrLnโดยการคัดลอกอักขระของสตริงอาร์กิวเมนต์ไปยังบัฟเฟอร์เอาต์พุต แต่เมื่อเข้าสู่วงนี้showยังไม่ได้ทำงาน ดังนั้นเมื่อต้องคัดลอกอักขระตัวแรกจากสตริง Haskell จะประเมินเศษของshowและการquicksortเรียกที่จำเป็นในการคำนวณอักขระนั้น จากนั้นputStrLnไปยังอักขระถัดไป ดังนั้นการดำเนินการของทั้งสาม functions- putStrLn, showและquicksort- เป็นบรรณนิทัศน์ quicksortดำเนินการทีละน้อยโดยปล่อยให้กราฟของthunks ที่ไม่ได้ประเมินค่าขณะที่มันไปจำจุดที่ค้างไว้

ตอนนี้สิ่งนี้แตกต่างอย่างมากจากสิ่งที่คุณคาดหวังหากคุณคุ้นเคยกับภาษาโปรแกรมอื่น ๆ ที่เคยมีมา ไม่ใช่เรื่องง่ายที่จะเห็นภาพว่าquicksortพฤติกรรมจริงของ Haskell ในแง่ของการเข้าถึงหน่วยความจำหรือแม้แต่ลำดับของการเปรียบเทียบ หากคุณสามารถสังเกตได้เฉพาะพฤติกรรมเท่านั้นไม่ใช่ซอร์สโค้ดคุณจะไม่ทราบว่ามันทำอะไรเป็น Quicksortquicksort

ตัวอย่างเช่นพาร์ติชัน Quicksort เวอร์ชัน C จะแสดงข้อมูลทั้งหมดก่อนการเรียกซ้ำครั้งแรก ในเวอร์ชัน Haskell องค์ประกอบแรกของผลลัพธ์จะถูกคำนวณ (และอาจปรากฏบนหน้าจอของคุณ) ก่อนที่พาร์ติชันแรกจะทำงานเสร็จก่อนที่จะทำงานใด ๆ ทั้งหมดบนgreaterที่จะทำใน

ป.ล. รหัส Haskell จะเหมือน Quicksort มากขึ้นถ้ามีการเปรียบเทียบจำนวนเท่ากันกับ Quicksort รหัสตามที่เขียนจะทำการเปรียบเทียบมากกว่าสองเท่าเนื่องจากlesserและgreaterระบุให้คำนวณโดยอิสระทำการสแกนสองเส้นผ่านรายการ โดยหลักการแล้วเป็นไปได้ที่คอมไพเลอร์จะฉลาดพอที่จะกำจัดการเปรียบเทียบเพิ่มเติม หรือสามารถเปลี่ยนรหัสเพื่อใช้งานได้Data.List.partitionได้

PPS ตัวอย่างคลาสสิกของอัลกอริทึม Haskell ที่ไม่ทำงานอย่างที่คุณคาดหวังคือตะแกรงของ Eratosthenesสำหรับการคำนวณราคา


2
lpaste.net/108190 - กำลังทำ "การจัดเรียงต้นไม้ที่ถูกตัดไม้ทำลายป่า" มีด้ายแดงเก่า ๆเกี่ยวกับเรื่องนี้ cf เลย stackoverflow.com/questions/14786904/…และที่เกี่ยวข้อง
Will Ness

1
ดูใช่นั่นเป็นลักษณะที่ค่อนข้างดีของสิ่งที่โปรแกรมทำจริง
Jason Orendorff

คำพูดของตะแกรงถูกเขียนว่าเทียบเท่าprimes = unfoldr (\(p:xs)-> Just (p, filter ((> 0).(`rem` p)) xs)) [2..]หรือไม่ปัญหาที่เกิดขึ้นทันทีที่สุดอาจจะชัดเจนกว่า และนั่นคือก่อนที่เราจะพิจารณาเปลี่ยนไปใช้อัลกอริทึมตะแกรงจริง
Will Ness

ฉันสับสนกับคำจำกัดความของคุณว่ารหัสอะไร "ดูเหมือนมัน" รหัสของคุณ "ดูเหมือน" สำหรับฉันเหมือนที่เรียกputStrLnว่าแอปพลิเคชันshowที่ถูก thunked ของแอปพลิเคชันที่ถูก thunked ของquicksortรายการตามตัวอักษร --- และนั่นคือสิ่งที่มันทำ! (ก่อนการปรับให้เหมาะสม - แต่ให้เปรียบเทียบโค้ด C กับแอสเซมเบลอร์ที่ปรับให้เหมาะสมในบางครั้ง!) บางทีคุณอาจหมายถึง "ด้วยการประเมินที่ขี้เกียจโปรแกรม Haskell ไม่ได้ทำโค้ดที่มีลักษณะคล้ายกันในภาษาอื่น"?
Jonathan Cast

4
@jcast ฉันคิดว่ามีความแตกต่างในทางปฏิบัติระหว่าง C และ Haskell ในเรื่องนี้ เป็นเรื่องยากมากที่จะถกเถียงกันอย่างน่าพอใจเกี่ยวกับเรื่องประเภทนี้ในชุดความคิดเห็นเท่าที่ฉันอยากจะให้มันออกมามากกว่ากาแฟในชีวิตจริง แจ้งให้เราทราบหากคุณเคยอยู่ในแนชวิลล์โดยมีเวลาเหลืออีก 1 ชั่วโมง!
Jason Orendorff

13

ฉันเชื่อว่าเหตุผลที่คนส่วนใหญ่บอกว่า Quicksort Haskell สวยไม่ใช่ Quicksort ที่ "จริง" คือความจริงที่ว่ามันไม่ได้อยู่ในสถานที่ - ชัดเจนว่าไม่สามารถใช้เมื่อใช้ประเภทข้อมูลที่ไม่เปลี่ยนรูปได้ แต่ก็มีการคัดค้านว่ามันไม่ "เร็ว": ส่วนหนึ่งเป็นเพราะ ++ ราคาแพงและเนื่องจากมีช่องว่างรั่ว - คุณค้างที่รายการอินพุตขณะทำการเรียกซ้ำกับองค์ประกอบที่น้อยกว่าและ ในบางกรณี - เช่นเมื่อรายการลดลง - ส่งผลให้เกิดการใช้พื้นที่กำลังสอง (คุณอาจบอกได้ว่าการทำให้มันวิ่งในอวกาศเชิงเส้นนั้นใกล้เคียงที่สุดที่คุณจะไปถึง "ในสถานที่" โดยใช้ข้อมูลที่ไม่เปลี่ยนรูปได้) มีวิธีแก้ปัญหาทั้งสองอย่างอย่างเป็นระเบียบโดยใช้พารามิเตอร์การสะสมการทวีคูณและการหลอมรวม ดู S7.6.1 ของ Richard Bird '


4

ไม่ใช่แนวคิดของการกลายพันธุ์องค์ประกอบในสถานที่ในการตั้งค่าที่ใช้งานได้อย่างหมดจด วิธีการทางเลือกในชุดข้อความนี้ที่มีอาร์เรย์ที่เปลี่ยนแปลงได้ทำให้จิตวิญญาณแห่งความบริสุทธิ์หายไป

มีขั้นตอนอย่างน้อยสองขั้นตอนในการเพิ่มประสิทธิภาพเวอร์ชันพื้นฐาน (ซึ่งเป็นเวอร์ชันที่แสดงออกได้ชัดเจนที่สุด) ของการเรียงลำดับด่วน

  1. เพิ่มประสิทธิภาพการเรียงต่อกัน (++) ซึ่งเป็นการดำเนินการเชิงเส้นโดยตัวสะสม:

    qsort xs = qsort' xs []
    
    qsort' [] r = r
    qsort' [x] r = x:r
    qsort' (x:xs) r = qpart xs [] [] r where
        qpart [] as bs r = qsort' as (x:qsort' bs r)
        qpart (x':xs') as bs r | x' <= x = qpart xs' (x':as) bs r
                               | x' >  x = qpart xs' as (x':bs) r
  2. ปรับให้เหมาะสมกับการจัดเรียงแบบด่วนของ ternary (พาร์ติชัน 3 ทางที่กล่าวถึงโดย Bentley และ Sedgewick) เพื่อจัดการองค์ประกอบที่ซ้ำกัน:

    tsort :: (Ord a) => [a] -> [a]
    tsort [] = []
    tsort (x:xs) = tsort [a | a<-xs, a<x] ++ x:[b | b<-xs, b==x] ++ tsort [c | c<-xs, c>x]
  3. รวม 2 และ 3 อ้างถึงหนังสือของ Richard Bird:

    psort xs = concat $ pass xs []
    
    pass [] xss = xss
    pass (x:xs) xss = step xs [] [x] [] xss where
        step [] as bs cs xss = pass as (bs:pass cs xss)
        step (x':xs') as bs cs xss | x' <  x = step xs' (x':as) bs cs xss
                                   | x' == x = step xs' as (x':bs) cs xss
                                   | x' >  x = step xs' as bs (x':cs) xss

หรือหากองค์ประกอบที่ซ้ำกันไม่ใช่องค์ประกอบส่วนใหญ่:

    tqsort xs = tqsort' xs []

    tqsort' []     r = r
    tqsort' (x:xs) r = qpart xs [] [x] [] r where
        qpart [] as bs cs r = tqsort' as (bs ++ tqsort' cs r)
        qpart (x':xs') as bs cs r | x' <  x = qpart xs' (x':as) bs cs r
                                  | x' == x = qpart xs' as (x':bs) cs r
                                  | x' >  x = qpart xs' as bs (x':cs) r

น่าเสียดายที่ค่ามัธยฐานของสามไม่สามารถใช้กับเอฟเฟกต์เดียวกันได้ตัวอย่างเช่น:

    qsort [] = []
    qsort [x] = [x]
    qsort [x, y] = [min x y, max x y]
    qsort (x:y:z:rest) = qsort (filter (< m) (s:rest)) ++ [m] ++ qsort (filter (>= m) (l:rest)) where
        xs = [x, y, z]
        [s, m, l] = [minimum xs, median xs, maximum xs] 

เนื่องจากยังทำงานได้ไม่ดีใน 4 กรณีต่อไปนี้:

  1. [1, 2, 3, 4, .... , น]

  2. [n, n-1, n-2, ... , 1]

  3. [ม -1, ม -2, ... 3, 2, 1, ม. + 1, ม. + 2, ... , น]

  4. [n, 1, n-1, 2, ... ]

ทั้ง 4 กรณีนี้ได้รับการจัดการอย่างดีโดยวิธีค่ามัธยฐานที่จำเป็นของสาม

ที่จริงแล้วอัลกอริธึมการเรียงลำดับที่เหมาะสมที่สุดสำหรับการตั้งค่าที่ใช้งานได้จริงยังคงเป็นแบบผสานการเรียงลำดับ แต่ไม่ใช่การเรียงลำดับอย่างรวดเร็ว

สำหรับรายละเอียดโปรดเยี่ยมชมงานเขียนของฉันที่: https://sites.google.com/site/algoxy/dcsort


มีการเพิ่มประสิทธิภาพอื่นที่คุณพลาดไป: ใช้พาร์ติชันแทน 2 ตัวกรองเพื่อสร้างรายการย่อย (หรือตัวพับในฟังก์ชันภายในที่คล้ายกันเพื่อสร้างรายการย่อย 3 รายการ)
Jeremy List

3

ไม่มีคำจำกัดความที่ชัดเจนว่าอะไรคืออะไรและอะไรไม่ใช่ Quicksort ที่แท้จริง

พวกเขาเรียกมันว่าไม่ใช่ Quicksort ที่แท้จริงเพราะมันไม่ได้จัดเรียงในสถานที่:

True quicksort ใน C จัดเรียงในสถานที่


-1

เนื่องจากการนำองค์ประกอบแรกจากรายการส่งผลให้รันไทม์แย่มาก ใช้ค่ามัธยฐานของ 3: แรกกลางสุดท้าย


2
การใช้องค์ประกอบแรกก็ใช้ได้ถ้ารายการเป็นแบบสุ่ม
Keith Thompson

2
แต่การจัดเรียงรายการที่เรียงลำดับหรือเกือบจะเป็นเรื่องปกติ
Joshua

7
แต่ qsort IS O(n^2)
Thomas Eding

8
qsort คือค่าเฉลี่ย n log n, แย่ที่สุด n ^ 2
Joshua

3
ในทางเทคนิคแล้วมันไม่ได้แย่ไปกว่าการเลือกค่าแบบสุ่มเว้นแต่ว่าอินพุตจะเรียงลำดับแล้วหรือเกือบจะเรียงลำดับแล้ว เดือยที่ไม่ดีคือเดือยที่อยู่ห่างจากค่ามัธยฐาน องค์ประกอบแรกเป็นเพียงเดือยที่ไม่ดีหากใกล้ถึงจุดต่ำสุดหรือสูงสุด
Platinum Azure

-1

ขอให้ทุกคนเขียน Quicksort ใน Haskell และคุณจะได้รับโปรแกรมเดียวกัน - เห็นได้ชัดว่าเป็น Quicksort ข้อดีและข้อเสียมีดังนี้

Pro: ปรับปรุงบน Quicksort "true" โดยการทำให้เสถียรกล่าวคือรักษาลำดับขององค์ประกอบที่เท่ากัน

Pro: มันเป็นเรื่องเล็กน้อยที่จะพูดถึงการแบ่งสามทาง (<=>) ซึ่งหลีกเลี่ยงพฤติกรรมกำลังสองเนื่องจากค่าบางอย่างเกิดขึ้น O (n) ครั้ง

Pro: อ่านง่ายขึ้นแม้ว่าจะต้องใส่คำจำกัดความของตัวกรองก็ตาม

Con: ใช้หน่วยความจำมากขึ้น

Con: มีค่าใช้จ่ายสูงในการสรุปทางเลือก Pivot โดยการสุ่มตัวอย่างเพิ่มเติมซึ่งสามารถหลีกเลี่ยงพฤติกรรมกำลังสองในลำดับเอนโทรปีต่ำบางอย่างได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.