طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 101 · تركيب البيانات والخوارزميات
الدرس 7 · الوحدة 3: تصميم وتنفيذ خوارزميات فعّالة

الترتيب بالدمج والترتيب السريع

خوارزميتان من فرّق تسُد ترتّبان مليون عنصر في لحظات. ⏱ نحو 35 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح الترتيب بالدمج وتنفّذه بالعودية.
  • تدمج قائمتين مرتبتين في قائمة مرتبة.
  • تشرح الترتيب السريع وفكرة المحور.
  • تقارن بين الدمج والسريع في الأداء والذاكرة والحالة الأسوأ.

١الترتيب بالدمج Merge Sort

تطبيق مباشر لفرّق تسُد:

  1. فرّق: اقسم القائمة نصفين.
  2. سُد: رتّب كل نصف بالطريقة نفسها (عوديًّا)، حتى تصل إلى قوائم من عنصر واحد، وهي مرتبة أصلًا.
  3. اجمع: ادمج النصفين المرتبين في قائمة واحدة مرتبة.
🎬 محاكاة: الترتيب بالدمج

مقارنةتبديل أو نقلالمحور أو الوسطفي مكانه النهائي أو وُجد

٢الدمج: قلب الخوارزمية

دمج قائمتين مرتبتين سهل: قارن أول عنصر في كل منهما، وخذ الأصغر، وكرر:

main.py
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]))
Shell×
>>> %Run main.py
[3, 9, 10, 15, 27, 38, 43, 82]
>>>
main.py
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]))
Shell×
>>> %Run main.py
[3, 9, 10, 15, 27, 38, 43, 82]
>>>

٣الترتيب السريع Quick Sort

فكرة أخرى لفرّق تسُد: اختر عنصرًا اسمه المحور Pivot، وضع كل الأصغر منه يساره وكل الأكبر يمينه. صار المحور في مكانه النهائي! ثم كرر في كل جهة:

🎬 محاكاة: الترتيب السريع

مقارنةتبديل أو نقلالمحور أو الوسطفي مكانه النهائي أو وُجد
main.py
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]))
Shell×
>>> %Run main.py
[3, 9, 10, 15, 27, 38, 43, 82]
>>>

هذه نسخة واضحة للفهم تنشئ قوائم جديدة. والنسخة الاحترافية (كما في المحاكاة) تبدّل العناصر داخل القائمة نفسها دون نسخ.

٤المقارنة بينهما

الدمجالسريع
أين العمل الأكبر؟في الدمج (بعد التقسيم)في التقسيم حول المحور (قبل العودية)
الأداء المعتادسريع: نحو n log nسريع: نحو n log n، وغالبًا أسرع عمليًّا
الحالة الأسوأثابت دائمًا: n log nبطيء n² إن كان المحور دائمًا الأصغر أو الأكبر (قائمة مرتبة مع محور أخير)
الذاكرةيحتاج قائمة إضافية بحجم nيعمل داخل القائمة نفسها تقريبًا
الثبات Stableنعم: المتساويان يبقيان بترتيبهمالا (في نسخته المعتادة)

نعدّ المقارنات على قائمة عشوائية وعلى قائمة مرتبة، لنرى الحالة الأسوأ للسريع:

main.py
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)
Shell×
>>> %Run main.py
عشوائية: 5040
مرتبة: 124750
>>>

على القائمة المرتبة يكون المحور (الأخير) أكبر عنصر دائمًا، فلا يقسم شيئًا. لذلك تختار النسخ الاحترافية المحور عشوائيًّا أو من الوسط. أما sorted() في بايثون فتستخدم خوارزمية Timsort المبنية على الدمج والإدراج، وهي سريعة في كل الحالات.

توقّع وتحقّق

في الترتيب بالدمج، العمل الأكبر يحدث في:

Combine.

بعد التقسيم حول المحور يكون المحور:

يساره أصغر ويمينه أكبر.

الحالة الأسوأ للسريع بمحور أخير:

المحور لا يقسم.

أي خوارزمية تحتاج ذاكرة إضافية بحجم القائمة؟

قائمة الدمج.

الخلاصة

تحدٍّ برمجي: دمج قائمتي درجات

متوسط

لديك درجات شعبتين، كل منهما مرتبة تصاعديًّا. ادمجهما في قائمة واحدة مرتبة دون sorted، واطبع أعلى 3 درجات.

المطلوب:
  • دالة merge تدمج قائمتين مرتبتين.
  • اطبع القائمة المدمجة.
  • اطبع: أعلى ثلاث: [95, 97, 99]

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
a = [55, 68, 74, 90, 97]b = [61, 70, 82, 95, 99]
المطلوب×
[55, 61, 68, 70, 74, 82, 90, 95, 97, 99]
أعلى ثلاث: [95, 97, 99]
💡 تلميح 1
قارن left[i] و right[j] وخذ الأصغر.
💡 تلميح 2
أعلى ثلاث في قائمة مرتبة تصاعديًّا: آخر ثلاثة [-3:]

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
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:])
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس