เหตุใด Quicksort จึงดีกว่าอัลกอริธึมการเรียงลำดับอื่น ๆ ในทางปฏิบัติ


308

ในหลักสูตรอัลกอริทึมมาตรฐานเราได้รับการสอนว่าquicksortคือโดยเฉลี่ยและในกรณีที่แย่ที่สุด ในเวลาเดียวกันขั้นตอนวิธีการเรียงลำดับอื่น ๆ มีการศึกษาซึ่งเป็นในกรณีที่เลวร้ายที่สุด (เช่นmergesortและheapsort ) และเวลาแม้กระทั่งการเชิงเส้นในกรณีที่ดีที่สุด (เช่นการเรียงลำดับแบบฟอง ) แต่มีความต้องการเพิ่มเติมบางส่วนของหน่วยความจำO ( n 2 ) O ( n log n )O(nlogn)O(n2)O(nlogn)

หลังจากมองผ่าน ๆ ในเวลาที่วิ่งเร็วขึ้นมันเป็นเรื่องธรรมดาที่จะบอกว่า quicksort ไม่ควรมีประสิทธิภาพเหมือนกับคนอื่น ๆ

นอกจากนี้ให้พิจารณาว่านักเรียนเรียนรู้ในหลักสูตรการเขียนโปรแกรมพื้นฐานที่การเรียกซ้ำโดยทั่วไปไม่ดีนักเพราะอาจใช้หน่วยความจำมากเกินไป ฯลฯ ดังนั้น (และแม้ว่านี่จะไม่ใช่การโต้แย้งจริง) สิ่งนี้ทำให้เกิดความคิดว่า ดีจริงๆเพราะเป็นอัลกอริทึมแบบเรียกซ้ำ

เหตุใด quicksort จึงมีประสิทธิภาพสูงกว่าอัลกอริทึมการเรียงลำดับอื่น ๆ ในทางปฏิบัติ มันเกี่ยวข้องกับโครงสร้างของข้อมูลจริงหรือไม่? มันเกี่ยวข้องกับการทำงานของหน่วยความจำในคอมพิวเตอร์หรือไม่? ฉันรู้ว่าความทรงจำบางอย่างนั้นเร็วกว่าวิธีอื่น ๆ แต่ฉันไม่รู้ว่านั่นเป็นเหตุผลที่แท้จริงสำหรับประสิทธิภาพการตอบโต้ที่ใช้งานง่ายนี้หรือไม่ (เมื่อเปรียบเทียบกับการประเมินเชิงทฤษฎี)


อัปเดต 1:คำตอบที่ยอมรับได้บอกว่าค่าคงที่ที่เกี่ยวข้องในของกรณีเฉลี่ยนั้นเล็กกว่าค่าคงที่ที่เกี่ยวข้องในอัลกอริทึมอื่น ๆ อย่างไรก็ตามฉันยังไม่เห็นเหตุผลที่ถูกต้องพร้อมการคำนวณที่แม่นยำแทนที่จะใช้ความคิดที่เป็นธรรมชาติเท่านั้นO ( n log n )O(nlogn)O(nlogn)

ไม่ว่าในกรณีใดดูเหมือนว่าความแตกต่างที่แท้จริงเกิดขึ้นตามที่บางคำแนะนำในระดับหน่วยความจำซึ่งการใช้งานใช้ประโยชน์จากโครงสร้างภายในของคอมพิวเตอร์โดยใช้ตัวอย่างเช่นหน่วยความจำแคชนั้นเร็วกว่า RAM การอภิปรายที่น่าสนใจอยู่แล้ว แต่ผมยังต้องการดูรายละเอียดเพิ่มเติมเกี่ยวกับการจัดการหน่วยความจำเพราะมันปรากฏว่าคำตอบที่ได้จะทำอย่างไรกับมัน


อัปเดต 2:มีหน้าเว็บหลายหน้าที่นำเสนอการเปรียบเทียบอัลกอริทึมการเรียงลำดับบางอันที่น่าสนใจกว่าเพจอื่น ๆ นอกเหนือจากการนำเสนอการช่วยเหลือด้านภาพที่ดีวิธีนี้ไม่ตอบคำถามของฉัน


2
การเรียงลำดับการผสานคือในกรณีที่แย่ที่สุดและการเรียงลำดับอาร์เรย์ของจำนวนเต็มที่มีขอบเขตผูกพันที่ทราบขนาดของจำนวนเต็มสามารถทำได้ในเวลาด้วยการเรียงลำดับการนับ O ( n )O(nlogn)O(n)
Carl Mummert

13
sorting-algorithms.comมีการเปรียบเทียบขั้นตอนวิธีการเรียงลำดับอย่างละเอียด
Joe

2
โฆษณาอัปเดต 1: ฉันคาดเดาว่าคุณสามารถมีการวิเคราะห์ที่เข้มงวดหรือสมมติฐานที่สมจริง ฉันไม่เห็นทั้งสองอย่าง ตัวอย่างเช่นการวิเคราะห์ที่เป็นทางการส่วนใหญ่จะนับการเปรียบเทียบเท่านั้น
Raphael

