المحاضرة 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)
- باستخدام البحث الخطّي، البحث عن عنصر هو .
- باستخدام البحث الثنائي، يمكننا البحث عن عنصر في — بافتراض أن القائمة مرتّبة!
- متى يكون من المعقول أن نرتب أولًا ثم نبحث؟
«متى يكون الترتيب أصغر من ؟!؟ هذا لا يحدث أبدًا!»
الشريحة 3: التكلفة المُطفَّرة (Amortized Cost) — حيث n هو len(L)
- لماذا نرتّب أولًا إذن؟
- رتّب القائمة مرّة واحدة ثم أجرِ عمليات بحث كثيرة.
- نُطفئ (amortize) كلفة الترتيب على عمليات البحث الكثيرة.
- ، أي إن كان
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)
- أفضل حالة: حيث هو
len(L)، للتحقّق من كون القائمة مرتّبة. - أسوأ حالة: — فهي غير محدودة (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) حتى تصير المبادلات معدومة. - حيث هو
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]
- تعقيد الترتيب بالاختيار هو حيث هو
len(L). - الحلقة الخارجية تُنفَّذ
len(L)مرّة. - الحلقة الداخلية تُنفَّذ
len(L) – iمرّة، أيlen(L)/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: عرض توضيحي للترتيب بالدمج
- قسّم المسألة إلى مشكلات فرعية بالاستدعاء الذاتي.
- رتّب كل مسألة فرعية باستخدام دمج خطّي.
- ادمج المشكلات الفرعية (المرتّبة) في قائمة المُخرج.
الشريحة 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: تعقيد خطوة الدمج
- نمرّ على قائمتين في تمريرة واحدة فقط.
- نقارن أصغر عنصر في كل قائمة فرعية فقط.
- عنصرًا يُنسخ.
- أسوأ حالة: مقارنة.
- خطّي في طول القوائم.
الشريحة 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: تعقيد الترتيب بالدمج
كل مستوى:
- عند مستوى الاستدعاء الذاتي الأول:
- عنصرًا في كل قائمة، وقائمتان.
- دمج واحد حيث هو
len(L).
- عند مستوى الاستدعاء الذاتي الثاني:
- عنصرًا في كل قائمة، وأربع قوائم.
- دمجان حيث هو
len(L).
- وهكذا…
عدد المستويات:
- تقسيم القائمة إلى نصفين عند كل استدعاء ذاتي يعطينا مستوياتنا:
- حيث هو
len(L). - مثل البحث بالتنصيف: تخبرنا بعدد التقسيمات المطلوبة للوصول إلى عنصر واحد.
- حيث هو
الإجمالي: كل مستوى استدعاء ذاتي يؤدّي من العمل، وعدد المستويات ، حيث هو len(L). إذن التعقيد الإجمالي هو:
الشريحة 25: خلاصة خوارزميات الترتيب — حيث n هو len(L)
| الخوارزمية | التعقيد | ملاحظة |
|---|---|---|
| الترتيب العشوائي (Bogo sort) | غير محدود | عشوائية |
| الترتيب الفقاعي (Bubble sort) | ||
| الترتيب بالاختيار (Selection sort) | يضمن أنّ أول i عناصر كانت مرتّبة |
|
| الترتيب بالدمج (Merge sort) |
هو أسرع ما يمكن أن يكون عليه أي ترتيب.
الشريحة 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.