ทั้ง Quicksort และ heapsort ทำการเรียงลำดับในสถานที่ แบบไหนดีกว่ากัน? แอปพลิเคชันและกรณีใดบ้างที่เป็นที่ต้องการ
ทั้ง Quicksort และ heapsort ทำการเรียงลำดับในสถานที่ แบบไหนดีกว่ากัน? แอปพลิเคชันและกรณีใดบ้างที่เป็นที่ต้องการ
คำตอบ:
บทความนี้มีการวิเคราะห์บางอย่าง
นอกจากนี้จาก Wikipedia:
คู่แข่งที่ตรงที่สุดของ quicksort คือ heapsort โดยทั่วไป Heapsort จะค่อนข้างช้ากว่า Quicksort แต่เวลาทำงานในกรณีที่เลวร้ายที่สุดคือΘ (nlogn) เสมอ Quicksort มักจะเร็วกว่าแม้ว่าจะยังคงมีโอกาสที่จะเกิดประสิทธิภาพในกรณีที่เลวร้ายที่สุดยกเว้นในตัวแปร introsort ซึ่งจะเปลี่ยนเป็น heapsort เมื่อตรวจพบกรณีที่ไม่ดี หากทราบล่วงหน้าว่าฮีปพอร์ตจำเป็นต้องใช้การใช้โดยตรงจะเร็วกว่าการรอให้ introsort เปลี่ยนไปใช้
Heapsort ได้รับการรับรอง O (N log N) สิ่งที่ดีกว่ากรณีที่เลวร้ายที่สุดใน Quicksort Heapsort ไม่ต้องการหน่วยความจำเพิ่มเติมสำหรับอาร์เรย์อื่นในการวางข้อมูลที่สั่งซื้อตามที่ Mergesort ต้องการ เหตุใดแอปพลิเคชันเชิงพาณิชย์จึงติดกับ Quicksort? Quicksort มีอะไรพิเศษกว่าการใช้งานอื่น ๆ ?
ฉันได้ทดสอบอัลกอริทึมด้วยตัวเองและฉันเห็นว่า Quicksort มีบางอย่างที่พิเศษแน่นอน ทำงานได้เร็วเร็วกว่าอัลกอริทึม Heap และ Merge มาก
ความลับของ Quicksort คือแทบจะไม่ทำการแลกเปลี่ยนองค์ประกอบที่ไม่จำเป็น Swap ใช้เวลานาน
ด้วย Heapsort แม้ว่าข้อมูลทั้งหมดของคุณจะถูกเรียงลำดับไปแล้วคุณจะสลับองค์ประกอบ 100% เพื่อสั่งอาร์เรย์
ด้วย Mergesort มันยิ่งแย่ลงไปอีก คุณจะเขียนองค์ประกอบ 100% ในอาร์เรย์อื่นและเขียนกลับในองค์ประกอบเดิมแม้ว่าข้อมูลจะเรียงลำดับแล้วก็ตาม
ด้วย Quicksort คุณจะไม่เปลี่ยนสิ่งที่สั่งซื้อไปแล้ว หากข้อมูลของคุณถูกเรียงลำดับอย่างสมบูรณ์คุณแทบจะไม่ต้องเปลี่ยนอะไรเลย! แม้ว่าจะมีเรื่องยุ่งยากมากมายเกี่ยวกับกรณีที่เลวร้ายที่สุด แต่การปรับปรุงตัวเลือก pivot เพียงเล็กน้อยนอกเหนือจากการรับองค์ประกอบแรกหรือองค์ประกอบสุดท้ายของอาร์เรย์ก็สามารถหลีกเลี่ยงได้ หากคุณได้รับเดือยจากองค์ประกอบที่อยู่ตรงกลางระหว่างองค์ประกอบแรกองค์ประกอบสุดท้ายและองค์ประกอบกลางคุณควรหลีกเลี่ยงกรณีที่เลวร้ายที่สุด
สิ่งที่เหนือกว่าใน Quicksort ไม่ใช่กรณีที่เลวร้ายที่สุด แต่เป็นกรณีที่ดีที่สุด! ในกรณีที่ดีที่สุดคุณทำการเปรียบเทียบในจำนวนเท่ากันก็โอเค แต่คุณแทบไม่ได้สลับอะไรเลย โดยเฉลี่ยแล้วคุณจะสลับบางส่วนขององค์ประกอบ แต่ไม่ใช่องค์ประกอบทั้งหมดเช่นเดียวกับใน Heapsort และ Mergesort นั่นคือสิ่งที่ทำให้ Quicksort มีเวลาที่ดีที่สุด แลกเปลี่ยนน้อยลงความเร็วมากขึ้น
การใช้งานด้านล่างใน C # บนคอมพิวเตอร์ของฉันทำงานในโหมดรีลีสเต้น Array เรียงลำดับ 3 วินาทีด้วยเดือยกลางและ 2 วินาทีพร้อมเดือยที่ปรับปรุงแล้ว (ใช่มีค่าใช้จ่ายในการรับเดือยที่ดี)
static void Main(string[] args)
{
int[] arrToSort = new int[100000000];
var r = new Random();
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
Console.WriteLine("Press q to quick sort, s to Array.Sort");
while (true)
{
var k = Console.ReadKey(true);
if (k.KeyChar == 'q')
{
// quick sort
Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
QuickSort(arrToSort, 0, arrToSort.Length - 1);
Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
else if (k.KeyChar == 's')
{
Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
Array.Sort(arrToSort);
Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
}
}
static public void QuickSort(int[] arr, int left, int right)
{
int begin = left
, end = right
, pivot
// get middle element pivot
//= arr[(left + right) / 2]
;
//improved pivot
int middle = (left + right) / 2;
int
LM = arr[left].CompareTo(arr[middle])
, MR = arr[middle].CompareTo(arr[right])
, LR = arr[left].CompareTo(arr[right])
;
if (-1 * LM == LR)
pivot = arr[left];
else
if (MR == -1 * LR)
pivot = arr[right];
else
pivot = arr[middle];
do
{
while (arr[left] < pivot) left++;
while (arr[right] > pivot) right--;
if(left <= right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
left++;
right--;
}
} while (left <= right);
if (left < end) QuickSort(arr, left, end);
if (begin < right) QuickSort(arr, begin, right);
}
สำหรับสถานการณ์ส่วนใหญ่การเร็วกับเร็วกว่าเล็กน้อยนั้นไม่เกี่ยวข้อง ... คุณแค่ไม่ต้องการให้มันช้าลงในบางครั้ง แม้ว่าคุณจะสามารถปรับแต่ง QuickSort เพื่อหลีกเลี่ยงสถานการณ์ที่เชื่องช้า แต่คุณก็สูญเสียความสง่างามของ QuickSort พื้นฐานไป ดังนั้นสำหรับสิ่งส่วนใหญ่ฉันชอบ HeapSort จริงๆ ... คุณสามารถใช้งานได้ด้วยความสง่างามที่เรียบง่ายเต็มรูปแบบและไม่เคยช้าเลย
สำหรับสถานการณ์ที่คุณต้องการความเร็วสูงสุดในกรณีส่วนใหญ่ QuickSort อาจเป็นที่ต้องการมากกว่า HeapSort แต่อาจไม่ใช่คำตอบที่ถูกต้อง สำหรับสถานการณ์ที่สำคัญอย่างรวดเร็วควรตรวจสอบรายละเอียดของสถานการณ์อย่างใกล้ชิด ตัวอย่างเช่นในโค้ด speed-Critical บางส่วนของฉันเป็นเรื่องปกติมากที่ข้อมูลจะถูกจัดเรียงหรือจัดเรียงใกล้เคียงอยู่แล้ว (เป็นการทำดัชนีฟิลด์ที่เกี่ยวข้องหลายฟิลด์ซึ่งมักจะเลื่อนขึ้นและลงพร้อมกันหรือเลื่อนขึ้นและลงตรงข้ามกัน ดังนั้นเมื่อคุณเรียงลำดับทีละรายการรายการอื่น ๆ จะถูกจัดเรียงหรือเรียงลำดับย้อนกลับหรือปิด ... ซึ่งทั้งสองอย่างสามารถฆ่า QuickSort ได้) สำหรับกรณีนั้นฉันไม่ได้ใช้งาน ... แต่ฉันใช้ SmoothSort ของ Dijkstra ... ตัวแปร HeapSort ที่เป็น O (N) เมื่อเรียงลำดับแล้วหรือใกล้เรียงแล้ว ... มันไม่สวยหรูไม่เข้าใจง่ายเกินไป แต่เร็ว ... อ่านhttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDFหากคุณต้องการอะไรที่ท้าทายกว่านี้ในการเขียนโค้ด
Quicksort-Heapsort ลูกผสมในสถานที่ก็น่าสนใจเช่นกันเนื่องจากส่วนใหญ่ต้องการการเปรียบเทียบ n * log n ในกรณีที่เลวร้ายที่สุด (เหมาะสมที่สุดเมื่อเทียบกับระยะแรกของ asymptotics ดังนั้นพวกเขาจึงหลีกเลี่ยงสถานการณ์ที่เลวร้ายที่สุด ของ Quicksort), O (log n) พื้นที่พิเศษและพวกมันรักษาพฤติกรรมที่ดีของ Quicksort ไว้อย่างน้อย "ครึ่งหนึ่ง" ตามชุดข้อมูลที่สั่งไว้แล้ว Dikert และ Weiss นำเสนออัลกอริทึมที่น่าสนใจอย่างยิ่งในhttp://arxiv.org/pdf/1209.4214v1.pdf :
คอมพ์ ระหว่างquick sort
และmerge sort
เนื่องจากทั้งสองเป็นประเภทของการเรียงลำดับในสถานที่จึงมีความแตกต่างระหว่างเวลาทำงานของเคส wrost ของเวลาทำงานของเคส wrost สำหรับการเรียงลำดับอย่างรวดเร็วคือO(n^2)
และสำหรับการเรียงฮีปมันจะยังคงอยู่O(n*log(n))
และสำหรับจำนวนเฉลี่ยของการจัดเรียงข้อมูลอย่างรวดเร็วจะมีประโยชน์มากกว่า เนื่องจากเป็นอัลกอริทึมแบบสุ่มดังนั้นความน่าจะเป็นที่จะได้รับ ans ที่ถูกต้อง ในเวลาที่น้อยลงจะขึ้นอยู่กับตำแหน่งขององค์ประกอบ Pivot ที่คุณเลือก
ดังนั้นก
การโทรที่ดี:ขนาดของ L และ G แต่ละอันน้อยกว่า 3 วินาที / 4
การโทรไม่ถูกต้อง:หนึ่งใน L และ G มีขนาดมากกว่า 3 วินาที / 4
สำหรับจำนวนเล็กน้อยเราสามารถไปสำหรับการเรียงลำดับการแทรกและสำหรับข้อมูลจำนวนมากไปสำหรับการเรียงลำดับฮีป
Heapsort มีประโยชน์ในการมีO (n * log (n)) ที่แย่ที่สุดดังนั้นในกรณีที่ Quicksort มีแนวโน้มที่จะทำงานได้ไม่ดี (โดยทั่วไปแล้วชุดข้อมูลที่เรียงลำดับโดยทั่วไป) จึงเป็นที่ต้องการมาก
ถ้าคุณไปที่ระดับสถาปัตยกรรม ... เราใช้โครงสร้างข้อมูลคิวในหน่วยความจำแคชดังนั้นสิ่งที่มีอยู่ในคิวก็จะถูกจัดเรียงเช่นเดียวกับการจัดเรียงอย่างรวดเร็วเราไม่มีปัญหาในการแบ่งอาร์เรย์ออกเป็นความยาวใด ๆ ... แต่ในกอง เรียงลำดับ (โดยใช้อาร์เรย์) จึงอาจเกิดขึ้นได้ว่าพาเรนต์อาจไม่อยู่ในอาร์เรย์ย่อยที่มีอยู่ในแคชจากนั้นจึงต้องนำมาไว้ในหน่วยความจำแคช ... ซึ่งใช้เวลานาน Quicksort ดีที่สุด !! 😀
ฮีปพอร์ตสร้างฮีปแล้วแยกไอเท็มสูงสุดซ้ำ ๆ กรณีที่แย่ที่สุดคือ O (n log n)
แต่ถ้าคุณเห็นกรณีที่แย่ที่สุดของการเรียงลำดับด่วนซึ่งก็คือ O (n2) คุณจะรู้ว่าการจัดเรียงแบบรวดเร็วนั้นเป็นทางเลือกที่ไม่ดีนักสำหรับข้อมูลขนาดใหญ่
ดังนั้นการเรียงลำดับจึงเป็นสิ่งที่น่าสนใจ ฉันเชื่อว่าเหตุผลที่อัลกอริทึมการเรียงลำดับจำนวนมากใช้งานได้ในปัจจุบันเป็นเพราะทั้งหมดนั้น 'ดีที่สุด' ในตำแหน่งที่ดีที่สุด ตัวอย่างเช่นการจัดเรียงฟองสามารถทำการจัดเรียงอย่างรวดเร็วหากข้อมูลถูกจัดเรียง หรือถ้าเรารู้บางอย่างเกี่ยวกับรายการที่จะจัดเรียงแล้วเราอาจทำได้ดีกว่า
สิ่งนี้อาจไม่ตอบคำถามของคุณโดยตรงคิดว่าฉันจะเพิ่มสองเซ็นต์ของฉัน
Heap Sort เป็นเดิมพันที่ปลอดภัยเมื่อจัดการกับอินพุตที่มีขนาดใหญ่มาก การวิเคราะห์ Asymptotic แสดงให้เห็นลำดับการเติบโตของ Heapsort ในกรณีที่เลวร้ายที่สุดคือBig-O(n logn)
Quicksort Big-O(n^2)
เป็นกรณีที่แย่ที่สุด อย่างไรก็ตามHeapsortค่อนข้างช้าในทางปฏิบัติในเครื่องส่วนใหญ่มากกว่าการจัดเรียงอย่างรวดเร็วที่ใช้งานได้ดี Heapsort ไม่ใช่อัลกอริทึมการเรียงลำดับที่เสถียร
เหตุผลที่ฮีปพอร์ตทำงานช้ากว่า Quicksort นั้นเนื่องมาจากตำแหน่งที่ดีกว่าในการอ้างอิง (" https://en.wikipedia.org/wiki/Locality_of_reference ") ใน Quicksort ซึ่งองค์ประกอบข้อมูลอยู่ในสถานที่จัดเก็บที่ค่อนข้างใกล้ ระบบที่มีแหล่งอ้างอิงที่ชัดเจนเป็นตัวเลือกที่ดีสำหรับการเพิ่มประสิทธิภาพ อย่างไรก็ตามการจัดเรียงแบบฮีปเกี่ยวข้องกับการก้าวกระโดดที่มากขึ้น ทำให้ Quicksort ดีขึ้นสำหรับอินพุตขนาดเล็ก
สำหรับฉันแล้วมีความแตกต่างพื้นฐานอย่างมากระหว่าง heapsort และ quicksort: หลังใช้การเรียกซ้ำ ในอัลกอริธึมแบบเรียกซ้ำฮีปจะเพิ่มขึ้นตามจำนวนการเรียกซ้ำ นี่ไม่สำคัญว่าnจะเล็ก แต่ตอนนี้ฉันกำลังเรียงเมทริกซ์สองตัวด้วยn = 10 ^ 9 !! โปรแกรมใช้ ram เกือบ 10 GB และหน่วยความจำเพิ่มเติมใด ๆ จะทำให้คอมพิวเตอร์ของฉันเริ่มการแลกเปลี่ยนกับหน่วยความจำดิสก์เสมือน ดิสก์ของฉันเป็นดิสก์ RAM แต่ยังคงสลับไปมาทำให้ความเร็วแตกต่างกันมาก ดังนั้นใน statpack ที่เข้ารหัส C ++ ซึ่งรวมเมทริกซ์มิติข้อมูลที่ปรับได้โดยไม่ทราบขนาดล่วงหน้าสำหรับโปรแกรมเมอร์และการเรียงลำดับทางสถิติแบบไม่ใช้พารามิเตอร์ฉันชอบฮีปพอร์ตเพื่อหลีกเลี่ยงความล่าช้าในการใช้กับเมทริกซ์ข้อมูลขนาดใหญ่มาก
พูดง่ายๆ >> HeapSort ได้รับประกัน ~ กรณีที่เลวร้ายที่สุด ~ เวลาทำงานของ "O (n log n)" ซึ่งตรงข้ามกับเวลาทำงาน ~ เฉลี่ย ~ ของ QuickSort ของ "O (n log n)" โดยทั่วไปแล้ว QuickSort จะใช้ในทางปฏิบัติเนื่องจากโดยทั่วไปแล้วจะเร็วกว่า แต่ HeapSort ใช้สำหรับการจัดเรียงภายนอกเมื่อคุณต้องการจัดเรียงไฟล์ขนาดใหญ่ที่ไม่พอดีกับหน่วยความจำของคอมพิวเตอร์ของคุณ
หากต้องการตอบคำถามเดิมและตอบความคิดเห็นอื่น ๆ ที่นี่:
ฉันเพิ่งเปรียบเทียบการใช้งานการเลือกด่วนผสานและการเรียงลำดับฮีปเพื่อดูว่าพวกเขาจะซ้อนกันอย่างไร คำตอบคือพวกเขาทั้งหมดมีข้อเสีย
TL; DR: Quick เป็นการจัดเรียงตามวัตถุประสงค์ทั่วไปที่ดีที่สุด (เร็วพอสมควรเสถียรและส่วนใหญ่อยู่ในตำแหน่ง) โดยส่วนตัวแล้วฉันชอบการเรียงลำดับฮีปแม้ว่าฉันจะต้องการการจัดเรียงที่มั่นคง
การเลือก - N ^ 2 - มันดีจริงๆสำหรับองค์ประกอบน้อยกว่า 20 ชิ้นหรือมากกว่านั้นก็ทำได้ดีกว่า เว้นแต่ข้อมูลของคุณจะได้รับการจัดเรียงเรียบร้อยแล้วหรือเกือบจะมาก N ^ 2 ช้ามากเร็วมาก
จากประสบการณ์ของฉันอย่างรวดเร็วไม่ใช่ว่าจะรวดเร็วตลอดเวลา โบนัสสำหรับการใช้การจัดเรียงอย่างรวดเร็วเป็นการจัดเรียงทั่วไปแม้ว่าจะเร็วพอสมควรและมีความเสถียร นอกจากนี้ยังเป็นอัลกอริทึมที่ใช้งานได้ แต่เนื่องจากโดยทั่วไปมีการใช้งานซ้ำจึงต้องใช้พื้นที่สแต็กเพิ่มเติม นอกจากนี้ยังอยู่ระหว่าง O (n log n) และ O (n ^ 2) การกำหนดเวลาในบางประเภทดูเหมือนจะยืนยันสิ่งนี้โดยเฉพาะอย่างยิ่งเมื่อค่าอยู่ในช่วงที่ จำกัด วิธีนี้เร็วกว่าการจัดเรียงการเลือก 10,000,000 รายการ แต่ช้ากว่าการผสานหรือฮีป
รับประกันการเรียงลำดับการผสาน O (n log n) เนื่องจากการเรียงลำดับไม่ขึ้นอยู่กับข้อมูล มันทำในสิ่งที่ทำโดยไม่คำนึงถึงคุณค่าที่คุณให้ไว้ นอกจากนี้ยังมีความเสถียร แต่ประเภทที่มีขนาดใหญ่มากสามารถทำลายสแต็กของคุณได้หากคุณไม่ระมัดระวังในการใช้ มีการใช้งานการเรียงลำดับการผสานในตำแหน่งที่ซับซ้อน แต่โดยทั่วไปคุณต้องมีอาร์เรย์อื่นในแต่ละระดับเพื่อรวมค่าของคุณเข้าด้วยกัน หากอาร์เรย์เหล่านั้นอยู่บนสแต็กคุณอาจพบปัญหาได้
การเรียงลำดับฮีปคือสูงสุด O (n log n) แต่ในหลาย ๆ กรณีจะเร็วกว่าขึ้นอยู่กับว่าคุณต้องย้ายค่าของคุณไปที่ฮีปลึกของ log n มากแค่ไหน ฮีปสามารถติดตั้งในตำแหน่งเดิมในอาร์เรย์เดิมได้อย่างง่ายดายดังนั้นจึงไม่จำเป็นต้องมีหน่วยความจำเพิ่มเติมและมีการทำซ้ำดังนั้นจึงไม่ต้องกังวลเกี่ยวกับสแตกล้นในขณะที่เรียกซ้ำ ใหญ่ข้อเสียในการจัดเรียงกองก็คือว่ามันไม่ได้เป็นมีเสถียรภาพเรียงลำดับซึ่งหมายความว่ามันออกไปทางขวาถ้าคุณต้องการที่