مقدمة في علوم الحاسوب والبرمجة باستخدام Python › المحاضرة 24: خوارزميات الترتيب (Sorting Algorithms) 26 / 30 السابق التالي

المحاضرة 24: خوارزميات الترتيب (Sorting Algorithms)

المحاضرة 24: خوارزميات الترتيب (Sorting Algorithms)

المصادر والنسبة والترخيص

Ana Bell. 6.100L Introduction to CS and Programming using Python, Fall 2022. Massachusetts Institute of Technology: MIT OpenCourseWare, https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/. License: CC BY-NC-SA 4.0.

  • صفحة المحاضرة على MIT OpenCourseWare: https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/pages/lecture-24-sorting-algorithms/
  • ملف الشرائح (صفحة الوصف): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec24_pdf/
  • ملف الشرائح (PDF مباشر): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/mit6_100l_f22_lec24.pdf
  • ملف الشيفرة: https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec24_code_py/
  • تفريغ المحاضرة على MIT OpenCourseWare (بالإنجليزية): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec24/
  • الترخيص: CC BY-NC-SA 4.0 — https://creativecommons.org/licenses/by-nc-sa/4.0/
  • شروط الاستخدام في MIT OpenCourseWare: https://ocw.mit.edu/terms/

منهج الترجمة: نُقلت هذه المحاضرة ترجمةً عربية كاملة أمينة لمحتوى ملف الشرائح الأصلي في MIT OpenCourseWare، تحت رخصة CC BY-NC-SA 4.0 التي تسمح بإعادة التوزيع مع الإبقاء على الإسناد والتأهيل والإشارة إلى التغيير، وبشرط أن يكون الاستخدام غير تجاري وأن تُوزَّع كل ترجمة مشتقّة تحت الرخصة نفسها. أُبقيت الشيفرة الخوارزمية والشيفرة الكاذبة (pseudocode) كما هي بالإنجليزية، ولم تُترجَم الأسماء والمكتبة ولا الكلمات المفتاحية في Python. لكل شريحة من الشرائح السبع والعشرين عنوانها الخاص. لم تُضمَّن الرسوم التوضيحية لأن حقوقها مستقلّة عن رخصة MIT؛ وقد أَوردت الشرائحُ 5 و7 و9 إشعار حقوقٍ صريحًا بصيغة «هذا المحتوى مستثنى من رخصة المشتقّ الإبداعي الخاصة بنا»، وقد نُقل نصُّ الإشعار هنا حرفيًّا مع بيان عدم إعادة نشر الصور.

ملاحظة المترجم: تحمل تذييل شرائح هذه المحاضرة سطرًا متبقّيًا من عرض أقدم (6.0001 LECTURE 9) استُخدمت الشرائح فيه كقالب. هذا أثرٌ موجود في ملف MIT الأصلي، وليس إشارة إلى مقرر آخر.

الشريحة 1: خوارزميات الترتيب (Sorting Algorithms)

هذه المحاضرة عن خوارزميات الترتيب. يُرفق ملف الشرائح وملفات .py للمتابعة الجانبيًا.

الشريحة 2: البحث في قائمة مرتّبة — حيث n هو len(L)

  • باستخدام البحث الخطّي، البحث عن عنصر هو Θ(n)\Theta(n).
  • باستخدام البحث الثنائي، يمكننا البحث عن عنصر في Θ(log⁡n)\Theta(\log n) — بافتراض أن القائمة مرتّبة!
  • متى يكون من المعقول أن نرتب أولًا ثم نبحث؟
SORT+Θ(log⁡n)<Θ(n)SORT + \Theta(\log n) < \Theta(n) ⇒SORT<Θ(n)−Θ(log⁡n)\Rightarrow SORT < \Theta(n) - \Theta(\log n)

«متى يكون الترتيب أصغر من Θ(n)\Theta(n)؟!؟ هذا لا يحدث أبدًا!»

الشريحة 3: التكلفة المُطفَّرة (Amortized Cost) — حيث n هو len(L)

  • لماذا نرتّب أولًا إذن؟
  • رتّب القائمة مرّة واحدة ثم أجرِ عمليات بحث كثيرة.
  • نُطفئ (amortize) كلفة الترتيب على عمليات البحث الكثيرة.
  • SORT+K∗Θ(log⁡n)<K∗Θ(n)SORT + K * \Theta(\log n) < K * \Theta(n)، أي إن كان K كبيرًا فإن زمن الترتيب يصبح غير ذي أثر.

الشريحة 4: خوارزميات الترتيب

شريحة فاصلة.

الشريحة 5: الترتيب العشوائي (Bogo / Random / Monkey Sort)

  • تُعرف أيضًا بـ: bogosort، stupidsort، slowsort، randomsort، shotgunsort.
  • لترتيب مجموعة ورق (deck of cards):
    • ارمِها في الهواء.
    • اجمعها.
    • هل هي مرتّبة؟
    • كرّر إن لم تكن مرتّبة.