9
คำถามนี้ชนะการประกวดเมื่อไม่นานมานี้ที่โปรแกรมเมอร์ ! SE !
กราฟิลส์

3
คำถามที่น่าสนใจ ฉันรันการทดสอบบางเวลาก่อนหน้านี้ด้วยข้อมูลแบบสุ่มและการใช้งานการเรียงลำดับแบบรวดเร็วและแบบไร้เดียงสา อัลกอริทึมทั้งสองทำงานได้ค่อนข้างดีสำหรับชุดข้อมูลขนาดเล็ก (สูงสุด 100,000 รายการ) แต่หลังจากการเรียงลำดับการผสานนั้นกลับกลายเป็นดีขึ้นมาก นี่ดูเหมือนจะขัดแย้งกับข้อสันนิษฐานทั่วไปว่าการจัดเรียงอย่างรวดเร็วนั้นดีมากและฉันยังไม่พบคำอธิบายสำหรับมัน ความคิดเดียวที่ฉันคิดได้คือปกติแล้วคำว่าการเรียงแบบเร็วจะใช้สำหรับอัลกอริทึมที่ซับซ้อนมากขึ้นเช่นการเรียงลำดับแบบอินโทร
Giorgio

คำตอบ:


215

คำตอบสั้น ๆ

อาร์กิวเมนต์ประสิทธิภาพแคชได้รับการอธิบายโดยละเอียดแล้ว นอกจากนี้ยังมีข้อโต้แย้งที่แท้จริงว่าเหตุใด Quicksort จึงรวดเร็ว หากนำไปปฏิบัติเช่นเดียวกับ“ ตัวชี้ข้าม” สองตัวเช่นที่นี่ห่วงด้านในมีรูปร่างที่เล็กมาก เนื่องจากนี่เป็นรหัสที่ถูกเรียกใช้บ่อยที่สุดจึงเป็นการชำระเงิน

คำตอบยาว

ก่อนอื่นเลย

กรณีเฉลี่ยไม่อยู่!

ในกรณีที่ดีที่สุดและเลวร้ายที่สุดมักจะเป็นสุดขั้วที่ไม่ค่อยเกิดขึ้นในทางปฏิบัติการวิเคราะห์กรณีโดยเฉลี่ยจะทำ แต่การวิเคราะห์กรณีโดยเฉลี่ยถือว่าการแจกแจงบางส่วน! สำหรับการเรียงลำดับตัวเลือกทั่วไปคือโมเดลการเปลี่ยนแปลงแบบสุ่ม (สันนิษฐานโดยปริยายบน Wikipedia)

ทำไมต้อง -Notation?O

ทิ้งค่าคงที่ในการวิเคราะห์ขั้นตอนวิธีการที่จะทำเพื่อเหตุผลหลักที่หนึ่ง: ถ้าฉันสนใจในการที่แน่นอนครั้งทำงานที่ฉันต้องการค่าใช้จ่าย (ญาติ) ของการดำเนินงานพื้นฐานทั้งหมดที่เกี่ยวข้อง (แม้จะยังคงละเลยปัญหาแคช pipelining ในโปรเซสเซอร์ที่ทันสมัย ... ) การวิเคราะห์ทางคณิตศาสตร์สามารถนับได้ว่าคำสั่งแต่ละคำสั่งถูกดำเนินการบ่อยเพียงใด แต่เวลาในการทำงานของคำสั่งเดียวนั้นขึ้นอยู่กับรายละเอียดของตัวประมวลผลเช่นการคูณจำนวนเต็มแบบ 32 บิตนั้นต้องใช้เวลาเพิ่มขึ้นหรือไม่

มีสองวิธีคือ:

  1. แก้ไขเครื่องบางรุ่น

    สิ่งนี้ทำในหนังสือซีรีส์เรื่อง“ ศิลปะแห่งการเขียนโปรแกรมคอมพิวเตอร์” ของ Don Knuthสำหรับคอมพิวเตอร์“ ปกติ” ที่ประดิษฐ์ขึ้นโดยผู้แต่ง ในเล่ม 3 คุณจะพบผลการค้นหาค่าเฉลี่ยที่แน่นอนสำหรับอัลกอริทึมการเรียงลำดับจำนวนมากเช่น

    • ทาง :11.667(n+1)ln(n)1.74n18.74
    • การรวม:12.5nln(n)
    • Heapsort: 16nln(n)+0.01n
    • ส่วนแทรก: [ แหล่งที่มา ]2.25n2+7.75n3ln(n) Runtimes ของอัลกอริทึมการเรียงลำดับหลายอย่าง

    ผลลัพธ์เหล่านี้บ่งชี้ว่า Quicksort เร็วที่สุด แต่มันได้รับการพิสูจน์ในเครื่องประดิษฐ์ของ Knuth เท่านั้นไม่จำเป็นต้องมีความหมายใด ๆ สำหรับการพูด x86 PC ของคุณ โปรดทราบว่าอัลกอริทึมนั้นมีความแตกต่างกันไปสำหรับอินพุตขนาดเล็ก:
    Runtimes ของอัลกอริทึมการเรียงลำดับหลายอย่างสำหรับอินพุตขนาดเล็ก
    [ แหล่งที่มา ]

  2. วิเคราะห์นามธรรมดำเนินงานขั้นพื้นฐาน

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

    • Quicksort: การเปรียบเทียบและ swaps โดยเฉลี่ย12nln(n)13nln(n)
    • การรวม:การเปรียบเทียบ แต่มากถึงเข้าถึงอาเรย์ (การรวมกันนั้นไม่ได้มีการแลกเปลี่ยนดังนั้นเราจึงไม่สามารถนับได้)8.66 n ln ( n )1.44nln(n)8.66nln(n)
    • ส่วนแทรก:การเปรียบเทียบและแลกเปลี่ยนโดยเฉลี่ย114n214n2

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

