เหตุใด Collections.sort จึงใช้ Mergesort แต่ Arrays.sort ใช้ไม่ได้


97

ฉันใช้ JDK-8 (x64) สำหรับArrays.sort(primitives) ฉันพบสิ่งต่อไปนี้ในเอกสาร Java:

อัลกอริทึมการเรียงลำดับคือ Dual-Pivot Quicksortโดย Vladimir Yaroslavskiy, Jon Bentley และ Joshua Bloch

สำหรับCollections.sort(วัตถุ) ฉันพบ "Timsort" นี้:

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

ถ้าCollections.sortใช้อาร์เรย์ทำไมไม่ได้เพียงโทรArrays.sortหรือใช้แบบ dual-เดือยQuickSort ? ทำไมต้องใช้Mergesort ?


9
นั่นคือ javadoc สำหรับอาร์เรย์ของ primitives - อาร์เรย์ของวัตถุถูกจัดเรียงโดยใช้ meregsort
assylias

2
mergesort ให้คุณ nlogn เสมอในขณะที่ quicksort บางครั้งอาจให้ขนาดอาร์เรย์ของ nlogn2 ที่ไม่ใหญ่นัก แต่คอลเลกชันสามารถเข้าถึงได้มากถึงหลายล้านรายการดังนั้นการเสี่ยงต่อ nlogn2 จึงไม่คุ้มค่า PS nlogn2 ฉันหมายถึง sqaure ของ n
Kumar Saurabh

O (n ^ 2) สำหรับ Quicksort เป็นกรณีที่เลวร้ายที่สุด ในทางปฏิบัติจะเร็วกว่า
James Wierzba

แต่คุณไม่สามารถเพิกเฉยต่อ caese เหล่านั้นได้ในขณะที่สร้าง api
Kumar Saurabh

2
ลิงค์นี้มีความเกี่ยวข้องมาก
qartal

คำตอบ:


100

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

สำหรับวัตถุที่คุณอาจสังเกตเห็นเมื่อวัตถุที่มีเอกลักษณ์แตกต่างกันซึ่งถือว่าเท่ากันตามequalsการนำไปใช้หรือการComparatorเปลี่ยนแปลงลำดับของวัตถุนั้น ดังนั้นQuicksortจึงไม่ใช่ทางเลือก ดังนั้นตัวแปรของmergesortจะใช้รุ่น Java ปัจจุบันใช้TimSort สิ่งนี้ใช้ได้กับทั้งสองอย่างArrays.sortและCollections.sortแม้ว่าด้วย Java 8 Listตัวมันเองอาจแทนที่อัลกอริทึมการเรียงลำดับ


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

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

สถานการณ์ปัจจุบัน (รวมถึง Java 8 ถึง Java 11) มีดังนี้:

  • โดยทั่วไปวิธีการเรียงลำดับสำหรับอาร์เรย์ดั้งเดิมจะใช้Quicksort ในบางสถานการณ์เท่านั้น สำหรับอาร์เรย์ขนาดใหญ่พวกเขาจะพยายามระบุการรันของข้อมูลที่เรียงลำดับล่วงหน้าก่อนเช่นที่TimSortทำและจะรวมเข้าด้วยกันเมื่อจำนวนการรันไม่เกินเกณฑ์ที่กำหนด มิฉะนั้นพวกเขาจะถอยกลับไปที่Quicksortแต่ด้วยการใช้งานที่จะถอยกลับไปที่การจัดเรียงการแทรกสำหรับช่วงเล็ก ๆ ซึ่งไม่เพียงส่งผลต่ออาร์เรย์ขนาดเล็กเท่านั้น
  • sort(char[],…) และ sort(short[],…)เพิ่มกรณีพิเศษอื่นเพื่อใช้การเรียงลำดับสำหรับอาร์เรย์ที่มีความยาวเกินเกณฑ์ที่กำหนด
  • ในทำนองเดียวกันsort(byte[],…)จะใช้การเรียงลำดับการนับแต่มีเกณฑ์ที่เล็กกว่ามากซึ่งจะสร้างความแตกต่างมากที่สุดกับเอกสารเนื่องจากsort(byte[],…)ไม่เคยใช้ Quicksort ใช้เฉพาะการเรียงลำดับการแทรกสำหรับอาร์เรย์ขนาดเล็กและการนับการเรียงลำดับเป็นอย่างอื่น

1
อืมน่าสนใจที่ Collections.sort Javadoc ระบุ: "การจัดเรียงนี้รับประกันว่าจะเสถียร" แต่เนื่องจากมันมอบหมายให้ List.sort ซึ่งสามารถแทนที่ได้ด้วยการใช้งานรายการการเรียงลำดับที่เสถียรจึงไม่สามารถรวบรวมโดย Collections.sort สำหรับรายการทั้งหมดได้ การใช้งาน หรือฉันพลาดอะไรไป? และ List.sort ไม่ต้องการให้การเรียงลำดับ alogirthm มีเสถียรภาพ
Puce

11
@Puce: นั่นหมายความว่าความรับผิดชอบในการรับประกันนั้นตอนนี้อยู่ในมือของผู้ที่ใช้List.sortวิธีการลบล้าง Collections.sortไม่สามารถรับประกันการทำงานที่ถูกต้องสำหรับListการใช้งานทุกครั้งเนื่องจากไม่สามารถรับประกันได้เช่นListไม่ได้เปลี่ยนแปลงเนื้อหาอย่างปลอมแปลง ทุกอย่างลงเอยด้วยการที่การรับประกันCollections.sortใช้เฉพาะกับการListใช้งานที่ถูกต้องเท่านั้น(และแก้ไขComparatorหรือequalsการนำไปใช้งาน)
Holger