إشعار حقوق في الشريحة الأصلية (نُقل نصًّا): © Nmnogueira at English Wikipedia. License: CC-BY-SA. All rights reserved. This content is excluded from our Creative Commons license. For more information, see https://ocw.mit.edu/help/faq-fair-use/

ملاحظة المترجم: صور Wikimedia هذه مرخّصة بـ CC BY-SA وهي مستثناة صراحةً من رخصة MIT OpenCourseWare، لذلك لم تُعَد نشرها هنا. الوصف النصّي أعلاه كافٍ لفهم الخوارزمية.

الشريحة 6: تعقيد الترتيب العشوائي (Bogo Sort)

def bogo_sort(L):
    while not is_sorted(L):
        random.shuffle(L)
  • أفضل حالة: Θ(n)\Theta(n) حيث nn هو len(L)، للتحقّق من كون القائمة مرتّبة.
  • أسوأ حالة: Θ(?)\Theta(?) — فهي غير محدودة (unbounded) إن كانت الحظّ سيّئًا جدًّا.

الشريحة 7: الترتيب الفقاعي (Bubble Sort)

  • قارن أزواجًا متتالية من العناصر.
  • بدّل العنصرين في كل زوج بحيث يكون الأصغر أولًا.
  • عند بلوغ نهاية القائمة، ابدأ من جديد.
  • توقّف عندما لا تُجرى أي مبادلات (swaps) بعد اليوم.

إشعار حقوق في الشريحة الأصلية (نُقل نصًّا): © Nmnogueira at English Wikipedia. License: CC-BY-SA. All rights reserved. This content is excluded from our Creative Commons license. For more information, see https://ocw.mit.edu/help/faq-fair-use/

quoting Donald Knuth في كتابه «The Art of Computer Programming»:

«يبدو للترتيب الفقاعي (bubble sort) أنه لا شيء يروّج له، إلا اسمًا سهل التلقّي وحقيقة أنه يقود إلى بعض المسائل النظرية المثيرة».

الشريحة 8: تعقيد الترتيب الفقاعي

def bubble_sort(L):
    did_swap = True
    while did_swap:
        did_swap = False
        for j in range(1, len(L)):
            if L[j-1] > L[j]:
                did_swap = True
                L[j],L[j-1] = L[j-1],L[j]
  • الحلقة for الداخلية تُجري المقارنات.
  • الحلقة while الخارجية تُجري عدّة تمريرات (passes) حتى تصير المبادلات معدومة.
  • Θ(n2)\Theta(n^2) حيث nn هو len(L)، يلزم len(L)-1 مقارنة و len(L)-1 تمريرة.

الشريحة 9: الترتيب بالاختيار (Selection Sort)

الخطوة الأولى:

  • استخرج العنصر الأصغر (minimum element).
  • بدّله مع العنصر الموجود عند الفهرس 0.

الخطوة الثانية:

  • في القائمة الفرعية المتبقّية، استخرج العنصر الأصغر.
  • بدّله مع العنصر الموجود عند الفهرس 1.

احتفظ بالجزء الأيسر من القائمة مرتّبًا:

  • عند الخطوة i، تكون أول i عناصر في القائمة مرتّبة، وكل العناصر الأخرى أكبر من هذه العناصر الأولى.

إشعار حقوق في الشريحة الأصلية (نُقل نصًّا): © Nmnogueira at English Wikipedia. License: CC-BY-SA. All rights reserved. This content is excluded from our Creative Commons license. For more information, see https://ocw.mit.edu/help/faq-fair-use/

الشريحة 10: تعقيد الترتيب بالاختيار

def selection_sort(L):
    for i in range(len(L)):
        for j in range(i, len(L)):
            if L[j] < L[i]:
                L[i], L[j] = L[j], L[i]
  • تعقيد الترتيب بالاختيار هو Θ(n2)\Theta(n^2) حيث nn هو len(L).
  • الحلقة الخارجية تُنفَّذ len(L) مرّة.
  • الحلقة الداخلية تُنفَّذ len(L) – i مرّة، أي len(L)/2 في المتوسط.
  • ويمكن أيضًا التفكير في عدد مرات المقارنة عبر الحلقتين معًا: لنفرض n=len(L)n = len(L):
1+2+3+⋯+n=(n)(n+1)/2=n2/2+n/2=Θ(n2)1 + 2 + 3 + \dots + n = (n)(n+1)/2 = n^2/2 + n/2 = \Theta(n^2)

الشريحة 11: نسخة مختلفة من الترتيب بالاختيار: لا تبدّل في كل مرّة

شريحة فاصلة تعرض تغيّرًا بسيطًا في الخوارزمية.

