Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Merge Sort ใน Ruby: คู่มือทีละขั้นตอน

บทความนี้เป็นส่วนที่ 3 ในชุดบทความที่สำรวจการนำอัลกอริทึมการจัดเรียงต่างๆ ไปใช้กับ Ruby ส่วนที่ 1 สำรวจ Bubble Sort และส่วนที่ 2 สำรวจ Selection Sort

ตามที่เราได้พูดคุยกันในบทความก่อนหน้านี้ของชุดนี้ การเข้าใจวิธีการจัดเรียงข้อมูลเป็นส่วนสำคัญในชุดเครื่องมือของวิศวกรซอฟต์แวร์ทุกคน โชคดีที่ภาษาระดับสูงส่วนใหญ่ เช่น Ruby มีเมธอดสำเร็จรูปที่มีประสิทธิภาพในการจัดเรียงอาร์เรย์อยู่แล้ว ตัวอย่างเช่น เมื่อคุณเรียก .sort บนอาร์เรย์ คุณก็กำลังใช้ Quicksort ภายใต้ฮู้ด ในบทความนี้เราจะเรียนรู้อัลกอริทึมที่คล้ายกับ Quick Sort คือ Merge Sort ทั้งสองอัลกอริทึมใช้แนวทาง "แบ่งและเอาชนะ" (Divide and Conquer) Merge Sort ถูกสร้างขึ้นโดย John von Neumann ในปี 1945 von Neumann เป็นนักวิทยาศาสตร์คอมพิวเตอร์และนักฟิสิกส์ที่มีชื่อเสียง ยังเป็นที่รู้จักจากงานในโครงการ Manhattan ทฤษฎี "Mini-max" วิธี Monte Carlo และอื่นๆ อีกมากมาย

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

เชี่ยวชาญ Merge Sort ใน Ruby: คู่มือทีละขั้นตอน

ไม่เหมือนกับอัลกอริทึมที่เราพูดคุยกันมาก่อนหน้านี้ (เช่น Bubble และ Selection) ซึ่งเป็นพื้นฐานไม่ค่อยเหมาะสมสำหรับงานจริง Merge Sort ให้ประสิทธิภาพที่ดีกว่ามากในแง่ของ Big-O Notation หากคุณไม่คุ้นเคยกับ Big-O Notation มันแสดงถึงประสิทธิภาพสถานการณ์แย่ที่สุดของอัลกอริทึมต่างๆ ซึ่งทำให้เราเปรียบเทียบอัลกอริทึมได้ง่ายขึ้นจาก Big-O ของมัน ตัวอย่างเช่น อัลกอริทึมที่มี Big-O เป็น O(1) หมายความว่าเวลารันสถานการณ์แย่ที่สุดคงที่เมื่อจำนวนองค์ประกอบ "n" เพิ่มขึ้น ในขณะที่อัลกอริทึมที่มี Big-O เป็น O(n) หมายความว่าเวลารันสถานการณ์แย่ที่สุดเพิ่มขึ้นแบบเส้นตรงเมื่อ n เพิ่มขึ้น หมายความว่าหากคุณมีอาร์เรย์ 100 องค์ประกอบและต้องเลือกระหว่างอัลกอริทึมจัดเรียงที่เป็น O(n) และ O(1) คุณจะเลือก O(1) เพราะ O(1) ชนะ O(100) แน่นอน อัลกอริทึม Bubble และ Selection Sort ทั้งคู่มีสถานการณ์แย่ที่สุดเป็น O(n^2) นี่ไม่ค่อยมีประโยชน์เพราะหมายถึงอัลกอริทึมจะทำงานช้ามากเมื่อจำนวนองค์ประกอบเพิ่มขึ้น ในทางตรงข้าม Merge Sort ให้ประสิทธิภาพที่ n log(n) ซึ่งหมายถึงอัลกอริทึมจะไม่เสียประสิทธิภาพเท่า Bubble หรือ Selection Sort

มาเดินทางผ่านตัวอย่างในภาพประกอบกัน เราเริ่มต้นด้วยอาร์เรย์ [38, 27, 43, 3, 9, 82, 10] จากนั้นแบ่งอาร์เรย์ครึ่งซ้ำๆ จนเหลือองค์ประกอบเดี่ยว

  1. เราแบ่งอาร์เรย์เริ่มต้นออกเป็นสองครึ่ง: [38, 27, 43, 3] และ [9, 82, 10]
  2. เราแบ่งครึ่งแรกอีกครั้ง: [38, 27] และ [43, 3]
  3. เราแบ่งครึ่งแรกเป็นองค์ประกอบเดี่ยว: [38], [27], [43], และ [3]
  4. เราจัดเรียง 38 และ 27 ให้เป็น [27, 38] และ 43 และ 3 ให้เป็น [3, 43]
  5. นำมารวมกัน เราได้ [3, 27, 38, 43]
  6. ตอนนี้เราย้ายไปครึ่งที่สองของอาร์เรย์เดิมซึ่งเป็น [9, 82, 10] เราแบ่งครึ่งได้ [9, 82] และ [10]
  7. เราแบ่ง [9, 82] เป็น [9] และ [82] และมี [10] ซึ่งเป็นองค์ประกอบเดี่ยวอยู่แล้ว
  8. เราจัดเรียง [9, 82] กลับเข้าด้วยกันแล้วรวม [10] กลับเข้าไป ทำให้ได้ [9, 10, 82]
  9. สุดท้ายเรารวม [3, 27, 38, 43] และ [9, 10, 82] ได้ [3, 9, 10, 27, 38, 43, 82]

