อัลกอริทึมการแบ่งและพิชิต - ทำไมไม่แยกส่วนมากกว่าสอง


33

ในขั้นตอนวิธีแบ่งแยกและเอาชนะเช่น quicksort และ mergesort ใส่มักจะ (อย่างน้อยในตำราเบื้องต้น) แยกเป็นสองและทั้งสองมีขนาดเล็กชุดข้อมูลจะได้รับไพ่แล้วด้วยซ้ำ มันสมเหตุสมผลสำหรับฉันว่าสิ่งนี้ทำให้การแก้ไขปัญหาได้เร็วขึ้นหากทั้งสองส่วนใช้เวลาน้อยกว่าครึ่งในการจัดการกับชุดข้อมูลทั้งหมด แต่ทำไมไม่แยกชุดข้อมูลออกเป็นสามส่วน? สี่? n ?

ฉันเดาว่างานของการแบ่งข้อมูลในชุดย่อยหลายชุดทำให้ไม่คุ้มค่า แต่ฉันขาดสัญชาตญาณเพื่อดูว่าควรหยุดที่สองชุดย่อย

ฉันยังได้เห็นการอ้างอิงจำนวนมากถึง QuickSort 3 ทาง เมื่อไหร่จะเร็วกว่านี้? สิ่งที่ใช้ในการปฏิบัติ?


ลองสร้างอัลกอริทึมที่คล้ายกับ quicksort ที่แบ่งอาร์เรย์ออกเป็นสามส่วน
gnasher729

คำตอบ:


49

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

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

แต่ทำไมไม่แยกชุดข้อมูลออกเป็นสามส่วน? สี่? n?

สาเหตุหลักมาจากการแยกออกเป็นสองส่วนและรวบรวมผลลัพธ์มากกว่าสองรายการส่งผลให้เกิดการใช้งานที่ซับซ้อนมากขึ้น แต่ไม่ได้เปลี่ยนลักษณะพื้นฐาน (Big O) ของอัลกอริทึม - ความแตกต่างเป็นปัจจัยคงที่และอาจส่งผลให้การชะลอตัว ถ้าการหารและการรวมเข้าด้วยกันอีกครั้งของ 2 เซ็ตย่อยสร้างค่าใช้จ่ายเพิ่มเติม

ตัวอย่างเช่นหากคุณทำการเรียงแบบผสาน 3 ทางจากนั้นในขั้นตอนการรวมตัวกันใหม่ตอนนี้คุณต้องค้นหาองค์ประกอบที่ใหญ่ที่สุดของ 3 องค์ประกอบสำหรับทุกองค์ประกอบซึ่งต้องใช้การเปรียบเทียบ 2 แทนที่จะเป็น 1 ดังนั้นคุณจะทำการเปรียบเทียบสองเท่า . ในการแลกเปลี่ยนคุณลดความลึกของการเรียกซ้ำโดยใช้ ln (2) / ln (3) == 0.63 ดังนั้นคุณจึงมีการแลกเปลี่ยนน้อยลง 37% แต่ 2 * 0.63 == การเปรียบเทียบเพิ่มเติม 26% (และการเข้าถึงหน่วยความจำ) ไม่ว่าจะดีหรือไม่ดีขึ้นอยู่กับว่าฮาร์ดแวร์ของคุณมีราคาแพงกว่าหรือไม่

ฉันยังได้เห็นการอ้างอิงจำนวนมากถึง QuickSort 3 ทาง เมื่อไหร่จะเร็วกว่านี้?

เห็นได้ชัดว่าตัวแปรเดือยคู่ของ quicksortสามารถพิสูจน์ได้ว่าต้องการการเปรียบเทียบจำนวนเท่ากัน แต่โดยเฉลี่ยน้อยกว่า 20% swaps ดังนั้นจึงเป็นกำไรสุทธิ

สิ่งที่ใช้ในการปฏิบัติ?

ทุกวันนี้แทบจะไม่มีใครเขียนโปรแกรมอัลกอริธึมการเรียงลำดับของตัวเองอีกต่อไป พวกเขาใช้อย่างใดอย่างหนึ่งโดยห้องสมุด ตัวอย่างเช่นJava 7 APIจริง ๆ แล้วใช้ dual-pivot quicksort