การกระจายอินพุตอื่น ๆ

ดังที่ระบุไว้ข้างต้นกรณีเฉลี่ยโดยทั่วไปเกี่ยวกับการแจกแจงการป้อนข้อมูลบางอย่างดังนั้นหนึ่งอาจพิจารณาคนอื่นนอกเหนือจากการสุ่มเรียงสับเปลี่ยน เช่นการวิจัยได้ทำเพื่อQuicksort ที่มีองค์ประกอบเท่ากันและมีบทความที่ดีเกี่ยวกับฟังก์ชั่นการจัดเรียงมาตรฐานใน Java


8
ผลลัพธ์ของชนิด 2 สามารถแปลงเป็นผลลัพธ์ของชนิด 1 โดยการแทรกค่าคงที่ที่ขึ้นกับเครื่อง ดังนั้นฉันจะโต้แย้ง 2 เป็นวิธีที่เหนือกว่า
Raphael

2
@Raphael +1 ฉันคิดว่าคุณสมมติว่าเครื่องขึ้นอยู่กับการใช้งานด้วยใช่ไหม ฉันหมายถึงการใช้งานเครื่องจักรที่รวดเร็วและการใช้งานไม่ดีอาจไม่ได้มีประสิทธิภาพมากนัก
Janoma

2
@ Janom ฉันคิดว่าอัลกอริทึมการวิเคราะห์จะได้รับในรูปแบบที่มีรายละเอียดมาก (ตามการวิเคราะห์มีรายละเอียด) และการดำเนินการให้มากที่สุดโดยตัวอักษรที่เป็นไปได้ แต่ใช่การดำเนินการจะคำนึงถึงปัจจัยเช่นกัน
กราฟิลส์

3
ที่จริงแล้วการวิเคราะห์ประเภท 2 นั้นด้อยกว่าในทางปฏิบัติ เครื่องในโลกแห่งความจริงนั้นซับซ้อนมากจนผลลัพธ์จากการพิมพ์ 2 ไม่สามารถแปลได้อย่างเป็นไปได้สำหรับการพิมพ์ 1 เปรียบเทียบกับเครื่องที่ 1: การวางแผนการทดลองใช้เวลา 5 นาที
Jules

4
@Jules: "การวางแผนการทดลองใช้งานเวลา" ไม่ได้พิมพ์ 1; มันไม่ใช่การวิเคราะห์ที่เป็นทางการและไม่สามารถถ่ายโอนไปยังเครื่องอื่นได้ นั่นคือเหตุผลที่เราทำการวิเคราะห์อย่างเป็นทางการหลังจากทั้งหมด
Raphael

78

มีหลายจุดที่สามารถทำได้เกี่ยวกับคำถามนี้

Quicksort มักจะรวดเร็ว

O(n2)

n1O(nlogn)

Quicksort มักจะเร็วกว่าประเภทส่วนใหญ่

O(nlogn)O(n2)n

O(nlogn)O(nBlog(nB))B

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

O(nBlogMB(nB))Mk

Quicksort มักจะเร็วกว่า Mergesort

การเปรียบเทียบนี้เกี่ยวกับปัจจัยคงที่ทั้งหมด โดยเฉพาะอย่างยิ่งตัวเลือกนั้นอยู่ระหว่างตัวเลือกย่อยของ pivot สำหรับ Quicksort เทียบกับสำเนาของอินพุตทั้งหมดสำหรับการรวม (หรือความซับซ้อนของอัลกอริทึมที่จำเป็นเพื่อหลีกเลี่ยงการคัดลอกนี้) ปรากฎว่าอดีตมีประสิทธิภาพมากขึ้น: ไม่มีทฤษฎีที่อยู่เบื้องหลังสิ่งนี้มันเกิดขึ้นเร็วขึ้น

nO(logn)O(n)

สุดท้ายโปรดทราบว่า Quicksort มีความอ่อนไหวเล็กน้อยต่ออินพุตที่เกิดขึ้นในลำดับที่ถูกต้องซึ่งในกรณีนี้สามารถข้ามการสลับบางอย่างได้ การควบรวมกิจการไม่มีการเพิ่มประสิทธิภาพดังกล่าวซึ่งทำให้ Quicksort เร็วขึ้นเล็กน้อยเมื่อเทียบกับการรวมกิจการ