الشريحة 12: الترتيب بالدمج (Merge Sort)

استخدِم منهج «اقسم وانتصر» (divide-and-conquer):

  • إذا كان طول القائمة 0 أو 1 فهي مرتّبة أصلًا.
  • إذا كان في القائمة أكثر من عنصر:
    • قسّمها إلى قائمتين، ورتّب كلًّا منهما.
    • ادمج القائمتين الفرعيتين المرتّبتين:
      • انظر إلى العنصر الأول من كلٍّ منهما، وانقل الأصغر إلى نهاية النتيجة.
      • عندما تفرغ إحدى القائمتين، انسخ بقيّة الأخرى.

الشريحة 13: الترتيب بالدمج — «اقسم وانتصر»

رسم شجري يقسّم القائمة إلى أنصاف متساوية حتى القوائم الفرعية من عنصر واحد فقط:

المرحلة القوائم
المستوى الأعلى unsorted واحدة
التقسيم الأول unsorted, unsorted
التقسيم التالي أربع قوائم unsorted
  • قسّم القائمة إلى نصفين حتى تحصل على قوائم فرعية من عنصر واحد فقط.

ملاحظة المترجم: رسم الشجرة في الأصل يحوي خطوطًا متّصلة وحوافّ لا تحفظها طبقة النصّ المستخرَجة؛ وقد صيغت الشجرة هنا على شكل جدول يبيّن التقسيم فقط.

الشريحة 14: الترتيب بالدمج — «اقسم وانتصر» بعد أول عمليات الفرز

نفس الشجرة، وقد صارت أربع من القوائم الفرعية sort (مرتّبة):

المرحلة القوائم
المستوى الأعلى unsorted
المستوى الثاني unsorted, unsorted
المستوى الثالث أربع قوائم، اثنتان منها sort واثنتان unsorted
المستوى الرابع sort و merge يتناوبان على الجانبين
  • ادمج بحيث تصير القوائم الفرعية مرتّبة بعد الدمج.

الشريحة 15: الترتيب بالدمج — «اقسم وانتصر» بعد الدمج الثاني

المرحلة القوائم
المستوى الأعلى unsorted
المستوى الثاني unsorted, sorted
المستوى الثالث ثلاث قوائم sorted و واحدة unsorted
المستوى الرابع sorted مع merge
  • ادمج القوائم الفرعية المرتّبة؛ set صارت القوائم الفرعية مرتّبة بعد الدمج.

ملاحظة المترجم: الرقم التسلسلي الظاهر في نسخة نصّ هذه الشريحة هو بقايا الرسم ولا يمثّل جزءًا من الخوارزمية.

الشريحة 16: الترتيب بالدمج — «اقسم وانتصر» بعد الدمج الثالث

المرحلة القوائم
المستوى الأعلى unsorted
المستوى الثاني sorted
المستوى الثالث sorted
  • ادمج القوائم الفرعية المرتّبة؛ set صارت القوائم الفرعية مرتّبة بعد الدمج.

الشريحة 17: الترتيب بالدمج — «اقسم وانتصر»: انتهى

المستوى الحالة
الجذر sorted

الشريحة 18: عرض توضيحي للترتيب بالدمج

  1. قسّم المسألة إلى مشكلات فرعية بالاستدعاء الذاتي.
  2. رتّب كل مسألة فرعية باستخدام دمج خطّي.
  3. ادمج المشكلات الفرعية (المرتّبة) في قائمة المُخرج.

الشريحة 19: نظرة أوسع على خطوة الدمج (مثال)

الباقي في القائمة ١ الباقي في القائمة ٢ المقارنة النتيجة
[1,5,12,18,19,20] [2,3,4,17] 1, 2 []
[5,12,18,19,20] [2,3,4,17] 5, 2 [1]
[5,12,18,19,20] [3,4,17] 5, 3 [1,2]
[5,12,18,19,20] [4,17] 5, 4 [1,2,3]
[5,12,18,19,20] [17] 5, 17 [1,2,3,4]
[12,18,19,20] [17] 12, 17 [1,2,3,4,5]
[18,19,20] [17] 18, 17 [1,2,3,4,5,12]
[18,19,20] [] 18, -- [1,2,3,4,5,12,17]
[] [] [1,2,3,4,5,12,17,18]
[1,2,3,4,5,12,17,18,19,20]

الشريحة 20: خطوة دمج القوائم الفرعية

def merge(left, right):
    result = []
    i,j = 0, 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
    while (i < len(left)):
        result.append(left[i])
        i += 1
    while (j < len(right)):
        result.append(right[j])
        j += 1
    return result