คนที่ตั้งโปรแกรมอัลกอริทึมการเรียงลำดับของตัวเองด้วยเหตุผลบางอย่างจะมีแนวโน้มที่จะยึดติดกับตัวแปรสองทางแบบง่าย ๆ เพราะโอกาสที่จะเกิดข้อผิดพลาดน้อยกว่านั้นจะทำให้ประสิทธิภาพดีขึ้น 20% เป็นส่วนใหญ่ โปรดจำไว้ว่า: การปรับปรุงประสิทธิภาพที่สำคัญที่สุดคือเมื่อรหัสเปลี่ยนจาก "ไม่ทำงาน" เป็น "กำลังทำงาน"


1
บันทึกย่อขนาดเล็ก: Java 7 ใช้ Quicksort Dual-Pivot เฉพาะเมื่อทำการเรียงลำดับแบบดั้งเดิม เพื่อเรียงลำดับวัตถุจะใช้ Timsort
Bakuriu

1
+1 สำหรับ "ทุกวันนี้แทบทุกคนจะตั้งโปรแกรมอัลกอริธึมการเรียงลำดับของตัวเองอีกต่อไป" และ (ที่สำคัญกว่านั้น) "โปรดจำไว้ว่า: การปรับปรุงประสิทธิภาพที่สำคัญที่สุดคือตอนที่โค้ดเปลี่ยนจาก อย่างไรก็ตามฉันต้องการทราบว่าค่าใช้จ่ายนั้นยังคงไม่สำคัญหากหนึ่งแยกข้อมูลที่กำหนดเป็นหลายส่วน อย่างที่มันเกิดขึ้นดังนั้นมีคนอื่นด้วย: bealto.com/gpu-sorting_intro.html stackoverflow.com/questions/1415679/… devgurus.amd.com/thread/157159
AndrewJacksonZA

ฉันเพียงเล็กน้อยช้า. ใครช่วยอธิบายได้บ้างว่าทำไมจึงต้องใช้การเปรียบเทียบเพิ่มเติม 2 * 0.69 ไม่แน่ใจว่ามาจากไหน 0.69
jeebface

@ jeebface อุ๊ปส์นั่นเป็นตัวพิมพ์ผิด เป็น 0.63 (การลดลงของความลึกการเรียกซ้ำ) จากนั้นผลที่มากขึ้น 26% ก็ใช้ได้เช่นกัน
Michael Borgwardt

30

Asymptotically พูดมันไม่สำคัญ ตัวอย่างเช่นการค้นหาแบบไบนารี่ทำการ เปรียบเทียบล็อก2 n โดยประมาณและการค้นหาแบบไตรภาคทำให้ การเปรียบเทียบล็อก3 n โดยประมาณ ถ้าคุณรู้ลอการิทึมของคุณคุณรู้ว่าบันทึกa  = บันทึกb  x / บันทึกb  a ดังนั้นการค้นหาแบบไบนารีทำให้เพียงประมาณ 1 / log 3 2 ≈ 1.5 เท่าของการเปรียบเทียบให้มากเท่ากับการค้นหาแบบไตรภาค นี่คือเหตุผลที่ไม่มีใครระบุฐานของลอการิทึมในสัญกรณ์โอ้ใหญ่: มันเป็นปัจจัยคงที่เสมอห่างจากลอการิทึมในฐานที่กำหนดไม่ว่าฐานจริงจะเป็นอะไร ดังนั้นการแยกปัญหาออกเป็นชุดย่อยเพิ่มเติมจึงไม่ได้ปรับปรุงความซับซ้อนของเวลาและในทางปฏิบัติไม่เพียงพอที่จะเทียบกับตรรกะที่ซับซ้อนมากขึ้น ในความเป็นจริงความซับซ้อนนั้นอาจส่งผลต่อประสิทธิภาพในทางปฏิบัติเพิ่มความดันแคชหรือทำให้การเพิ่มประสิทธิภาพของไมโครเป็นไปไม่ได้น้อยลง