ใช้การเรียงลำดับที่เหมาะสมกับความต้องการของคุณ

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


3
คำพูดสุดท้ายของคุณมีค่าเป็นพิเศษ เพื่อนร่วมงานของฉันกำลังวิเคราะห์การใช้งาน Quicksort ภายใต้การกระจายอินพุตที่แตกต่างกัน บางคนแยกย่อยสำหรับการทำซ้ำหลายรายการเช่น
Raphael

4
O(n2)

8
"[T] นี่ไม่มีทฤษฎีเบื้องหลังสิ่งนี้มันเกิดขึ้นเร็วขึ้น" คำกล่าวนั้นไม่เป็นที่น่าพอใจอย่างมากจากมุมมองทางวิทยาศาสตร์ ลองนึกภาพนิวตันพูดว่า "ผีเสื้อบินขึ้นแอปเปิ้ลหล่นลง: ไม่มีทฤษฎีอยู่เบื้องหลังเรื่องนี้แอปเปิ้ลเพิ่งจะตก"
David Richerby

2
@Alex ten Brink คุณหมายถึงอะไรกับ“ โดยเฉพาะอย่างยิ่งอัลกอริทึมคือแคชที่ลืมเลือน ”?
Hibou57

4
@ David Richerby“ คำแถลงดังกล่าวไม่เป็นที่น่าพอใจอย่างมากจากมุมมองทางวิทยาศาสตร์”: เขาอาจเป็นเพียงการเห็นความจริงโดยไม่ต้องแกล้งทำเป็นว่าเราควรจะมีความสุขกับมัน ครอบครัวอัลกอริทึมบางครอบครัวประสบปัญหาการขาดระเบียบแบบเต็ม ฟังก์ชัน hashing เป็นกรณีตัวอย่าง
Hibou57

45

ในบทเรียนการเขียนโปรแกรมหนึ่งที่มหาวิทยาลัยของฉันเราขอให้นักเรียนเปรียบเทียบประสิทธิภาพของ quicksort, meresort, insertion sort และ vs. Python list.sort (เรียกว่าTimsort ) ผลการทดลองทำให้ฉันประหลาดใจอย่างมากเนื่องจาก list.sort ในตัวทำงานได้ดีกว่าอัลกอริธึมการเรียงลำดับอื่น ๆ ถึงแม้จะมีอินสแตนซ์ที่ทำให้ Quicksort ง่ายขึ้นการรวมกันของการชนล้มเหลว ดังนั้นจึงเป็นการด่วนที่จะสรุปได้ว่าการติดตั้ง quicksort แบบปกตินั้นดีที่สุดในทางปฏิบัติ แต่ฉันแน่ใจว่ามีการใช้ quicksort ที่ดีขึ้นมากหรือมีบางเวอร์ชั่นไฮบริดของมันออกมา

นี่เป็นบทความบล็อกที่ดีโดยDavid R. MacIver ที่อธิบาย Timsort ว่าเป็นรูปแบบของการผสานการปรับตัว


17
@ ราฟาเอลเพื่อให้สำเร็จลุล่วง Timsort เป็นการผสานสำหรับ asymptotics และ insertion sort สำหรับอินพุตสั้นรวมทั้งฮิวริสติกบางอย่างเพื่อจัดการกับข้อมูลที่มีการเรียงลำดับการระเบิดเป็นครั้งคราว (ซึ่งเกิดขึ้นบ่อยครั้งในทางปฏิบัติ) Dai: นอกจากอัลกอริทึมแล้วยังได้list.sortรับประโยชน์จากการเป็นฟังก์ชั่นในตัวที่ปรับให้เหมาะกับมืออาชีพ การเปรียบเทียบที่เป็นธรรมจะทำให้ฟังก์ชั่นทั้งหมดเขียนด้วยภาษาเดียวกันในระดับเดียวกัน
Gilles

1
@ ได: อย่างน้อยคุณสามารถอธิบายด้วยชนิดของอินพุต (การกระจายการตอบสนองของพวกเขา) ภายใต้สถานการณ์ใด (RAM ต่ำ, การใช้งานแบบขนานอย่างใดอย่างหนึ่ง, ... ) คุณได้รับผลลัพธ์ของคุณ
กราฟิลส์

7
เราทดสอบในรายการของตัวเลขสุ่มและเรียงบางส่วนเรียงอย่างสมบูรณ์และเรียงกลับกัน มันเป็นหลักสูตรเบื้องต้นปีแรกดังนั้นจึงไม่ใช่การศึกษาเชิงประจักษ์ แต่ความจริงที่ว่าตอนนี้มันถูกใช้อย่างเป็นทางการในการจัดเรียงอาร์เรย์ใน Java SE 7 และบนแพลตฟอร์ม Android นั้นมีความหมายอะไรบางอย่าง
ได

3
สิ่งนี้ถูกกล่าวถึงที่นี่ด้วย: cstheory.stackexchange.com/a/927/74
Jukka Suomela

34

