วิธี Hoare - การเรียงลำดับด่วน Quicksort การใช้งาน Quicksort ค
มีการประเมินกันว่าคอมพิวเตอร์ส่วนกลางจะใช้เวลามากถึงหนึ่งในสี่ในการจัดเรียงข้อมูล เนื่องจากการค้นหาค่าในอาร์เรย์ที่เรียงลำดับไว้ล่วงหน้าจะง่ายกว่ามาก มิฉะนั้น การค้นหาก็เหมือนกับการมองหาเข็มในกองหญ้า
มีโปรแกรมเมอร์จำนวนหนึ่งที่ใช้เวลาทำงานทั้งหมดเพื่อศึกษาและใช้อัลกอริธึมการเรียงลำดับ เนื่องจากซอฟต์แวร์ธุรกิจส่วนใหญ่เกี่ยวข้องกับการจัดการฐานข้อมูล ผู้คนค้นหาข้อมูลในฐานข้อมูลตลอดเวลา ซึ่งหมายความว่าอัลกอริทึมการค้นหาเป็นที่ต้องการสูง
แต่มี "แต่" อย่างหนึ่ง อัลกอริธึมการค้นหาทำงานเร็วขึ้นมากกับฐานข้อมูลที่จัดเรียงไว้แล้ว ในกรณีนี้ ต้องใช้การค้นหาเชิงเส้นเท่านั้น
ในขณะที่คอมพิวเตอร์ไม่มีผู้ใช้ในบางช่วงเวลา อัลกอริธึมการเรียงลำดับยังคงทำงานบนฐานข้อมูลต่อไป ผู้ค้นหากลับมาอีกครั้ง และฐานข้อมูลจะถูกจัดเรียงตามวัตถุประสงค์ในการค้นหาอย่างใดอย่างหนึ่งแล้ว
บทความนี้แสดงตัวอย่างการใช้งานอัลกอริธึมการเรียงลำดับมาตรฐาน
เรียงลำดับการเลือก
หากต้องการเรียงลำดับอาร์เรย์จากน้อยไปมาก คุณต้องค้นหาองค์ประกอบที่มีค่ามากที่สุดในแต่ละการวนซ้ำ คุณต้องสลับองค์ประกอบสุดท้ายด้วย องค์ประกอบถัดไปที่มีค่าสูงสุดจะถูกวางไว้ในอันดับที่สองจากตำแหน่งสุดท้าย สิ่งนี้จะเกิดขึ้นจนกว่าองค์ประกอบในตำแหน่งแรกในอาร์เรย์จะอยู่ในลำดับที่ถูกต้อง
รหัสซี++
เป็นโมฆะ SortAlgo :: selectionSort (int data, int lenD) ( int j = 0; int tmp = 0; สำหรับ (int i = 0; i
ข้อมูล [i] = ข้อมูล [j];
ข้อมูล[j] = tmp;
รหัสซี++
- การเรียงลำดับการแทรกจะแยกอาร์เรย์ออกเป็นสองขอบเขต: เรียงลำดับและไม่เรียงลำดับ ในตอนแรก อาเรย์ทั้งหมดจะเป็นขอบเขตที่ไม่เรียงลำดับ ในการผ่านครั้งแรก องค์ประกอบแรกจากขอบเขตที่ไม่ได้เรียงลำดับจะถูกลบออก และวางในตำแหน่งที่ถูกต้องในภูมิภาคที่ได้รับคำสั่ง ในแต่ละรอบ ขนาดของขอบเขตที่ได้รับคำสั่งจะเพิ่มขึ้น 1 และขนาดของขอบเขตที่ไม่เป็นระเบียบจะลดลง 1 วงหลักเริ่มจาก 1 ถึง N-1 ในการวนซ้ำที่ j องค์ประกอบ [i] จะถูกแทรกเข้าไปในตำแหน่งที่ถูกต้องในภูมิภาคที่ได้รับคำสั่ง ทำได้โดยการเลื่อนองค์ประกอบทั้งหมดของขอบเขตการเรียงลำดับที่มากกว่า [i] ไปทางขวาหนึ่งตำแหน่ง [i] ถูกแทรกลงในช่องว่างระหว่างองค์ประกอบที่น้อยกว่า [i] และองค์ประกอบที่มากกว่า [i] รหัสซี++
เป็นโมฆะ SortAlgo :: insertionSort (int data, int lenD) ( int key = 0; int i = 0; for (int j = 1; j รหัสซี++
ถือเป็นโมฆะ SortAlgo::mergeSort (int data, int lenD) ( if (lenD>1)( int middle = lenD/2; int rem = lenD-middle; int * L = int ใหม่; int * R = int ใหม่; สำหรับ ( int i=0;i Quicksort ใช้อัลกอริธึมการแบ่งและพิชิต เริ่มต้นด้วยการแบ่งอาร์เรย์ดั้งเดิมออกเป็นสองส่วน ส่วนเหล่านี้อยู่ทางซ้ายและขวาขององค์ประกอบที่ทำเครื่องหมายไว้ เรียกว่าส่วนรองรับ เมื่อสิ้นสุดกระบวนการ ส่วนหนึ่งจะมีองค์ประกอบที่เล็กกว่าข้อมูลอ้างอิง และอีกส่วนหนึ่งจะมีองค์ประกอบที่มีขนาดใหญ่กว่าข้อมูลอ้างอิง รหัสซี++
ถือเป็นโมฆะ SortAlgo::quickSort(int * data, int const len) ( int const lenD = len; int pivot = 0; int ind = lenD/2; int i,j = 0,k = 0; if (lenD>1) ( int * L = int ใหม่ ; int * R = int ใหม่ ; pivot = data; สำหรับ (i=0;i เป้าหมาย: ศึกษาอัลกอริธึมการเรียงลำดับอย่างรวดเร็วและการแก้ไข ในบทนี้ เราจะศึกษาอัลกอริทึม Quicksort ซึ่งอาจมีการใช้บ่อยกว่าวิธีอื่นๆ พื้นฐานของอัลกอริธึมได้รับการพัฒนาในปี 1960 (C.A.R.Hoare) และได้รับการศึกษาอย่างรอบคอบโดยผู้คนจำนวนมากตั้งแต่นั้นมา Quicksort ได้รับความนิยมเป็นพิเศษเนื่องจากใช้งานง่าย เป็นอัลกอริธึมสำหรับวัตถุประสงค์ทั่วไปที่ค่อนข้างดีซึ่งทำงานได้ดีในหลาย ๆ สถานการณ์ในขณะที่ใช้ทรัพยากรน้อยกว่าอัลกอริธึมอื่น ๆ ข้อดีหลักของอัลกอริธึมนี้คือ เป็นแบบ point-wise (ใช้สแต็กเพิ่มเติมเพียงเล็กน้อยเท่านั้น) ต้องการโดยเฉลี่ยเพียงการดำเนินการ N log N เพื่อเรียงลำดับองค์ประกอบ N และมีวงในที่สั้นมาก ข้อเสียของอัลกอริธึมคือเป็นแบบเรียกซ้ำ (การใช้งานจะยากมากเมื่อไม่มีการเรียกซ้ำ) ในกรณีที่แย่ที่สุดต้องใช้การดำเนินการ N2 นอกจากนี้ ยัง "เปราะบาง" มาก: มีข้อผิดพลาดเล็กน้อยในการใช้งาน ซึ่งสามารถ มองข้ามได้ง่าย อาจทำให้อัลกอริธึมทำงานได้แย่มากในบางไฟล์ ประสิทธิภาพของ Quicksort ได้รับการศึกษามาเป็นอย่างดี อัลกอริธึมได้รับการวิเคราะห์ทางคณิตศาสตร์ ดังนั้นจึงมีสูตรทางคณิตศาสตร์ที่แม่นยำเกี่ยวกับปัญหาด้านประสิทธิภาพ ผลลัพธ์ของการวิเคราะห์ได้รับการตรวจสอบยืนยันซ้ำแล้วซ้ำอีก และอัลกอริธึมได้รับการปรับปรุงให้อยู่ในสถานะที่เหมาะสมที่สุดสำหรับงานเรียงลำดับที่หลากหลาย ทั้งหมดนี้ทำให้อัลกอริธึมคุ้มค่ากับการศึกษาโดยละเอียดมากขึ้นเกี่ยวกับวิธีการใช้งานที่มีประสิทธิภาพสูงสุด การใช้งานที่คล้ายกันใช้ได้กับอัลกอริธึมอื่นๆ เช่นกัน แต่ใน Quicksort เราสามารถใช้งานได้อย่างมั่นใจเพราะประสิทธิภาพของมันได้รับการศึกษามาอย่างดี การปรับปรุงอัลกอริธึมการเรียงลำดับอย่างรวดเร็วถือเป็นสิ่งล่อใจครั้งใหญ่: อัลกอริธึมการเรียงลำดับที่เร็วขึ้นนั้นเป็น "กับดักหนู" สำหรับโปรแกรมเมอร์ เกือบจะตั้งแต่ตอนที่ Oiaa ตีพิมพ์อัลกอริทึมของเขาเป็นครั้งแรก อัลกอริธึมเวอร์ชัน "ปรับปรุง" ก็เริ่มปรากฏในวรรณกรรม มีการทดลองและวิเคราะห์แนวคิดต่างๆ มากมาย แต่ก็ยังง่ายมากที่จะถูกหลอก เนื่องจากอัลกอริธึมมีความสมดุลที่ดีจนการปรับปรุงในส่วนใดส่วนหนึ่งอาจส่งผลให้ส่วนอื่นเสื่อมลงมากขึ้น เราจะศึกษารายละเอียดการปรับเปลี่ยนอัลกอริทึมนี้สามประการซึ่งให้การปรับปรุงที่สำคัญ Quicksort เวอร์ชันที่ได้รับการปรับแต่งอย่างดีมีแนวโน้มที่จะทำงานได้เร็วกว่าอัลกอริธึมอื่นๆ มาก อย่างไรก็ตาม ควรระลึกอีกครั้งว่าอัลกอริทึมนั้นเปราะบางมากและการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นอาจนำไปสู่ผลกระทบที่ไม่พึงประสงค์และไม่คาดคิดสำหรับข้อมูลอินพุตบางส่วน สาระสำคัญของอัลกอริทึม: จำนวนการดำเนินการในการเปลี่ยนตำแหน่งขององค์ประกอบภายในอาร์เรย์จะลดลงอย่างมากหากองค์ประกอบที่อยู่ห่างจากกันถูกสลับกัน เมื่อต้องการทำสิ่งนี้ ให้เลือกหนึ่งองค์ประกอบ x เพื่อเปรียบเทียบ พบองค์ประกอบแรกทางด้านซ้ายซึ่งไม่น้อยกว่า x และพบองค์ประกอบแรกทางด้านขวาซึ่งไม่เกิน x องค์ประกอบที่พบจะถูกสลับ หลังจากผ่านครั้งแรก องค์ประกอบทั้งหมดที่น้อยกว่า x จะอยู่ทางด้านซ้ายของ x และองค์ประกอบทั้งหมดที่มากกว่า x จะอยู่ทางด้านขวาของ x ทำเช่นเดียวกันกับสองครึ่งหนึ่งของอาร์เรย์ แบ่งครึ่งเหล่านี้ต่อไปจนกว่าจะมี 1 องค์ประกอบอยู่ในนั้น โปรแกรม Quitsort;<=v) or (j=i+1);
repeat
inc(i)
until (a[i]>ใช้ crt; 60,79, 82, 58, 39, 9, 54, 92, 44, 32
60,79, 82, 58, 39, 9, 54, 92, 44, 32
9,79, 82, 58, 39, 60, 54, 92, 44, 32
9,79, 82, 58, 39, 60, 54, 92, 44, 32
9, 32, 82, 58, 39, 60, 54, 92, 44, 79
9, 32, 44, 58, 39, 60, 54, 92, 82, 79
9, 32, 44, 58, 39, 54, 60, 92, 82, 79
9, 32, 44, 58, 39, 92, 60, 54, 82, 79
9, 32, 44, 58, 39, 54, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 60, 39, 54, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 44, 58, 54, 39, 60, 79, 82, 92
9, 32, 39, 58, 54, 44, 60, 79, 82, 92
9, 32, 39, 58, 54, 44, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
9, 32, 39, 44, 58, 54, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
9, 32, 39, 44, 54, 58, 60, 79, 92, 82
9, 32, 39, 44, 54, 58, 60, 79, 82, 92
ค่าคงที่ N=10; พิมพ์ Mas=อาร์เรย์ของจำนวนเต็ม; var a: mas; เมื่อไฟล์มีคีย์ที่เหมือนกัน จะมีคำถามที่น่าสงสัยอีกสองข้อเกิดขึ้น อย่างแรกคือพอยน์เตอร์ทั้งสองควรหยุดที่คีย์เท่ากับองค์ประกอบที่หาร หรือหยุดเพียงอันเดียวและอันที่สองจะผ่านคีย์ทั้งหมด หรือพอยน์เตอร์ทั้งสองควรผ่านพวกมันไป จริงๆ แล้ว ปัญหานี้ได้รับการศึกษาอย่างละเอียดแล้ว และผลลัพธ์ก็แสดงให้เห็นว่าวิธีที่ดีที่สุดคือหยุดพอยน์เตอร์ทั้งสอง สิ่งนี้ช่วยให้คุณสามารถรักษาพาร์ติชั่นที่สมดุลไม่มากก็น้อยเมื่อมีคีย์ที่เหมือนกันหลายตัว ที่จริงแล้ว โปรแกรมนี้สามารถปรับปรุงได้เล็กน้อยโดยการยุติการสแกน j ลักษณะการทำงานของการเรียงลำดับแบบรวดเร็ว สิ่งที่ดีที่สุดที่อาจเกิดขึ้นกับอัลกอริทึมคือถ้าแต่ละไฟล์ย่อยถูกแบ่งออกเป็นสองไฟล์ย่อยที่มีขนาดเท่ากัน เป็นผลให้จำนวนการเปรียบเทียบที่ทำโดย Quicksort จะเท่ากับค่าของนิพจน์แบบเรียกซ้ำ CN = 2CN/2+N - กรณีที่ดีที่สุด (2CN/2 ครอบคลุมค่าใช้จ่ายในการจัดเรียงไฟล์ย่อยผลลัพธ์ทั้งสองไฟล์ N คือต้นทุนในการประมวลผลแต่ละองค์ประกอบโดยใช้ตัวชี้ตัวใดตัวหนึ่งหรือตัวชี้อื่น) เรายังรู้ด้วยว่าค่าโดยประมาณของนิพจน์นี้คือ CN = N lg N แม้ว่าเราจะไม่ได้เจอสถานการณ์นี้บ่อยนัก แต่โดยเฉลี่ยแล้วระยะเวลาการทำงานของโปรแกรมจะสอดคล้องกับสูตรนี้ เมื่อคำนึงถึงตำแหน่งที่เป็นไปได้ของแต่ละส่วนจะทำให้การคำนวณซับซ้อนมากขึ้น แต่ผลลัพธ์สุดท้ายจะคล้ายกัน คุณสมบัติ 1 Quicksort ใช้การเปรียบเทียบ 2N ln N โดยเฉลี่ย วิธีการปรับปรุงการเรียงลำดับอย่างรวดเร็ว การปรับปรุงครั้งแรกในอัลกอริธึมการเรียงลำดับอย่างรวดเร็วนั้นมาจากการสังเกตว่าโปรแกรมรับประกันว่าจะเรียกตัวเองในไฟล์ย่อยขนาดเล็กจำนวนมาก ดังนั้นเราควรใช้วิธีการเรียงลำดับที่ดีที่สุดเมื่อเราพบไฟล์ย่อยขนาดเล็ก วิธีที่ชัดเจนในการบรรลุเป้าหมายนี้คือการเปลี่ยนการตรวจสอบที่จุดเริ่มต้นของฟังก์ชันเรียกซ้ำจาก "if r>l then" เป็นการเรียกการเรียงลำดับการแทรก (แก้ไขอย่างเหมาะสมเพื่อให้เคารพขอบเขตของไฟล์ย่อยที่ถูกเรียงลำดับ): "if r-l<=M then insertion(l, r)." Значение для M не обязано быть "самым-самым" лучшим: алгоритм работает примерно одинаково для M от 5 до 25. Время работы программы при этом снижается примерно на 20% для большинства программ. สำหรับไฟล์ย่อยขนาดเล็ก (5-25 องค์ประกอบ) การเรียงลำดับอย่างรวดเร็วจะเรียกตัวเองหลายครั้ง (ในตัวอย่างของเรา สำหรับ 10 องค์ประกอบจะเรียกตัวเองว่า 15 ครั้ง) ดังนั้นคุณไม่ควรใช้การเรียงลำดับอย่างรวดเร็ว แต่เป็นการเรียงลำดับการแทรก ขั้นตอน QuickSort (l,t:จำนวนเต็ม); var i: จำนวนเต็ม; เริ่มต้นถ้า t-l>m จากนั้นเริ่มต้น i:=part(l,t); การปรับปรุงประการที่สองในอัลกอริธึมการเรียงลำดับอย่างรวดเร็วคือการพยายามใช้องค์ประกอบการแบ่งที่ดีที่สุด เรามีความเป็นไปได้หลายประการ ตัวเลือกที่ปลอดภัยที่สุดคือพยายามหลีกเลี่ยงกรณีที่เลวร้ายที่สุดโดยการเลือกองค์ประกอบอาร์เรย์ที่กำหนดเองเป็นองค์ประกอบการแบ่ง จากนั้นความน่าจะเป็นของกรณีที่เลวร้ายที่สุดจะมีน้อยมาก นี่คือตัวอย่างง่ายๆ ของอัลกอริธึม "ความน่าจะเป็น" ที่ใช้งานได้เกือบตลอดเวลาโดยไม่คำนึงถึงข้อมูลอินพุต การสุ่มอาจเป็นเครื่องมือที่ดีในการออกแบบอัลกอริธึม โดยเฉพาะอย่างยิ่งหากอินพุตที่น่าสงสัยเป็นไปได้ การปรับปรุงที่เป็นประโยชน์มากขึ้นคือนำสามองค์ประกอบจากไฟล์ จากนั้นใช้องค์ประกอบตรงกลางเป็นองค์ประกอบในการแบ่ง หากองค์ประกอบถูกนำมาจากจุดเริ่มต้น ตรงกลาง และจุดสิ้นสุดของไฟล์ คุณสามารถหลีกเลี่ยงการใช้องค์ประกอบป้องกันได้ โดยจัดเรียงองค์ประกอบทั้งสามที่ได้รับ จากนั้นสลับองค์ประกอบตรงกลางด้วย a จากนั้นใช้อัลกอริธึมการหารบนอาร์เรย์ a การปรับปรุงนี้เรียกว่าค่ามัธยฐานของการหารสาม ค่ามัธยฐานของวิธีการหารสามมีประโยชน์ด้วยเหตุผลสามประการ ประการแรก มันทำให้โอกาสที่จะเกิดสถานการณ์ที่เลวร้ายที่สุดลดลงมาก เพื่อให้อัลกอริธึมนี้ใช้เวลา N2 ตามสัดส่วน องค์ประกอบสองในสามองค์ประกอบที่นำมาใช้จะต้องมีขนาดเล็กที่สุดหรือใหญ่ที่สุด และจะต้องทำซ้ำจากส่วนหนึ่งไปอีกส่วนหนึ่ง ประการที่สอง วิธีการนี้จะขจัดความจำเป็นในการใช้องค์ประกอบเฝ้าระวัง เนื่องจากบทบาทนี้เล่นโดยหนึ่งในสามองค์ประกอบที่เราดำเนินการก่อนการแบ่ง ประการที่สาม ลดเวลาการทำงานของอัลกอริทึมลงประมาณ 5% การแลกเปลี่ยนขั้นตอน (i, j: จำนวนเต็ม); var k:จำนวนเต็ม; เริ่มต้น k:=a[i]; ก[เจ]:=k; จบ; ขั้นตอน Mediana; var i: จำนวนเต็ม; เริ่มต้น i:=n div 4;(รูปที่) ถ้า a[i]>a แล้วถ้า a[i]>a แล้วแลกเปลี่ยน(i,n) อื่น ๆ แลกเปลี่ยน(i*3,n) อื่นถ้า a>a แล้วแลกเปลี่ยน (i*2,n); เรียงลำดับอย่างรวดเร็ว(1,n); จบ; 3. การใช้งานแบบไม่เรียกซ้ำ การรวมการใช้งานการแบ่งค่ามัธยฐานสามแบบแบบไม่เรียกซ้ำเข้ากับการตัดออกเป็นไฟล์ขนาดเล็กสามารถปรับปรุงเวลาการทำงานของอัลกอริทึมได้ 25% ถึง 30% ดังนั้น ในบทเรียนวันนี้ เราจึงดูที่อัลกอริธึมการเรียงลำดับแบบด่วน การเรียงลำดับภายนอกแตกต่างจากการเรียงลำดับภายในอย่างมาก ความจริงก็คือการเข้าถึงไฟล์เป็นไปตามลำดับ และไม่ขนานเหมือนในอาร์เรย์ ดังนั้น ไฟล์จึงสามารถอ่านได้เฉพาะในบล็อกเท่านั้น และบล็อกนี้สามารถจัดเรียงในหน่วยความจำและเขียนกลับไปยังไฟล์ได้ ความสามารถพื้นฐานในการจัดเรียงไฟล์อย่างมีประสิทธิภาพโดยการทำงานกับส่วนต่างๆ ของไฟล์และไม่เกินขอบเขตของส่วนนั้นได้มาจากอัลกอริธึมการรวม การรวมหมายถึงการรวมลำดับที่เรียงลำดับสองรายการ (หรือมากกว่า) ให้เป็นลำดับเดียวโดยการวนรอบองค์ประกอบที่มีอยู่ในปัจจุบัน การรวมเป็นการดำเนินการที่ง่ายกว่าการเรียงลำดับมาก เราจะดูอัลกอริธึมการรวม 2 แบบ: ลำดับ a แบ่งออกเป็นสองซีก b และ c ลำดับ b และ c ถูกรวมเข้าด้วยกันโดยการรวมแต่ละองค์ประกอบเข้าเป็นคู่ที่เรียงลำดับ ลำดับผลลัพธ์จะได้รับชื่อ a หลังจากนั้นจึงทำซ้ำขั้นตอนที่ 1 และ 2 ในกรณีนี้ คู่ที่ได้รับคำสั่งจะรวมกันเป็นสี่เท่าที่ได้รับคำสั่ง ขั้นตอนก่อนหน้านี้จะถูกทำซ้ำ โดยสี่จะรวมกันเป็นแปด ฯลฯ จนกระทั่งลำดับทั้งหมดถูกเรียงลำดับ เพราะ ความยาวของลำดับสองเท่าในแต่ละครั้ง ตัวอย่าง ลำดับดั้งเดิม ก = 44 55 12 42 94 18 06 67 1 b = 44 55 12 42 วิ = 94 18 06 67 ก = 44 94" 18 55" 06 12" 42 67 2 b = 44 94" 18 55" วิ = 06 12" 42 67 ก = 06 12 44 94" 18 42 55 67" 3 ข = 06 12 44 94" ค = 18 42 55 67" ก = 06 12 18 42 44 55 67 94 การดำเนินการที่ประมวลผลชุดข้อมูลทั้งหมดเพียงครั้งเดียวเรียกว่าเฟส กระบวนการย่อยที่เล็กที่สุดซึ่งเมื่อทำซ้ำแล้วจะทำให้เกิดกระบวนการเรียงลำดับเรียกว่าผ่านหรือขั้นตอน ในตัวอย่างของเรา การเรียงลำดับจะดำเนินการในสามรอบ แต่ละรอบประกอบด้วยเฟสแยกและเฟสรวม ข้อเสียเปรียบหลักของการเรียงลำดับแบบผสานคือขนาดหน่วยความจำที่เดิมครอบครองโดยข้อมูลที่เรียงลำดับเป็นสองเท่า ลองพิจารณาอัลกอริธึมการรวมแบบเรียกซ้ำที่เสนอโดย Bose และ Nelson ซึ่งไม่ต้องการหน่วยความจำสำรอง มีพื้นฐานอยู่บนแนวคิดที่ชัดเจน: คุณสามารถรวมสองส่วนที่เรียงลำดับเท่ากันเข้าด้วยกันได้โดยการผสานครึ่งแรก การรวมครึ่งสุดท้าย และการรวมครึ่งหลังของผลลัพธ์แรกกับครึ่งแรกของผลลัพธ์ที่ 2 ตัวอย่างเช่น: หากชิ้นส่วนไม่เท่ากันหรือแบ่งครึ่งไม่เท่ากัน ขั้นตอนจะถูกปรับตามนั้น ในทำนองเดียวกันการรวม "ครึ่งหนึ่ง" สามารถลดลงเป็นการรวม "ไตรมาส", "แปด" ฯลฯ การเรียกซ้ำเกิดขึ้น ค่าคอนสต n=200; พิมพ์ tipkl=word; tip = บันทึก kl: tipkl; z:อาร์เรย์ของการสิ้นสุดจริง; Var A: อาร์เรย์ของปลาย; เจ:คำ; ขั้นตอน Bose (Var AA; voz:Boolean); วาร์ ม,เจ:คำ; x:ทิป; (ทิป - ประเภทของเรคคอร์ดที่กำลังจัดเรียง) A: อาร์เรย์ของทิป Absolute AA; ขั้นตอน Sli(j,r,m: word); (r คือระยะห่างระหว่างจุดเริ่มต้นของส่วนที่ผสาน และ m คือขนาดของส่วนนั้น j คือหมายเลขบันทึกที่เล็กที่สุด) เริ่มต้นถ้า j+r<=n Then
If m=1 Then
Begin
If voz Xor (A[j].kl < A.kl) Then
Begin
x:=A[j];
A[j]:= A;
A:=x
End
End
Else
Begin
m:=m div 2;
Sli(j,r,m); {Слияние "начал"}
If j+r+m<=n Then
Sli(j+m,r,m); {Слияние "концов"}
Sli(j+m,r-m,m) End {Слияние в центральной части}
End{блока Sli};
Begin
m:=1;
Repeat
j:=1; {Цикл слияния списков равного размера: }
While j+m<=n do
Begin
Sli(j,m,m);
j:=j+m+m
End;
m:=m+m {Удвоение размера списка перед началом нового прохода}
Until m >= n (จุดสิ้นสุดของลูปที่ใช้แผนผังผสานทั้งหมด) สิ้นสุด(บล็อก Bose); เริ่มต้น สุ่ม; สำหรับ j:=1 ถึง n ให้เริ่มต้น A[j].kl:= Random(65535); เขียน(A[j].kl:8); จบ; อ่าน; โบส(ก,จริง); สำหรับ j:=1 ถึง n ทำ Write(A[j].kl:8); อ่านจบแล้ว มันรวมชิ้นส่วนที่ได้รับคำสั่งซึ่งเกิดขึ้นเองตามธรรมชาติในอาเรย์ดั้งเดิม นอกจากนี้ยังอาจเป็นผลมาจากการประมวลผลข้อมูลก่อนหน้านี้ ไม่จำเป็นต้องนับชิ้นส่วนที่รวมขนาดเท่ากัน บันทึกในลำดับคีย์ที่ไม่ลดลงจะถูกต่อเข้าด้วยกันเพื่อสร้างรายการย่อย รายการย่อยขั้นต่ำคือหนึ่งรายการ ตัวอย่าง: ให้คีย์บันทึกได้รับ 5 7 8 3 9 4 1 7 6
กำลังมองหารายการย่อย รายการย่อยที่ 1, 3, 5 ฯลฯ จะรวมกันเป็นรายการทั่วไปรายการเดียว และรายการย่อยที่ 2, 4 ฯลฯ จะรวมอยู่ในรายการอื่น ลองรวม 1 รายการย่อยจาก 1 รายการกับ 1 รายการย่อยจาก 2 รายการ, 2 รายการย่อยจาก 1 รายการ และ 2 รายการย่อยจาก 2 รายการ เป็นต้น จะได้รับโซ่ต่อไปนี้ 3 --> 5 --> 7 --> 8 --> 9 และ 1 --> 4 --> 7 รายการย่อยที่ประกอบด้วยรายการ "6" ไม่มีคู่และถูกรวม "บังคับ" กับเชนสุดท้ายซึ่งอยู่ในรูปแบบ 1 --> 4--> 6 --> 7 ด้วยจำนวนผู้เข้าร่วมที่น้อยของเรา ด่านที่ 2 ซึ่งทั้งสองเชนมารวมกันจะเป็นขั้นตอนสุดท้าย โดยทั่วไป ในแต่ละขั้นตอนของรายการย่อย ผลลัพธ์ของการรวมรายการย่อยเริ่มต้น 1 และ 2 ของรายการจะกลายเป็นจุดเริ่มต้นของรายการที่ 1 ใหม่ และผลลัพธ์ของการรวมสองรายการย่อยถัดไปจะกลายเป็นจุดเริ่มต้นของรายการที่ 2 รายการย่อยต่อไปนี้ที่เกิดขึ้นจะสลับกันรวมอยู่ในรายการที่ 1 และ 2 สำหรับการใช้งานซอฟต์แวร์ อาร์เรย์ sp จะถูกสร้างขึ้น: องค์ประกอบ sp[i] คือหมายเลขของบันทึกที่ตามหลัง i-th รายการสุดท้ายของรายการย่อยหนึ่งลิงก์ไปยังรายการแรกของอีกรายการหนึ่ง และเพื่อแยกความแตกต่างจุดสิ้นสุดของรายการย่อย ลิงก์นี้จะมีเครื่องหมายลบ ทำซ้ำ (การทำซ้ำของการรวมรายการย่อย) ถ้า A[j].kl< A[i].kl Then {Выбирается меньшая запись}
Begin sp[k]:=j; k:=j; j:=sp[j];
If j<=0 Then {Сцепление с остатком "i"-подсписка}
Begin sp[k]:=i; Repeat m:=i; i:=sp[i] Until i<=0 End
End
Else
Begin sp[k]:=i; k:=i; i:=sp[i];
If i<=0 Then {Сцепление с остатком "j"-подсписка}
Begin sp[k]:=j; Repeat m:=j; j:=sp[j] Until j<=0 End
End;
If j<=0 Then Begin sp[m]:= 0; sp[p]:=-sp[p]; i:=-i;
j:=-j; If j<>0 จากนั้น p:=r; เค:=ร; r:=m สิ้นสุดจนกระทั่ง j=0; (การอ้างอิงที่เป็นค่าว่าง (sp[m]:= 0) จะถูกเพิ่มต่อท้ายรายการย่อยที่สร้างขึ้นเสมอ เนื่องจากอาจเป็นรายการสุดท้าย การดำเนินการ sp[p]:= -sp[p] หมายถึงจุดสิ้นสุดของรายการย่อยที่สร้างขึ้นก่อนหน้านี้ด้วยเครื่องหมายลบ ดังนั้น ในบทเรียนวันนี้ เราจึงดูที่การรวมอัลกอริธึม // การโทรซ้ำหากมีสิ่งใดที่ต้องเรียงลำดับ ถ้า (j > 0) QuickSortR(a, j); ถ้า (N > i) QuickSortR(a+i, N-i); - แต่ละแผนกต้องการการดำเนินการ Theta(n) อย่างชัดเจน จำนวนขั้นตอนการหาร (ความลึกของการเรียกซ้ำ) จะอยู่ที่ประมาณ log n หากอาร์เรย์ถูกแบ่งออกเป็นส่วนที่เท่ากันไม่มากก็น้อย ดังนั้นประสิทธิภาพโดยรวมคือ O(n log n) ซึ่งเป็นสิ่งที่เกิดขึ้นในทางปฏิบัติ วิธีการนี้ไม่เสถียร ลักษณะการทำงานค่อนข้างเป็นธรรมชาติ เมื่อพิจารณาว่าการเรียงลำดับบางส่วนจะเพิ่มโอกาสในการแบ่งอาร์เรย์ออกเป็นส่วนที่เท่ากันมากขึ้น การเรียงลำดับใช้หน่วยความจำเพิ่มเติมเนื่องจากความลึกของการเรียกซ้ำโดยประมาณคือ O(log n) และการเรียกย่อยแบบเรียกซ้ำจะถูกผลักลงบนสแต็กในแต่ละครั้ง การปรับเปลี่ยนรหัสและวิธีการ เนื่องจากการเรียกซ้ำและค่าใช้จ่ายอื่นๆ Quicksort อาจไม่เร็วขนาดนั้นสำหรับอาร์เรย์แบบสั้น ดังนั้น หากมีองค์ประกอบ CUTOFF น้อยลงในอาเรย์ (ค่าคงที่ขึ้นอยู่กับการใช้งาน โดยปกติจะอยู่ระหว่าง 3 ถึง 40) การเรียงลำดับการแทรกจะถูกเรียก เพิ่มความเร็วได้ถึง 15%<=CUTOFF элементов, отсортированные друг относительно друга. Близкие элементы имеют близкие позиции, поэтому, аналогично сортировке Шелла, вызывается insertSort(), которая доводит процесс до конца. หากต้องการนำวิธีนี้ไปใช้ คุณสามารถแก้ไขฟังก์ชัน QuickSortR ได้โดยการแทนที่ 2 บรรทัดสุดท้ายด้วย ลองพิจารณากรณีที่เลวร้ายที่สุด เมื่อองค์ประกอบสนับสนุนที่เลือกแบบสุ่มกลายเป็นสิ่งที่แย่มาก (เกือบจะสุดขั้ว) ความน่าจะเป็นของสิ่งนี้ต่ำมาก โดยที่ n = 1,024 นั้นน้อยกว่า 2 -50 ดังนั้นความสนใจจึงเป็นทฤษฎีมากกว่าภาคปฏิบัติ อย่างไรก็ตาม พฤติกรรม "การเรียงลำดับอย่างรวดเร็ว" ถือเป็น "เกณฑ์มาตรฐาน" สำหรับอัลกอริธึมการแบ่งแยกและพิชิตที่มีการใช้งานคล้ายกัน ไม่ใช่ทุกที่ที่เป็นไปได้ที่จะลดความน่าจะเป็นของกรณีที่เลวร้ายที่สุดให้เหลือเกือบศูนย์ ดังนั้นสถานการณ์นี้จึงสมควรได้รับการศึกษา เพื่อความแน่นอน ให้เลือกองค์ประกอบที่เล็กที่สุดต่อนาทีในแต่ละครั้ง จากนั้นขั้นตอนการแยกจะย้ายองค์ประกอบนี้ไปยังจุดเริ่มต้นของอาร์เรย์ และสองส่วนจะถูกส่งไปยังระดับถัดไปของการเรียกซ้ำ: ส่วนหนึ่งจากองค์ประกอบเดียว a min อีกส่วนหนึ่งประกอบด้วยองค์ประกอบ n-1 ที่เหลือของอาร์เรย์ จากนั้นกระบวนการจะทำซ้ำสำหรับส่วนหนึ่งขององค์ประกอบ (n-1).. และอื่นๆ.. เพื่อขจัดสถานการณ์นี้ คุณสามารถแทนที่การเรียกซ้ำด้วยการวนซ้ำโดยการนำสแต็กแบบอาร์เรย์ไปใช้ ขั้นตอนการแยกจะดำเนินการในลักษณะวนซ้ำ รหัสเทียม ขนาดสแต็กที่มีการใช้งานนี้จะอยู่ในลำดับ O(log n) เสมอ ดังนั้นค่าที่ระบุใน MAXSTACK จึงเกินพอ จนถึงขณะนี้ สิ่งตีพิมพ์เดียวเกี่ยวกับการเรียงลำดับในบล็อกของฉันคือ ถึงเวลาแก้ไขแล้ว! ฉันจะไม่วางแผนที่ยิ่งใหญ่เพื่อพิชิตการเรียงลำดับทุกประเภท แต่ฉันจะเริ่มต้นด้วยการเรียงลำดับแบบด่วนที่ได้รับความนิยมมากที่สุด ฉันจะไม่ใช่คนแรกและจะไม่ใช่คนสุดท้ายที่บอกว่า "เร็ว" เป็นเพียงชื่อเท่านั้นและตอนนี้มีอะนาล็อกมากมายและโดยทั่วไปแล้วข้อมูลแต่ละประเภทต้องมีการเรียงลำดับของตัวเอง ใช่ นี่เป็นเรื่องจริงทั้งหมด แต่ความจริงนี้ไม่ได้เปลี่ยนข้อเท็จจริงง่ายๆ อย่างนั้น การใช้การเรียงลำดับอย่างรวดเร็วของคุณเองจะทำให้ทักษะการเขียนโปรแกรมของคุณดีขึ้นโดยทั่วไป
และมันจะเป็นหลักสูตรของมหาวิทยาลัยตลอดไป ฉันมั่นใจ ด้วยเหตุผลเดียวกัน จึงเลือกภาษาการเขียนโปรแกรมสำหรับการใช้งาน เนื่องจากคุณสามารถฝึกใช้พอยน์เตอร์ใน C/C++ ได้ทันที ฉันเสนอให้ลงมือทำธุรกิจและพิจารณาสาระสำคัญของอัลกอริทึมโดยสังเขปก่อน แผนภาพอัลกอริทึมสามารถอธิบายได้ดังต่อไปนี้: การเข้าสู่การเรียกซ้ำจะหยุดทันทีที่ขนาดของทั้งสองส่วนน้อยกว่าหรือเท่ากับหนึ่ง ฉันยืมภาพประกอบขั้นตอนหนึ่งของอัลกอริทึมมา มันชัดเจนอย่างเจ็บปวด ฟังก์ชันนี้ใช้เป็นอินพุตของอาร์เรย์เอง (ตัวชี้ไปยังจุดเริ่มต้น) และขนาดของอาร์เรย์ Void qsortRecursive(int *mas, int size) ( //ตัวชี้ไปยังจุดเริ่มต้นและจุดสิ้นสุดของอาร์เรย์ int i = 0; int j = ขนาด - 1; //องค์ประกอบกลางของอาร์เรย์ int mid = mas; //หาร array do ( // เราผ่านองค์ประกอบต่างๆ โดยมองหาองค์ประกอบที่ต้องถ่ายโอนไปยังส่วนอื่น // ทางด้านซ้ายของอาร์เรย์เราข้าม (ปล่อยไว้) องค์ประกอบที่เล็กกว่าองค์ประกอบส่วนกลาง ในขณะที่(mas[ ฉัน]< mid) {
i++;
}
//В правой части пропускаем элементы, которые больше центрального
while(mas[j] >กลาง) ( j--; ) // สลับองค์ประกอบถ้า (i<= j) {
int tmp = mas[i];
mas[i] = mas[j];
mas[j] = tmp;
i++;
j--;
}
} while (i <= j);
//Рекурсивные вызовы, если осталось, что сортировать
if(j >0) ( // "ชิ้นซ้าย" qsortRecursive (mas, j + 1); ) ถ้า (i< size) {
//"Првый кусок"
qsortRecursive(&mas[i], size - i);
}
}
งานนี้พร้อมกันช่วยให้คุณเข้าใจวิธีการทำงานของการเรียกซ้ำและสอนวิธีติดตามการเปลี่ยนแปลงข้อมูลระหว่างการดำเนินการของอัลกอริทึม ขอแนะนำให้ "ดำเนินการต่อ" และเขียนด้วยตัวเอง แต่นี่คือการนำไปปฏิบัติของฉัน ตัวฉันเองอาจพบว่ามีประโยชน์ นั่นคือทั้งหมดสำหรับฉัน ขอบคุณสำหรับความสนใจของคุณ! อัปเดต: 18/03/2019 จัดเรียงอย่างรวดเร็ว (จัดเรียงอย่างรวดเร็ว) หรือการเรียงลำดับ Hoare เป็นหนึ่งในอัลกอริธึมการเรียงลำดับข้อมูลที่เร็วที่สุด อัลกอริธึมของ Hoare เป็นเวอร์ชันแก้ไขของวิธีการแลกเปลี่ยนโดยตรง รูปแบบอื่นที่เป็นที่นิยมของวิธีนี้คือ การเรียงลำดับฟองและ การเรียงลำดับเครื่องปั่นต่างจาก Quicksort ตรงที่ไม่มีประสิทธิภาพมากนัก แนวคิดของอัลกอริทึมมีดังนี้: เนื่องจากวิธีการ Quicksort แบ่งอาร์เรย์ออกเป็นส่วนๆ จึงอยู่ในกลุ่มของอัลกอริทึม แบ่งแยกและพิชิต. QuickSort (อาร์เรย์, minIndex, pivotIndex - 1);การเรียงลำดับการแทรก
ผสานการเรียงลำดับ
จัดเรียงอย่างรวดเร็ว
1. ไฟล์ย่อยขนาดเล็ก
QuickSort(l,i-1);
ก[i]:=ก[เจ];
คุณสามารถเขียนอัลกอริทึมนี้ใหม่ได้โดยไม่ต้องใช้การเรียกซ้ำโดยใช้สแต็ก แต่เราจะไม่ทำอย่างนั้นที่นี่
การควบรวมกิจการโดยตรง อัลกอริธึมของโบส-เนลสัน
ฟิวชั่นธรรมชาติ (นอยมันน์)
รหัสเทียม
QuickSort(array a, upper bound N) ( เลือกองค์ประกอบ pivot p - ตรงกลางของอาร์เรย์ แยกอาร์เรย์ที่องค์ประกอบนั้น หากอาร์เรย์ย่อยทางด้านซ้ายของ p มีมากกว่าหนึ่งองค์ประกอบ ให้เรียก QuickSort ไว้ หากอาร์เรย์ย่อยทางด้านขวาของ p มีมากกว่าหนึ่งองค์ประกอบ เรียก QuickSort ให้เขา ) การใช้งานใน C
แม่แบบ อย่างไรก็ตาม อาจมีกรณีของข้อมูลอินพุตดังกล่าวซึ่งอัลกอริธึมจะทำงานในการดำเนินการ O(n 2) สิ่งนี้จะเกิดขึ้นหากแต่ละครั้งมีการเลือกลำดับอินพุตสูงสุดหรือต่ำสุดเป็นองค์ประกอบส่วนกลาง หากข้อมูลถูกสุ่ม ความน่าจะเป็นของสิ่งนี้คือ 2/n และความน่าจะเป็นนี้จะต้องเกิดขึ้นจริงในทุกขั้นตอน... โดยทั่วไป นี่เป็นสถานการณ์ที่ไม่สมจริง
การใช้โค้ดแบบเรียกซ้ำเหมือนกับที่กล่าวมาข้างต้น หมายความว่าไม่มีการเรียกแบบเรียกซ้ำแบบซ้อนไปยัง QuickSort
การโทรซ้ำแต่ละครั้งหมายถึงการจัดเก็บข้อมูลเกี่ยวกับสถานะปัจจุบัน ดังนั้นการเรียงลำดับจึงต้องใช้หน่วยความจำเพิ่มเติม O(n)... และไม่ใช่แค่ที่ใดก็ได้ แต่บนสแต็ก หาก n มีขนาดใหญ่เพียงพอ ข้อกำหนดดังกล่าวอาจนำไปสู่ผลลัพธ์ที่คาดเดาไม่ได้
ทุกครั้งที่อาร์เรย์ถูกแบ่งออกเป็นสองส่วน คำร้องขอจะถูกส่งไปยังสแต็กเพื่อจัดเรียงอันที่ใหญ่กว่า และอันที่เล็กกว่าจะถูกประมวลผลในการวนซ้ำครั้งถัดไป ข้อความค้นหาจะถูกดึงออกมาจากสแต็กเนื่องจากขั้นตอนการแบ่งพาร์ติชันไม่มีงานปัจจุบัน การเรียงลำดับจะสิ้นสุดลงเมื่อไม่มีคำขออีกต่อไป
Iterative QuickSort (อาร์เรย์ a, ขนาดขนาด) (พุชคำขอเพื่อเรียงลำดับอาร์เรย์จาก 0 ถึงขนาด -1 บนสแต็ก do (ป๊อปขอบเขต lb และ ub ของอาร์เรย์ปัจจุบันจากสแต็ก do ( 1. ดำเนินการแยก การดำเนินการกับอาร์เรย์ปัจจุบัน a. 2. ส่งขอบเขตของส่วนที่ใหญ่กว่าของผลลัพธ์ไปยังสแต็ก 3. ย้ายขอบเขตของ ub, lb เพื่อให้ชี้ไปที่ส่วนที่เล็กกว่า) ในขณะที่ส่วนที่เล็กกว่าประกอบด้วยสองส่วนขึ้นไป องค์ประกอบ) ในขณะที่มีการร้องขอบนสแต็ก) การใช้งานใน C.
#กำหนด MAXSTACK 2048 // ขนาดสแต็กสูงสุดแม่แบบ การเรียงลำดับอย่างรวดเร็วทำงานอย่างไร
ซึ่งสามารถแสดงให้เห็นได้อย่างชัดเจนดังนี้:
|———————|—————————|———————|
- มาส[i]<= mid | mid = mas | mas[i] >= กลาง |
|———————-|—————————|———————|การใช้งาน Quicksort แบบเรียกซ้ำ
บทสรุป
อัลกอริธึมการเรียงลำดับอย่างรวดเร็วทำงานอย่างไร
การใช้การเรียงลำดับอย่างรวดเร็ว
ใช้ระบบ; class Program ( //วิธีการแลกเปลี่ยนองค์ประกอบอาร์เรย์ static void Swap(ref int x, ref int y) ( var t = x; x = y; y = t; ) //วิธีการส่งคืนดัชนีขององค์ประกอบอ้างอิง static int Partition (int array , int minIndex, int maxIndex) ( var pivot = minIndex - 1; สำหรับ (var i = minIndex; i< maxIndex; i++)
{
if (array[i] < array)
{
pivot++;
Swap(ref array, ref array[i]);
}
}
pivot++;
Swap(ref array, ref array);
return pivot;
}
//быстрая сортировка
static int QuickSort(int array, int minIndex, int maxIndex)
{
if (minIndex >= maxIndex) ( return array; ) var pivotIndex = Partition(array, minIndex, maxIndex);< a.Length; ++i)
{
Console.Write("a[{0}] = ", i);
a[i] = Convert.ToInt32(Console.ReadLine());
}
Console.WriteLine("Упорядоченный массив: {0}", string.Join(", ", QuickSort(a)));
Console.ReadLine();
}
}