الشريحة 21: تعقيد خطوة الدمج

  • نمرّ على قائمتين في تمريرة واحدة فقط.
  • نقارن أصغر عنصر في كل قائمة فرعية فقط.
  • Θ(len(left)+len(right))\Theta(len(left) + len(right)) عنصرًا يُنسخ.
  • أسوأ حالة: Θ(len(longerlist))\Theta(len(longer list)) مقارنة.
  • خطّي في طول القوائم.

الشريحة 22: خوارزمية الترتيب بالدمج كاملة — بالاستدعاء الذاتي

def merge_sort(L):
    if len(L) < 2:
        return L[:]
    else:
        middle = len(L)//2
        left = merge_sort(L[:middle])
        right = merge_sort(L[middle:])
        return merge(left, right)
  • يقسم القائمة تباعًا إلى أنصاف.
  • يسير بعمق أولًا (depth-first) بحيث يخضع أصغر القطع في فرع واحد قبل الانتقال إلى قطع أكبر.

الشريحة 23: شجرة الاستدعاء الذاتي في الترتيب بالدمج

المدخل 84165920، والناتج 01245689. الشجرة المقروءة من الجذر إلى الأور:

84165920  (Merge → 01245689)
├── 8416 (Merge)
│   ├── 84 (Merge)
│   │   ├── 8 (base case)
│   │   └── 4 (base case)
│   └── 16 (Merge)
│       ├── 1 (base case)
│       └── 6 (base case)
└── 5920 (Merge)
    ├── 59 (Merge)
    │   ├── 5 (base case)
    │   └── 9 (base case)
    └── 20 (Merge)
        ├── 2 (base case)
        └── 0 (base case)

الشريحة 24: تعقيد الترتيب بالدمج

كل مستوى:

  • عند مستوى الاستدعاء الذاتي الأول:
    • n/2n/2 عنصرًا في كل قائمة، وقائمتان.
    • دمج واحد ⇒Θ(n)+Θ(n)=Θ(n)\Rightarrow \Theta(n) + \Theta(n) = \Theta(n) حيث nn هو len(L).
  • عند مستوى الاستدعاء الذاتي الثاني:
    • n/4n/4 عنصرًا في كل قائمة، وأربع قوائم.
    • دمجان ⇒Θ(n)\Rightarrow \Theta(n) حيث nn هو len(L).
  • وهكذا…

عدد المستويات:

  • تقسيم القائمة إلى نصفين عند كل استدعاء ذاتي يعطينا مستوياتنا:
    • Θ(log⁡n)\Theta(\log n) حيث nn هو len(L).
    • مثل البحث بالتنصيف: 1=n/2i1 = n/2^i تخبرنا بعدد التقسيمات المطلوبة للوصول إلى عنصر واحد.

الإجمالي: كل مستوى استدعاء ذاتي يؤدّي Θ(n)\Theta(n) من العمل، وعدد المستويات Θ(log⁡n)\Theta(\log n)، حيث nn هو len(L). إذن التعقيد الإجمالي هو:

Θ(nlog⁡n) حيث n=len(L)\Theta(n \log n) \text{ حيث } n = len(L)

الشريحة 25: خلاصة خوارزميات الترتيب — حيث n هو len(L)

الخوارزمية التعقيد ملاحظة
الترتيب العشوائي (Bogo sort) Θ(?)\Theta(?) غير محدود عشوائية
الترتيب الفقاعي (Bubble sort) Θ(n2)\Theta(n^2)
الترتيب بالاختيار (Selection sort) Θ(n2)\Theta(n^2) يضمن أنّ أول i عناصر كانت مرتّبة
الترتيب بالدمج (Merge sort) Θ(nlog⁡n)\Theta(n \log n)

Θ(nlog⁡n)\Theta(n \log n) هو أسرع ما يمكن أن يكون عليه أي ترتيب.

الشريحة 26: خلاصة التعقيد

  • قارن كفاءة الخوارزميات:
    • صِف الرتبة الحتمية (asymptotic order of growth) بالحرف ثيتا الكبير (Big Theta).
    • تحليل الحالة الأسوأ (worst case analysis).
    • رأينا أصناف تعقيد مختلفة:
      • ثابت (Constant)
      • لوغاريتمي (Log)
      • خطّي (Linear)
      • خطّي لوغاريتمي (Log linear)
      • متعدّد الحدود (Polynomial)
      • أُسّي (Exponential)
    • التقييم القبلي (a priori evaluation) — قبل كتابة الشيفرة أو تشغيلها:
      • يقيّم الخوارزمية بمعزل عن الآلة وعن التنفيذ.
      • يمنح رؤيةً مباشرة في تصميم الخوارزميات الفعّالة.

الشريحة 27: MIT OpenCourseWare

MITOpenCourseWare
https://ocw.mit.edu

6.100L Introduction to Computer Science and Programming Using Python
Fall 2022

For information about citing these materials or our Terms of Use, visit: https://ocw.mit.edu/terms.