ฉันคิดว่าหนึ่งในสาเหตุหลักที่ QuickSort รวดเร็วมากเมื่อเทียบกับอัลกอริธึมการเรียงลำดับอื่น ๆ ก็เพราะว่ามันเป็นมิตรกับแคช เมื่อ QS ประมวลผลเซกเมนต์ของอาร์เรย์มันจะเข้าถึงองค์ประกอบที่จุดเริ่มต้นและจุดสิ้นสุดของเซกเมนต์และย้ายไปยังกึ่งกลางของเซกเมนต์

ดังนั้นเมื่อคุณเริ่มต้นคุณจะเข้าถึงองค์ประกอบแรกในอาร์เรย์และโหลดหน่วยความจำ ("ตำแหน่ง") ลงในแคช และเมื่อคุณพยายามเข้าถึงองค์ประกอบที่สองมัน (น่าจะ) อยู่ในแคชอยู่แล้วดังนั้นมันจึงเร็วมาก

อัลกอริธึมอื่น ๆ เช่นฮีปพอร์ตไม่ทำงานอย่างนี้พวกมันกระโดดเข้าแถวมากซึ่งทำให้ช้าลง


5
นั่นเป็นคำอธิบายที่โต้แย้งได้: การรวมกันเป็นมิตรกับแคช
Dmytro Korduban

2
ฉันคิดว่าคำตอบนี้ถูกต้องแล้ว แต่นี่คือรายละเอียดบางส่วนyoutube.com/watch?v=aMnn0Jq0J-E
rgrig

3
อาจจะเป็นค่าคงที่ทวีคูณสำหรับความซับซ้อนของเวลาโดยเฉลี่ยในกรณีของการจัดเรียงอย่างรวดเร็วก็ดีกว่า (เป็นอิสระจากปัจจัยแคชที่คุณได้กล่าวถึง)
Kaveh

1
จุดที่คุณกล่าวถึงนั้นไม่สำคัญเมื่อเทียบกับคุณสมบัติที่ดีอื่น ๆ ของการจัดเรียงอย่างรวดเร็ว
MMS

1
@Kaveh: "ค่าคงที่การคูณสำหรับความซับซ้อนของเวลาโดยเฉลี่ยในกรณีของการเรียงลำดับแบบด่วนก็ดีกว่า" คุณมีข้อมูลอะไรบ้างไหม?
Giorgio

29

คนอื่น ๆ ได้บอกไปแล้วว่ารันไทม์เฉลี่ยของ Quicksort นั้นดีกว่า (ในค่าคงที่) กว่าอัลกอริทึมการเรียงลำดับอื่น ๆ (ในการตั้งค่าบางอย่าง)

O(nlogn)

โปรดทราบว่ามีหลายสายพันธุ์ของ Quicksort (ดูเช่นวิทยานิพนธ์ของ Sedgewick) พวกเขาดำเนินการแตกต่างกันในการแจกแจงอินพุตที่แตกต่างกัน (เหมือนกันเกือบเรียงกันเกือบเรียงกันเรียงซ้ำหลายรายการ ... ) และอัลกอริทึมอื่น ๆ อาจดีกว่าสำหรับบางคน

k10


20

O(nlgn)

ป.ล. : จะแม่นยำถูกกว่าดีกว่าอัลกอริทึมอื่น ๆ ขึ้นอยู่กับงาน สำหรับบางงานอาจเป็นการดีกว่าถ้าใช้อัลกอริธึมการเรียงลำดับอื่น

ดูสิ่งนี้ด้วย:


3
@ Janoma นี่เป็นเรื่องของภาษาและคอมไพเลอร์ที่คุณใช้ เกือบทุกภาษาที่ใช้งานได้ (ML, Lisp, Haskell) สามารถทำการเพิ่มประสิทธิภาพที่ป้องกันสแต็คจากการเติบโตและคอมไพเลอร์ที่ชาญฉลาดขึ้นสำหรับภาษาที่จำเป็นสามารถทำเช่นเดียวกัน (GCC, G ++ และฉันเชื่อว่า MSVC ทำสิ่งนี้) ข้อยกเว้นที่น่าสังเกตคือ Java ซึ่งจะไม่ทำการเพิ่มประสิทธิภาพนี้ดังนั้นจึงควรใช้ Java ในการเขียนการสอบถามซ้ำเป็นการวนซ้ำ
Rafe Kettler

4
@JD คุณไม่สามารถใช้การเพิ่มประสิทธิภาพการโทรด้วย tailsort (อย่างน้อยก็ไม่สมบูรณ์) เพราะมันเรียกตัวเองสองครั้ง คุณสามารถปรับการโทรครั้งที่สองให้เหมาะสม แต่ไม่ใช่สายแรก
svick

1
@ Janoma คุณไม่จำเป็นต้องมีการใช้งานแบบเรียกซ้ำ ตัวอย่างเช่นถ้าคุณดูการใช้งาน qsort ใน C มันไม่ได้ใช้การเรียกซ้ำและดังนั้นการใช้งานจะกลายเป็นเร็วขึ้นมาก
Kaveh

