الترتيب بالدمج والترتيب السريع
خوارزميتان من فرّق تسُد ترتّبان مليون عنصر في لحظات. ⏱ نحو 35 دقيقة.
- تشرح الترتيب بالدمج وتنفّذه بالعودية.
- تدمج قائمتين مرتبتين في قائمة مرتبة.
- تشرح الترتيب السريع وفكرة المحور.
- تقارن بين الدمج والسريع في الأداء والذاكرة والحالة الأسوأ.
١الترتيب بالدمج Merge Sort
تطبيق مباشر لفرّق تسُد:
- فرّق: اقسم القائمة نصفين.
- سُد: رتّب كل نصف بالطريقة نفسها (عوديًّا)، حتى تصل إلى قوائم من عنصر واحد، وهي مرتبة أصلًا.
- اجمع: ادمج النصفين المرتبين في قائمة واحدة مرتبة.
٢الدمج: قلب الخوارزمية
دمج قائمتين مرتبتين سهل: قارن أول عنصر في كل منهما، وخذ الأصغر، وكرر:
def merge(left, right): result = [] i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]); i += 1 else: result.append(right[j]); j += 1 result.extend(left[i:]) # ما بقي من اليسرى result.extend(right[j:]) # ما بقي من اليمنى return result print(merge([3, 27, 38, 43], [9, 10, 15, 82]))
def merge(left, right): result = [] i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]); i += 1 else: result.append(right[j]); j += 1 return result + left[i:] + right[j:] def merge_sort(a): if len(a) <= 1: # حالة التوقف return a mid = len(a) // 2 left = merge_sort(a[:mid]) # فرّق وسُد right = merge_sort(a[mid:]) return merge(left, right) # اجمع print(merge_sort([38, 27, 43, 3, 9, 82, 10, 15]))
٣الترتيب السريع Quick Sort
فكرة أخرى لفرّق تسُد: اختر عنصرًا اسمه المحور Pivot، وضع كل الأصغر منه يساره وكل الأكبر يمينه. صار المحور في مكانه النهائي! ثم كرر في كل جهة:
def quick_sort(a): if len(a) <= 1: return a pivot = a[-1] smaller = [x for x in a[:-1] if x < pivot] larger = [x for x in a[:-1] if x >= pivot] return quick_sort(smaller) + [pivot] + quick_sort(larger) print(quick_sort([38, 27, 43, 3, 9, 82, 10, 15]))
هذه نسخة واضحة للفهم تنشئ قوائم جديدة. والنسخة الاحترافية (كما في المحاكاة) تبدّل العناصر داخل القائمة نفسها دون نسخ.
٤المقارنة بينهما
| الدمج | السريع | |
|---|---|---|
| أين العمل الأكبر؟ | في الدمج (بعد التقسيم) | في التقسيم حول المحور (قبل العودية) |
| الأداء المعتاد | سريع: نحو n log n | سريع: نحو n log n، وغالبًا أسرع عمليًّا |
| الحالة الأسوأ | ثابت دائمًا: n log n | بطيء n² إن كان المحور دائمًا الأصغر أو الأكبر (قائمة مرتبة مع محور أخير) |
| الذاكرة | يحتاج قائمة إضافية بحجم n | يعمل داخل القائمة نفسها تقريبًا |
| الثبات Stable | نعم: المتساويان يبقيان بترتيبهما | لا (في نسخته المعتادة) |
نعدّ المقارنات على قائمة عشوائية وعلى قائمة مرتبة، لنرى الحالة الأسوأ للسريع:
import randomrandom.seed(1) comps = 0def quick(a): global comps if len(a) <= 1: return a p = a[-1] comps += len(a) - 1 return quick([x for x in a[:-1] if x < p]) + [p] + quick([x for x in a[:-1] if x >= p]) data = random.sample(range(10000), 500)quick(data); print("عشوائية:", comps)comps = 0quick(sorted(data)); print("مرتبة: ", comps)
على القائمة المرتبة يكون المحور (الأخير) أكبر عنصر دائمًا، فلا يقسم شيئًا. لذلك تختار النسخ الاحترافية المحور عشوائيًّا أو من الوسط. أما sorted() في بايثون فتستخدم خوارزمية Timsort المبنية على الدمج والإدراج، وهي سريعة في كل الحالات.
توقّع وتحقّق
في الترتيب بالدمج، العمل الأكبر يحدث في:
Combine.بعد التقسيم حول المحور يكون المحور:
الحالة الأسوأ للسريع بمحور أخير:
أي خوارزمية تحتاج ذاكرة إضافية بحجم القائمة؟
الخلاصة
- الدمج: قسّم نصفين، ورتّب كلًّا عوديًّا، ثم ادمج.
- الدمج ثابت
n log nدائمًا، لكنه يحتاج ذاكرة إضافية. - السريع: محور، الأصغر يساره والأكبر يمينه، ثم كرر.
- السريع غالبًا أسرع عمليًّا، وأسوأ حالته
n² بمحور سيئ.
تحدٍّ برمجي: دمج قائمتي درجات
متوسطلديك درجات شعبتين، كل منهما مرتبة تصاعديًّا. ادمجهما في قائمة واحدة مرتبة دون sorted، واطبع أعلى 3 درجات.
- دالة merge تدمج قائمتين مرتبتين.
- اطبع القائمة المدمجة.
- اطبع: أعلى ثلاث:
[95, 97, 99]
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
a = [55, 68, 74, 90, 97]b = [61, 70, 82, 95, 99]
💡 تلميح 1
left[i] و right[j] وخذ الأصغر.💡 تلميح 2
[-3:]حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
a = [55, 68, 74, 90, 97]b = [61, 70, 82, 95, 99] def merge(left, right): result = [] i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]); i += 1 else: result.append(right[j]); j += 1 return result + left[i:] + right[j:] all_marks = merge(a, b)print(all_marks)print("أعلى ثلاث:", all_marks[-3:])