การนำ Merge Sort ไปใช้กับ Ruby

ต่อไปนี้คืออัลกอริทึม Merge Sort ที่เขียนด้วย Ruby:

class MergeSort def sort(numbers) num_elements = numbers.length if num_elements <= 1 return numbers end half_of_elements = (num_elements / 2).round left = numbers.take(half_of_elements) right = numbers.drop(half_of_elements) sorted_left = sort(left) sorted_right = sort(right) merge(sorted_left, sorted_right) end def merge(left_array, right_array) if right_array.empty? return left_array end if left_array.empty? return right_array end smallest_number = if left_array.first <= right_array.first left_array.shift else right_array.shift end recursive = merge(left_array, right_array) [smallest_number].concat(recursive) end end 

มาเดินทางดูว่ากำลังเกิดอะไรขึ้นที่นี่กัน แรกเราจะเน้นที่เมธอด sort ด้านบน

 def sort(numbers) num_elements = numbers.length if num_elements <= 1 return numbers end half_of_elements = (num_elements / 2).round left = numbers.take(half_of_elements) right = numbers.drop(half_of_elements) sorted_left = sort(left) sorted_right = sort(right) merge(sorted_left, sorted_right) end 

เป้าหมายของส่วนโค้ดนี้คือแบ่งตัวเลขที่กำหนดครึ่งซ้ำๆ จนเหลือเพียงรายการเดียวในแต่ละอัน หากต้องการดูการทำงาน ให้คอมเมนต์บรรทัดสุดท้าย (merge(sorted_left, sorted_right)) และแทนที่ด้วยการพิมพ์ sorted_left และ sorted_right การรันโปรแกรมโดยส่งอาร์เรย์ตัวอย่างของเรา คุณควรเห็นสิ่งนี้ในเทอร์มินัลของคุณ:

merge_sort = MergeSort.new puts merge_sort.sort([38, 27, 43, 3, 9, 82, 10]) 

ruby ruby-merge-sort.rb

27 43 38 3 9 82 10 

ยอดเยี่ยม! โค้ดของเราได้รับอาร์เรย์เริ่มต้นและแบ่งครึ่งแล้ว มาดูส่วน merge ของโค้ดต่อไป

 def merge(left_array, right_array) if right_array.empty? return left_array end if left_array.empty? return right_array end smallest_number = if left_array.first <= right_array.first left_array.shift else right_array.shift end recursive = merge(left_array, right_array) [smallest_number].concat(recursive) end 

แรกเราตรวจสอบว่าซับอาร์เรย์ใดซับอาร์เรย์หนึ่งว่างหรือไม่ หากใช่ เราสามารถคืนค่าอีกอันได้เลย มิฉะนั้น ถ้าทั้งสองซับอาร์เรย์ไม่ว่าง เราเปรียบเทียบค่าขององค์ประกอบแรกของแต่ละอาร์เรย์ จากนั้น shift ค่าที่เปรียบเทียบเพื่อป้องกันการสร้าง Infinite Loop ต่อไปเราใช้ Recursion (รายละเอียดเพิ่มเติมในส่วหนี้!) บนอาร์เรย์เดิมจนกว่าจะสามารถเชื่อมต่อสองอาร์เรย์เข้าด้วยกันได้อย่างสวยงามและจัดเรียงแล้ว

เพิ่มเติมเกี่ยวกับ Recursion

หากมีสิ่งที่ดูแปลกในโค้ดของเรา คาดเดาว่าคือบรรทัดนี้: recursive = merge(left_array, right_array) เรากำลังเรียกเมธอด merge จากภายในตัวมันเอง ว้าว! นี่คือสิ่งที่เราเรียกว่า Recursion — เทคนิคที่ฟังก์ชันเรียกตัวมันเองหนึ่งครั้งหรือหลายครั้งจนกว่าเงื่อนไขที่ระบุจะถูกตอบสนอง ในกรณีของเรา merge จะถูกเรียกซ้ำจนกว่าอาร์เรย์ซ้ายหรือขวาจะว่าง หากคุณสนใจเรียนรู้เพิ่มเติมเกี่ยวกับ Recursion นี่คือตัวอย่างที่เดินทางผ่านการใช้ Ruby และ Recursion เพื่อเขียนฟังก์ชันสำหรับลำดับฟีโบนัชชี

สรุป

หวังว่าคุณจะเพลิดเพลินกับการเรียนรู้เพิ่มเติมเกี่ยวกับ Merge Sort! การเข้าใจวิธีการทำงานในระดับภาพรวม รวมถึงเหตุผลที่มันเป็นตัวเลือกที่มีประสิทธิภาพกว่า Bubble หรือ Selection Sort อาจจะเป็นประโยชน์ในงานสัมภาษณ์หรืองานประจำวันของคุณ อีกทั้งยังมีรูปแบบต่างๆ ของ Merge Sort หลายแบบที่คุณสามารถอ่านเพิ่มเติมได้ที่ Wikipedia หากสนใจ จนกว่าจะพบกันใหม่ ... Happy Sorting!