1
ฮีปพอร์ตยังมีอยู่ในสถานที่เหตุใด QS มักเร็วกว่า
Kevin

6
23240

16

Θ(n2)Θ(nlogn)

เหตุผลที่สองคือทำการin-placeเรียงลำดับและทำงานได้ดีกับสภาพแวดล้อมหน่วยความจำเสมือน

UPDATE:: (หลังจากความคิดเห็นของ Janoma และ Svick)

เพื่อแสดงให้เห็นถึงสิ่งที่ดีกว่านี้ให้ฉันยกตัวอย่างโดยใช้ Merge Sort (เพราะ Merge sort เป็นอัลกอริทึมการเรียงลำดับที่นำมาใช้กันอย่างแพร่หลายหลังจากการเรียงลำดับอย่างรวดเร็วฉันคิดว่า) และบอกคุณว่าค่าคงที่พิเศษมาจากไหน จัดเรียงด่วนดีกว่า):

พิจารณา seqence ต่อไปนี้:

12,30,21,8,6,9,1,7. The merge sort algorithm works as follows:

(a) 12,30,21,8    6,9,1,7  //divide stage
(b) 12,30   21,8   6,9   1,7   //divide stage
(c) 12   30   21   8   6   9   1   7   //Final divide stage
(d) 12,30   8,21   6,9   1,7   //Merge Stage
(e) 8,12,21,30   .....     // Analyze this stage

หากคุณสนใจอย่างเต็มที่ว่าขั้นตอนสุดท้ายเกิดขึ้นอย่างไร 12 ครั้งแรกเมื่อเทียบกับ 8 และ 8 นั้นเล็กลงดังนั้นมันจะไปก่อน ตอนนี้ 12 เป็นอีกครั้งเมื่อเทียบกับ 21 และ 12 ไปต่อไปเรื่อย ๆ หากคุณใช้การรวมสุดท้ายเช่น 4 องค์ประกอบที่มีอีก 4 องค์ประกอบมันจะมีการเปรียบเทียบพิเศษมากมายเป็นค่าคงที่ซึ่งไม่ได้เกิดขึ้นใน Quick Sort นี่คือเหตุผลว่าทำไมการเรียงลำดับด่วนจึงเป็นที่ต้องการ


1
แต่อะไรทำให้ค่าคงที่เล็กเหลือเกิน
svick

1
@svick เนื่องจากมีการเรียงลำดับin-placeเช่นไม่จำเป็นต้องมีหน่วยความจำเพิ่มเติม
0x0

Θ(nlgn)

15

ประสบการณ์ของการทำงานกับข้อมูลโลกแห่งความจริงก็คือว่าquicksort เป็นทางเลือกที่ดี Quicksort ทำงานได้ดีกับข้อมูลแบบสุ่ม แต่ข้อมูลในโลกแห่งความเป็นจริงมักไม่สุ่ม

ย้อนกลับไปในปี 2008 ฉันติดตามข้อผิดพลาดของซอฟต์แวร์ที่ค้างอยู่เพื่อใช้งาน quicksort ในขณะที่ภายหลังฉันเขียน implentations ง่าย ๆ ของการเรียงลำดับแทรก, quicksort, heap sort และผสานเรียงและทดสอบเหล่านี้ การผสานของฉันเรียงลำดับดีกว่าที่อื่น ๆ ทั้งหมดในขณะที่ทำงานกับชุดข้อมูลขนาดใหญ่

ตั้งแต่นั้นมาผสานการเรียงเป็นอัลกอริทึมการเรียงลำดับที่ฉันเลือก มันเป็นสง่า มันง่ายที่จะใช้ มันเป็นประเภทที่มั่นคง มันไม่ได้ลดทอนพฤติกรรมกำลังสองอย่างที่ quicksort ทำ ฉันสลับไปที่การเรียงลำดับการแทรกเพื่อเรียงลำดับอาร์เรย์ขนาดเล็ก

หลายครั้งที่ฉันพบว่าตัวเองคิดว่าการติดตั้งที่ได้ผลนั้นทำได้ดีอย่างน่าประหลาดใจสำหรับ Quicksort เพียงเพื่อจะพบว่าจริง ๆ แล้วมันไม่ใช่ Quicksort บางครั้งการใช้งานจะสลับระหว่าง quicksort และอัลกอริทึมอื่นและบางครั้งก็ไม่ได้ใช้ quicksort เลย ตัวอย่างเช่นฟังก์ชัน qsort () ของ GLibc ใช้การเรียงลำดับผสาน เฉพาะในกรณีที่การจัดสรรพื้นที่ทำงานล้มเหลวไม่ก็ถอยกลับไป quicksort ในสถานที่ซึ่งความคิดเห็นรหัสเรียกว่า "อัลกอริทึมที่ช้าลง"

แก้ไข: ภาษาการเขียนโปรแกรมเช่น Java, Python และ Perl ยังใช้การจัดเรียงผสานหรืออนุพันธ์ที่แม่นยำยิ่งขึ้นเช่นเรียงลำดับ Timsort หรือผสานสำหรับชุดใหญ่และเรียงแทรกสำหรับชุดเล็ก (Java ยังใช้ dual-pivot quicksort ซึ่งเร็วกว่า quicksort ธรรมดา)