ในทางกลับกันโครงสร้างข้อมูล tree-ish บางอย่างใช้ปัจจัยการแตกแขนงสูง (ใหญ่กว่า 3, 32 หรือมากกว่านั้น) แต่มักจะด้วยเหตุผลอื่น มันปรับปรุงการใช้ประโยชน์จากลำดับชั้นของหน่วยความจำ: โครงสร้างข้อมูลที่เก็บไว้ใน RAM ใช้ประโยชน์จากแคชได้ดีขึ้นโครงสร้างข้อมูลที่เก็บไว้ในดิสก์ต้องการการอ่าน HDD-> RAM น้อยลง


ใช่ค้นหา octree สำหรับแอปพลิเคชันเฉพาะของโครงสร้างต้นไม้ไบนารีมากกว่า
daaxix

@daaxix btree น่าจะเป็นเรื่องธรรมดามากกว่า
จูลส์

4

มีอัลกอริธึมการค้นหา / จัดเรียงที่แบ่งย่อยไม่ใช่สอง แต่โดย N

ตัวอย่างง่ายๆคือการค้นหาโดยการเข้ารหัสแฮชซึ่งใช้เวลา O (1)

หากฟังก์ชั่นแฮชเป็นการรักษาลำดับไว้สามารถใช้เพื่อสร้างอัลกอริทึมการเรียงลำดับ O (N) (คุณสามารถนึกถึงอัลกอริทึมการเรียงลำดับใด ๆ เพียงแค่ทำการค้นหา N เพื่อหาว่าตัวเลขควรไปที่ใดในผลลัพธ์)

ปัญหาพื้นฐานคือเมื่อโปรแกรมตรวจสอบข้อมูลบางส่วนจากนั้นเข้าสู่สถานะต่อไปนี้มีสถานะต่อไปนี้กี่สถานะ

เมื่อคอมพิวเตอร์ทำการเปรียบเทียบตัวเลขสองตัวพูดแล้วก็กระโดดหรือไม่ถ้าทั้งสองเส้นทางมีแนวโน้มเท่ากันตัวนับโปรแกรม "รู้" อีกหนึ่งบิตของข้อมูลในแต่ละเส้นทางดังนั้นโดยเฉลี่ยแล้วมันได้ "เรียนรู้" บิต. หากปัญหาต้องการให้บิต M ต้องเรียนรู้ดังนั้นการใช้การตัดสินใจแบบไบนารีจะไม่ได้รับคำตอบในการตัดสินใจที่น้อยกว่า M ยกตัวอย่างเช่นการค้นหาตัวเลขในตารางที่เรียงลำดับขนาด 1024 ไม่สามารถทำได้ในการตัดสินใจไบนารีที่น้อยกว่า 10 การตัดสินใจถ้าเพียงเพราะการที่น้อยกว่าจะไม่มีผลลัพธ์เพียงพอ แต่ก็สามารถทำได้มากกว่านั้น

เมื่อคอมพิวเตอร์ใช้หมายเลขหนึ่งและแปลงมันเป็นดัชนีในอาร์เรย์มันจะ "เรียนรู้" จนถึงล็อกฐาน 2 ของจำนวนองค์ประกอบในอาร์เรย์และมันจะทำในเวลาคงที่ ตัวอย่างเช่นหากมีตารางกระโดดจำนวน 1024 รายการมีโอกาสมากหรือน้อยเท่ากันทั้งหมดแล้วกระโดดผ่านตารางนั้น "เรียนรู้" 10 บิต นั่นเป็นเคล็ดลับพื้นฐานที่อยู่เบื้องหลังการเข้ารหัสแฮช ตัวอย่างการเรียงลำดับของสิ่งนี้คือวิธีที่คุณสามารถเรียงลำดับไพ่ได้ มี 52 ถังขยะหนึ่งสำหรับแต่ละบัตร เหวี่ยงไพ่แต่ละใบลงในถังขยะจากนั้นให้ตักขึ้นมา ไม่ต้องแบ่งย่อย


1

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

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

Karatsubaอัลกอริทึมสำหรับการคูณใช้แบ่ง 3 ทางและพิชิตเพื่อให้บรรลุเวลาการทำงานของ O (3 n ^ log_2 3) ซึ่งเต้น O, (n ^ 2) สำหรับขั้นตอนวิธีการคูณสามัญ (n คือจำนวนตัวเลขใน ตัวเลข)


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

-4

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

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