ทำไม Radix Sort จึงใช้ไม่บ่อยกว่านี้?


31

มันมีเสถียรภาพและมีความซับซ้อนเวลาของ O (n) มันควรจะเร็วกว่าอัลกอริทึมอย่าง Quicksort และ Mergesort แต่ฉันแทบจะไม่เคยเห็นมันมาใช้เลย


2
ดูที่นี่: en.wikipedia.org/wiki/Radix_sort#Efficiencyประสิทธิภาพคือ O (kn) และอาจไม่ดีกว่า O (n * log (n))
FrustratedWithFormsDesigner

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

@FrustratedWithFormsDesigner บางทีวิกิมีการเปลี่ยนแปลงหรือไม่? ฉันไม่เห็นการอ้างอิงไปยัง `n ล็อก (n)อีกต่อไป ... FWIW
rogerdpack

Boost มีตัวแปร (ในตำแหน่ง): boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.htmlแต่ใช่ฉันคิดว่าผู้คนไม่รู้ว่ามีอยู่จริง ... ไม่ว่าจะใช้วิธีการเรียงลำดับแบบ "มาตรฐาน" ซึ่งไม่ว่าด้วยเหตุผลใดผู้สร้างเฟรมเวิร์กมักจะยังคงใช้ประเภท "ทั่วไป" ที่ไม่มีประสิทธิภาพอีกครั้ง ... บางทีพวกเขาอาจไม่ได้มุ่งเน้นไปที่การเรียงลำดับ int โดยทั่วไปแล้วเนื่องจากเป็นกรณีที่ใช้ยากกว่า
rogerdpack

คำตอบ:


38

ซึ่งแตกต่างจากการจัดเรียง Radix, quicksort เป็นสากลในขณะที่การเรียง radix จะเป็นประโยชน์สำหรับคีย์จำนวนเต็มความยาวคงที่เท่านั้น

คุณต้องเข้าใจด้วยว่า O (f (n)) หมายถึง K * f (n) โดยที่ K นั้นมีค่าคงที่ตามอำเภอใจ สำหรับการเรียงเรเดียนซ์ K นี้เกิดขึ้นค่อนข้างใหญ่ (อย่างน้อยเรียงลำดับจำนวนบิตในจำนวนเต็มเรียง) ในทางตรงกันข้าม quicksort มี K ต่ำที่สุดแห่งหนึ่งในบรรดาอัลกอริทึมการเรียงลำดับและความซับซ้อนเฉลี่ยของ n * log (n) ดังนั้นใน quicksort สถานการณ์ในชีวิตจริงมักจะเร็วกว่าการเรียงลำดับ radix


หมายเหตุเกี่ยวกับความซับซ้อนที่ระบุไว้: แม้ว่า (LSD) Radix sort มีความซับซ้อนของ O (n * K) ค่าคงที่นี้มักจะมีขนาดเล็กโดยทั่วไปเลือกเช่น (2 ^ (W / K)) * C พอดีกับ L1 โดยที่ C คือขนาดเป็นไบต์ของตัวนับ W คือขนาดของคีย์ที่เรียงลำดับ การใช้งานส่วนใหญ่เลือก K = [3,4] สำหรับคำ 32- บิตใน x86 K ยังสามารถปรับให้เข้ากับการใช้ประโยชน์จากการเชื่อมโยงชั่วคราว
awdz9nld

11
หมายเหตุเกี่ยวกับความเป็นสากล: การเรียงแบบ Radix สามารถใช้งานได้อย่างเต็มที่บนปุ่มจุดลอยตัวเช่นเดียวกับปุ่มจำนวนเต็มแบบยาว
awdz9nld

20

อัลกอริทึมการเรียงลำดับส่วนใหญ่มีวัตถุประสงค์ทั่วไป รับฟังก์ชั่นการเปรียบเทียบพวกมันทำงานอะไรก็ได้และอัลกอริธึมอย่าง Quicksort และ Heapsort จะเรียงลำดับด้วยหน่วยความจำเสริม O (1)

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

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


ที่จริงแล้ว quicksort ต้องการO(n^2)หน่วยความจำในกรณีที่แย่ที่สุดเนื่องจากการnเรียกซ้ำบนพาร์ติชั่นซ้ายและขวา หากการใช้งานใช้การปรับให้เหมาะสมแบบเรียกซ้ำหางซึ่งสามารถลดลงเหลือเพียงO(n)เนื่องจากการเรียกไปยังพาร์ติชันที่เหมาะสมไม่จำเป็นต้องใช้พื้นที่เพิ่มเติม ( en.wikipedia.org/wiki/Quicksort#Space_complexity )
Splinter of Chaos

คุณต้องการS(n) \in O(n)พื้นที่สำหรับการเรียงด้วย radix เช่นเดียวกับ heap หรือการเรียงแบบด่วน
Velda

@SplinterofChaos wiki อาจมีการเปลี่ยนแปลงหรือไม่? ดูเหมือนจะไม่พูดถึงn^2เรื่อง quicksort อีกต่อไป แต่O(log n)...
rogerdpack

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

5

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

ในบางครั้งเกณฑ์ถูกกำหนดให้ดำเนินการเท่านั้น (จากสองเรคคอร์ดคุณสามารถตัดสินใจว่าจะมาก่อน แต่คุณไม่สามารถประเมินได้ว่าขนาดของเรคคอร์ดแยกต่างหาก 'ไกลแค่ไหน') ดังนั้นวิธีการนี้มักใช้ไม่ได้ใช้น้อยกว่าที่คุณอาจเชื่อหรือไม่เร็วกว่า O (n * log (n))


การจัดเรียง Radix สามารถจัดการจำนวนเต็ม (หรือสตริง) ในช่วงใด ๆ โดยเรียงลำดับพวกเขาซ้ำ "ไบต์ในเวลา" ดังนั้นพวกเขาจึงไม่ต้องอยู่ในช่วงเบาบาง FWIW ...
rogerdpack

4

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

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

สิ่งหนึ่งที่ฉันต้องการขว้างและพูดก็คือการเรียงตัวแบบ Radix สามารถทำงานกับจำนวนจุดลอยตัวและเนกาทีฟแม้ว่ามันจะยากในการเขียนรุ่น FP ที่พกพาได้มากที่สุด ในขณะที่มันเป็น O (n * K) K ก็ต้องเป็นจำนวนไบต์ของขนาดคีย์ (เช่น: จำนวนเต็ม 32- ล้านล้านบิตโดยทั่วไปจะผ่าน 4 ไบต์ขนาดถ้ามี 2 ^ 8 รายการในถัง ) รูปแบบการเข้าถึงหน่วยความจำยังมีแนวโน้มที่จะเป็นมิตรกับแคชมากกว่า quicksorts ถึงแม้ว่ามันจะต้องการอาเรย์แบบขนานและอาเรย์การฝากข้อมูลขนาดเล็กโดยทั่วไป QS อาจทำ 50 ล้าน swaps เพื่อจัดเรียงอาร์เรย์ของจำนวนเต็มหนึ่งล้านด้วยรูปแบบการเข้าถึงแบบสุ่มเป็นระยะ การเรียงลำดับ radix สามารถทำได้ในแบบเส้นตรง 4 แบบซึ่งเป็นมิตรกับแคชมากกว่าข้อมูล

อย่างไรก็ตามการขาดความตระหนักในความสามารถในการทำเช่นนี้กับ K ขนาดเล็กในจำนวนลบพร้อมกับ floating-point อาจมีส่วนสำคัญอย่างมากต่อการขาดความนิยมของ Radix

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

มาตรฐานบางอย่าง:

Sorting 10000000 elements 3 times...

mt_sort_int: {0.135 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

mt_radix_sort: {0.228 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

std::sort: {1.697 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

qsort: {2.610 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

และนั่นก็เป็นเพียงการดำเนินการที่ไร้เดียงสาของฉัน ( mt_sort_intเป็นการเรียงลำดับแบบ Radix แต่ด้วยรหัสสาขาที่เร็วกว่าเนื่องจากสามารถถือว่าคีย์เป็นจำนวนเต็มได้) ลองนึกภาพว่าการดำเนินการตามมาตรฐานแบบเขียนโดยผู้เชี่ยวชาญอาจรวดเร็วเพียงใด

กรณีเดียวที่ฉันพบว่าการเรียงเรดิสกับค่าโดยสารแย่กว่าการเปรียบเทียบอย่างรวดเร็วของ C ++ std::sortสำหรับองค์ประกอบจำนวนน้อยจริง ๆ เช่น 32 ซึ่ง ณ จุดนี้ฉันเชื่อว่าstd::sortเริ่มใช้การเรียงลำดับที่เหมาะสมกว่าสำหรับองค์ประกอบที่เล็กที่สุด ประเภทการแทรก std::sortแต่ที่จุดที่ฉันใช้การดำเนินการเพียง


1
ยินดีเสมอที่ได้รับฟังความคิดเห็นของผู้ที่มีประสบการณ์ในพื้นที่
Frank Hileman

ปรากฏ mt_ เป็นการใช้งานแบบหลายเธรด: softwareengineering.stackexchange.com/a/362097/65606
rogerdpack

1

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

ในโลกแห่งความเป็นจริงฉันได้ทำการจัดเรียง Radix หนึ่งครั้ง. นี่คือในอดีตเมื่อหน่วยความจำถูก จำกัด ฉันไม่สามารถนำข้อมูลทั้งหมดของฉันไปยังหน่วยความจำในครั้งเดียว นั่นหมายความว่าจำนวนการเข้าถึงข้อมูลมีความสำคัญมากกว่า O (n) และ O (n log n) ฉันทำหนึ่งผ่านข้อมูลที่จัดสรรแต่ละระเบียนไปยังถังขยะ (โดยรายการที่บันทึกอยู่ในถังขยะที่ไม่ได้ย้ายอะไรจริง ๆ ) สำหรับถังขยะที่ไม่ว่างแต่ละ (คีย์เรียงของฉันคือข้อความจะมีจำนวนมาก ช่องว่างเปล่า) ฉันตรวจสอบว่าฉันสามารถนำข้อมูลเข้าสู่หน่วยความจำจริงหรือไม่ถ้าใช่ให้นำเข้าและใช้ quicksort หากไม่มีให้สร้างไฟล์ชั่วคราวที่มีเฉพาะรายการในถังขยะและเรียกรูทีนซ้ำ ๆ (ในทางปฏิบัติถังขยะสองสามอันจะล้น) สิ่งนี้ทำให้เกิดการอ่านที่สมบูรณ์สองครั้งและการเขียนที่หนึ่งไปยังที่เก็บข้อมูลเครือข่ายสมบูรณ์

ทุกวันนี้ปัญหาเรื่องข้อมูลขนาดใหญ่นั้นยากที่จะหายากฉันอาจจะไม่เขียนอะไรแบบนั้นอีกเลย (ถ้าฉันต้องเผชิญกับข้อมูลเดียวกันวันนี้ฉันจะระบุระบบปฏิบัติการ 64 บิตเพิ่ม RAM ถ้าคุณได้รับการตีอย่างแรงในตัวแก้ไขนั้น)


สิ่งที่น่าสนใจเมื่อพิจารณาถึงข้อเสียอย่างหนึ่งที่กล่าวถึงการเรียงลำดับของ Radix บางครั้งที่กล่าวถึงคือ "ใช้เนื้อที่มากขึ้น" ยังคงพยายามที่จะตัดหัวของฉันรอบนี้ ...
rogerdpack

1
@rogerdpack ไม่ใช่ว่าแนวทางของฉันใช้พื้นที่น้อยกว่า แต่ใช้การเข้าถึงข้อมูลน้อยลง ฉันกำลังเรียงลำดับไฟล์ที่อยู่รอบ ๆ กิกะไบต์ในขณะที่จัดการกับขีด จำกัด ของคอมไพเลอร์ (นี่คือโหมดป้องกัน DOS ไม่ใช่ Windows) ของบิตต่ำกว่า 16mb ของการใช้หน่วยความจำทั้งหมดรวมถึงรหัสและขีด จำกัด โครงสร้าง 64kb
Loren Pechtel

-1

หากพารามิเตอร์ทั้งหมดของคุณเป็นจำนวนเต็มทั้งหมดและหากคุณมีพารามิเตอร์อินพุตมากกว่า 1024 พารามิเตอร์การเรียงลำดับแบบเลขฐานจะเร็วกว่าเสมอ

ทำไม?

Complexity of radix sort = max number of digits x number of input parameters.

Complexity of quick sort = log(number of input parameters) x   number of input parameters

ดังนั้นการเรียงลำดับของ Radix จะเร็วขึ้นเมื่อ

log(n)> max num of digits

จำนวนเต็มสูงสุดใน Java คือ 2147483647 ซึ่งมีความยาว 10 หลัก

ดังนั้นการเรียงลำดับของ Radix จะเร็วกว่าเสมอเมื่อ

log(n)> 10

ดังนั้นการเรียงลำดับของ Radix จะเร็วขึ้นเสมอเมื่อ n>1024


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