ฉันได้เห็นบางสิ่งที่คล้ายกันนี้เพราะเรามีการผนวก / หันไปใช้อย่างต่อเนื่องเพื่อแทรกลงในกลุ่มของข้อมูลที่เรียงลำดับแล้ว คุณสามารถหลีกเลี่ยงสิ่งนี้โดยเฉลี่ยโดยใช้การสุ่มแบบเร็ว (และประหลาดใจกับการเรียงลำดับช้า ๆ ที่หายากและสุ่มมาก) หรือคุณสามารถทนต่อการเรียงลำดับที่ช้ากว่าเสมอซึ่งไม่ต้องใช้เวลานานในการทำ บางครั้งคุณต้องการความมั่นคงในการเรียงลำดับเช่นกัน Java ได้หายไปจากการใช้การจัดเรียงผสานกับตัวแปร quicksort
Rob

@Rob นี้ไม่ถูกต้อง Java ยังคงใช้ตัวแปรของการรวมกิจการ (Timsort) จนถึงทุกวันนี้ มันใช้ตัวแปรของ quicksort เช่นกัน (dual-pivot quicksort)
Erwan Legrand

14

1 - การเรียงลำดับแบบด่วนคือ inplace (ไม่จำเป็นต้องมี memmory พิเศษนอกเหนือจากจำนวนคงที่)

2 - การเรียงแบบด่วนนั้นง่ายกว่าการใช้งานมากกว่าอัลกอริธึมการเรียงลำดับที่มีประสิทธิภาพ

3 - การจัดเรียงอย่างรวดเร็วมีปัจจัยคงที่ในเวลาทำงานน้อยกว่าอัลกอริทึมการเรียงลำดับที่มีประสิทธิภาพอื่น ๆ

อัปเดต: สำหรับการจัดเรียงผสานคุณต้องทำ "การผสาน" ซึ่งต้องมีอาร์เรย์พิเศษเพื่อจัดเก็บข้อมูลก่อนทำการผสาน แต่ในการจัดเรียงอย่างรวดเร็วคุณทำไม่ได้ นั่นเป็นเหตุผลที่จัดเรียงอย่างรวดเร็วอยู่ในสถานที่ นอกจากนี้ยังมีการเปรียบเทียบพิเศษบางอย่างสำหรับการรวมซึ่งเพิ่มปัจจัยคงที่ในการเรียงลำดับการผสาน


3
คุณเคยเห็นการใช้งาน Quicksort ขั้นสูงซ้ำแล้วซ้ำอีกหรือไม่? มีหลายสิ่งหลายอย่าง แต่ไม่ใช่ "ง่าย"
Raphael

2
หมายเลข 2 ไม่ตอบคำถามของฉันเลยและหมายเลข 1 และ 3 ต้องการเหตุผลที่เหมาะสมในความคิดของฉัน
Janoma

@ ราฟาเอล: พวกมันง่าย การใช้การเรียงลำดับแบบด่วนทำได้ง่ายกว่ามากโดยใช้อาร์เรย์แทนที่จะเป็นพอยน์เตอร์ และไม่จำเป็นต้องซ้ำในสถานที่
MMS

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

@ 1 การควบรวมกิจการก็สามารถทำได้เช่นกัน @ 2 กำหนดอะไรที่มีประสิทธิภาพ? ฉันชอบการเรียงลำดับเพราะมันง่ายมากและมีประสิทธิภาพในความคิดของฉัน @ 3 ไม่เกี่ยวข้องเมื่อคุณเรียงลำดับข้อมูลจำนวนมากและต้องการให้มีการใช้อัลกอริทึมอย่างมีประสิทธิภาพ
Oskar Skog

11

อัลกอริทึมการเรียงลำดับเฉพาะเงื่อนไขใดที่จริงแล้วเป็นวิธีที่เร็วที่สุด

Θ(log(n)2)Θ(nlog(n)2)

Θ(nk)Θ(nm)k=2#number_of_Possible_valuesm=#maximum_length_of_keys

3) โครงสร้างข้อมูลพื้นฐานประกอบด้วยองค์ประกอบที่เชื่อมโยงหรือไม่? ใช่ -> ใช้ทุกอย่างในการเรียงลำดับผสาน มีทั้งง่ายต่อการใช้ขนาดคงที่หรือจากล่างขึ้นบนที่ปรับได้ (รวมถึงธรรมชาติ) ในลักษณะที่รวมกันของ arities ที่หลากหลายสำหรับโครงสร้างข้อมูลที่เชื่อมโยงและเนื่องจากพวกเขาไม่ต้องการคัดลอกข้อมูลทั้งหมดในแต่ละขั้นตอน เร็วกว่าการเรียงลำดับแบบอิงการเปรียบเทียบทั่วไปอื่น ๆ เร็วกว่าการเรียงลำดับด่วน

Θ(n)