1
@Puce: แต่คุณมีสิทธิ Javadoc ไม่ได้เป็นอย่างเท่าเทียมกันอย่างชัดเจนเกี่ยวกับข้อ จำกัด ในวิธีการทั้งสอง แต่อย่างน้อยที่สุดรัฐเอกสารล่าสุดที่จะมอบหมายให้Collections.sort List.sort
โฮลเกอร์

@Puce: มีตัวอย่างมากมายโดยที่คุณสมบัติที่สำคัญไม่ได้เป็นส่วนหนึ่งของประเภท แต่กล่าวถึงเฉพาะในเอกสารประกอบเท่านั้น (และไม่ได้ตรวจสอบโดยคอมไพเลอร์) ระบบประเภทของ Java นั้นอ่อนแอเกินไปที่จะแสดงคุณสมบัติที่น่าสนใจใด ๆ (มันไม่แตกต่างจากภาษาที่พิมพ์แบบไดนามิกมากนักในเรื่องนี้มีการกำหนดคุณสมบัติไว้ในเอกสารด้วยและขึ้นอยู่กับโปรแกรมเมอร์ที่จะตรวจสอบให้แน่ใจว่าไม่ได้ละเมิด) มันจะยิ่งไปกว่านั้นจริง: คุณสังเกตเห็นหรือไม่ ที่Collections.sortไม่ได้กล่าวถึงในลายเซ็นประเภทว่าเอาต์พุตถูกจัดเรียง?
Jörg W Mittag

1
ในภาษาที่มีระบบประเภทที่แสดงออกมากขึ้นประเภทการส่งคืนของCollections.sortจะเป็นบางอย่างเช่น "คอลเล็กชันประเภทและความยาวเดียวกันกับอินพุตที่มีคุณสมบัติที่ 1) ทุกองค์ประกอบที่มีอยู่ในอินพุตจะมีอยู่ในเอาต์พุตด้วย 2 ) สำหรับทุกคู่ขององค์ประกอบจากเอาต์พุตหนึ่งด้านซ้ายจะไม่มากกว่าองค์ประกอบด้านขวา 3) สำหรับทุกคู่ขององค์ประกอบที่เท่ากันจากเอาต์พุตดัชนีด้านซ้ายในอินพุตจะเล็กกว่าด้านขวา "หรืออะไรทำนองนั้น ที่.
Jörg W Mittag

20

ฉันไม่รู้เกี่ยวกับเอกสาร แต่การนำไปใช้java.util.Collections#sortใน Java 8 (HotSpot) จะเป็นดังนี้:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

และList#sortมีการใช้งานนี้:

@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

ในท้ายที่สุดก็Collections#sortใช้Arrays#sort (ขององค์ประกอบวัตถุ) เบื้องหลัง การใช้งานนี้ใช้การเรียงลำดับการผสานหรือการเรียงลำดับเวลา


16

ตาม Javadoc อาร์เรย์ดั้งเดิมเท่านั้นที่จะเรียงลำดับโดยใช้ Quicksort อาร์เรย์อ็อบเจ็กต์จะถูกจัดเรียงด้วย Mergesort เช่นกัน

ดังนั้น Collections.sort ดูเหมือนจะใช้อัลกอริทึมการเรียงลำดับเดียวกันกับ Arrays.sort for Objects

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


2

ตามที่ระบุไว้ในหลาย ๆ คำตอบ

Arrays.sort ใช้ Quicksort สำหรับการเรียงลำดับคอลเลกชันดั้งเดิมเนื่องจากไม่จำเป็นต้องมีความเสถียร (คุณจะไม่ทราบหรือไม่สนใจว่ามีการสลับ int ที่เหมือนกันสองรายการในการจัดเรียงหรือไม่)

MergeSort หรือมากกว่านั้นโดยเฉพาะ Timsort ถูกใช้โดย Arrays.sort สำหรับการเรียงลำดับคอลเล็กชันของวัตถุ ความเสถียรเป็นสิ่งจำเป็น Quicksort ไม่ได้ให้ความเสถียร Timsort ทำ

Collections.sort มอบหมายให้ Arrays.sort ซึ่งเป็นสาเหตุที่คุณเห็น javadoc อ้างถึง MergeSort


1

การเรียงลำดับด่วนมีข้อเสียที่สำคัญสองประการในการผสานการเรียงลำดับ:

  • มันไม่คงที่ในขณะที่มันไม่ใช่แบบดั้งเดิม
  • ไม่รับประกันประสิทธิภาพ n log n

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

ความเสถียรเป็นเรื่องใหญ่เมื่อจัดเรียงวัตถุโดยพลการ เป็นข้อดีที่ Merge Sort รับประกันประสิทธิภาพ n log n (เวลา) ไม่ว่าจะป้อนข้อมูลใดก็ตาม นั่นเป็นเหตุผลที่เลือกการเรียงลำดับการผสานเพื่อจัดเรียงแบบคงที่ (Merge Sort) เพื่อจัดเรียงการอ้างอิงวัตถุ


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