5) ขนาดของข้อมูลพื้นฐานสามารถผูกกับขนาดเล็กถึงขนาดกลางได้หรือไม่? เช่น n <10,000 ... 100,000,000 (ขึ้นอยู่กับสถาปัตยกรรมพื้นฐานและโครงสร้างข้อมูล)? ใช่ -> ใช้ bitonic sort หรือ Batcher การผสานคู่คี่ ไปที่ 1)

Θ(n)Θ(n2)Θ(nlog(n)2)เป็นที่รู้กันว่าเวลาเรียกใช้กรณีที่เลวร้ายที่สุดหรืออาจลองเรียงลำดับหวี ฉันไม่แน่ใจว่าการเรียงลำดับของเปลือกหอยหรือเรียงลำดับหวีจะทำงานได้ดีพอสมควรในทางปฏิบัติ

Θ(log(n))Θ(n)Θ(n)Θ(log(n))Θ(n2)Θ(n)Θ(n)Θ(log(n))Θ(nlog(n))

Θ(nlog(n))

คำแนะนำการใช้งานสำหรับ quicksort:

Θ(n)Θ(log(n))Θ(nlogk(k1))

2) มีตัวแปรจากล่างขึ้นบนซ้ำซ้ำของ quicksort แต่ AFAIK มีพื้นที่ asymptotic และขอบเขตเวลาเดียวกับ top-down และด้านล่างของการใช้งานยาก (เช่นการจัดการคิวอย่างชัดเจน) ประสบการณ์ของฉันคือเพื่อวัตถุประสงค์ในทางปฏิบัติใด ๆ เหล่านั้นจะไม่คุ้มค่าพิจารณา

คำแนะนำการใช้งานสำหรับการรวม:

1) การรวม bottum-up จะเร็วกว่าการรวมจากบนลงล่างเสมอเนื่องจากไม่ต้องมีการเรียกซ้ำ

2) การผสานอย่างไร้เดียงสาอาจเร่งโดยใช้ double buffer และเปลี่ยน buffer แทนการคัดลอกข้อมูลกลับจาก array ชั่วคราวหลังจากแต่ละขั้นตอน

3) สำหรับข้อมูลจริงจำนวนมากการผสานการปรับตัวนั้นเร็วกว่าการผสานขนาดคงที่

Θ(k)Θ(log(k))Θ(1)Θ(n)

จากสิ่งที่ฉันเขียนเป็นที่ชัดเจนว่า quicksort มักไม่ใช่อัลกอริทึมที่เร็วที่สุดยกเว้นเมื่อมีเงื่อนไขต่อไปนี้ทั้งหมด:

1) มีค่าที่เป็นไปได้มากกว่า "ไม่กี่"

2) โครงสร้างข้อมูลที่ไม่ได้เชื่อมโยง

3) เราไม่ต้องการคำสั่งที่มั่นคง

4) ข้อมูลมีขนาดใหญ่พอที่ช่วงเวลาแบบ asymptotic ที่ดีที่สุดเล็กน้อยของตัวเรียงลำดับ bitonic หรือการรวมกันของ Batcher คี่ - คี่ใน

5) ข้อมูลไม่ได้ถูกจัดเรียงเกือบและไม่ประกอบด้วยชิ้นส่วนที่ใหญ่กว่าที่เรียงแล้ว

6) เราสามารถเข้าถึงลำดับข้อมูลพร้อมกันได้จากหลาย ๆ ที่

Θ(log(n))Θ(n)

ps: มีคนต้องการช่วยฉันจัดรูปแบบของข้อความ


(5): การดำเนินการเรียงลำดับของ Apple จะตรวจสอบหนึ่งการรันตามลำดับจากน้อยไปมากหรือมากไปหาน้อยทั้งที่จุดเริ่มต้นและจุดสิ้นสุดของอาร์เรย์ก่อน นี่เป็นวิธีที่รวดเร็วมากหากไม่มีองค์ประกอบดังกล่าวจำนวนมากและสามารถจัดการองค์ประกอบเหล่านี้ได้อย่างมีประสิทธิภาพหากมีมากกว่า n / ln n ขององค์ประกอบเหล่านั้น เชื่อมต่อสองอาร์เรย์ที่เรียงลำดับแล้วเรียงลำดับผลลัพธ์และคุณจะได้รับการรวม
gnasher729

8

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

ab


5
ข้อโต้แย้งของคุณเกี่ยวกับการเรียงลำดับด่วนเทียบกับการผสานไม่ได้เก็บน้ำไว้ Quicksort เริ่มต้นด้วยการย้ายที่มีขนาดใหญ่จากนั้นทำการย้ายที่เล็กลงและเล็กลง (ประมาณครึ่งหนึ่งมีขนาดใหญ่ในแต่ละขั้นตอน) การจัดเรียงผสานเริ่มต้นด้วยการเคลื่อนไหวขนาดเล็กจากนั้นทำการย้ายที่ใหญ่ขึ้นและใหญ่ขึ้น (ประมาณสองเท่าในแต่ละขั้นตอน) สิ่งนี้ไม่ได้ชี้ว่ามีประสิทธิภาพมากกว่าอีกอัน
Gilles
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.