🚀 الترتيب السريع

دليل دراسة شامل

CS210 - المادة الأسبوع السابع

1. المقدمة والتاريخ

🏆 حقائق سريعة

  • المخترع: توني هور (1960)
  • الجائزة: حاصل على جائزة تورينج عام 1980
  • التقدير: واحدة من أفضل 10 خوارزميات في القرن العشرين في العلوم والهندسة
  • الغرض الأصلي: طُوِّر لترجمة الروسية إلى الإنجليزية

القصة وراء الترتيب السريع

اخترع توني هور الترتيب السريع بقصة مثيرة للاهتمام:

  • أنشأها في البداية لحل مشكلة ترجمة لكن لم يستطع شرحها أو تنفيذها
  • تعلم Algol 60 ومفهوم التكرار
  • نفّذ الترتيب السريع بنجاح باستخدام تقنيات التكرار
  • نشر الخوارزمية في Communications of the ACM (يوليو 1961)
هناك طريقتان لتصميم البرمجيات: إحداهما هي جعلها بسيطة جدًا بحيث لا توجد أوجه قصور واضحة، والأخرى هي جعلها معقدة جدًا بحيث لا توجد أوجه قصور واضحة. الطريقة الأولى أصعب بكثير.

— توني هور

لماذا يهم الترتيب السريع

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

2. كيف يعمل الترتيب السريع

🎯 الفكرة الأساسية: يستخدم الترتيب السريع استراتيجية "فرّق تسد" بتقسيم المصفوفة حول عنصر محوري، ثم ترتيب المصفوفات الفرعية بشكل متكرر.

الخطة الأساسية

الخطوة 1: خلط المصفوفة (لضمان الأداء)
الخطوة 2: تقسيم المصفوفة بحيث يكون لبعض الفهرس j:
  • العنصر a[j] في مكانه النهائي
  • لا توجد عناصر أكبر على يسار j
  • لا توجد عناصر أصغر على يمين j
الخطوة 3: ترتيب كل مصفوفة فرعية بشكل متكرر

مثال مرئي

المصفوفة الأولية:

Q
U
I
C
K
S
O
R
T
E
X
A
M
P
L
E

بعد الخلط:

K
R
A
T
E
L
E
P
U
I
M
Q
C
X
O
S

بعد أول تقسيم (K في مكانه):

E
C
A
I
E
K
L
P
U
T
M
Q
R
X
O
S

■ أصغر من المحور | ■ المحور | ■ أكبر من المحور

3. خوارزمية التقسيم

كيف يعمل التقسيم

الهدف: إعادة ترتيب المصفوفة بحيث يكون العنصر المحوري في موضعه الصحيح، مع عناصر أصغر على يساره وعناصر أكبر على يمينه.

خطوات التقسيم:

  1. تهيئة المؤشرات: ضع i للإشارة مباشرة بعد أقصى اليسار، وj للإشارة إلى أقصى اليمين
  2. المسح من اليسار: زيادة i حتى إيجاد عنصر ≥ المحور
  3. المسح من اليمين: إنقاص j حتى إيجاد عنصر ≤ المحور
  4. التبادل: تبادل العناصر في الموضعين i وj
  5. التكرار: استمر في الخطوات 2-4 حتى يتقاطع المؤشران
  6. التبادل النهائي: تبادل المحور مع العنصر في الموضع j

⚠️ تفاصيل تنفيذ مهمة

  • التقسيم في نفس المكان: لا تستخدم مصفوفة إضافية (توفر ذاكرة لكن تضحي بالاستقرار)
  • إنهاء الحلقة: فحص تقاطع المؤشرات يتطلب تنفيذًا دقيقًا
  • المفاتيح المتساوية: من المثير للدهشة أنه من الأفضل التوقف عند المسح عند مفاتيح تساوي المحور
  • الحفاظ على العشوائية: الخلط ضروري لضمان الأداء

4. التنفيذ بلغة جافا

تنفيذ الترتيب السريع الكامل

public class Quick {
    
    // طريقة الترتيب الرئيسية
    public static void sort(Comparable[] a) {
        StdRandom.shuffle(a);  // الخلط لضمان الأداء
        sort(a, 0, a.length - 1);
    }
    
    // طريقة الترتيب المتكررة
    private static void sort(Comparable[] a, int lo, int hi) {
        if (hi <= lo) return;
        int j = partition(a, lo, hi);
        sort(a, lo, j-1);      // ترتيب المصفوفة الفرعية اليسرى
        sort(a, j+1, hi);      // ترتيب المصفوفة الفرعية اليمنى
    }
    
    // طريقة التقسيم
    private static int partition(Comparable[] a, int lo, int hi) {
        int i = lo, j = hi + 1;
        
        while (true) {
            // إيجاد عنصر على اليسار للتبادل
            while (less(a[++i], a[lo]))
                if (i == hi) break;
            
            // إيجاد عنصر على اليمين للتبادل
            while (less(a[lo], a[--j]))
                if (j == lo) break;
            
            // التحقق إذا تقاطع المؤشران
            if (i >= j) break;
            
            // التبادل
            exch(a, i, j);
        }
        
        // التبادل مع عنصر التقسيم
        exch(a, lo, j);
        
        // إرجاع فهرس العنصر المعروف الآن أنه في مكانه
        return j;
    }
    
    // طرق مساعدة
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
    
    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

✅ نقاط تنفيذ رئيسية

  • خطوة shuffle حرجة لضمان الأداء
  • استخدم معاملات البادئة ++i و--j في الحلقات while
  • المحور يكون في البداية في الموضع lo
  • بعد التقسيم، ينتقل المحور إلى الموضع j

5. تحليل الأداء

التعقيد الزمني

الحالة المقارنات الوصف
أفضل حالة ~ N lg N تقسيمات متوازنة تمامًا كل مرة
متوسط الحالة ~ 2N ln N ≈ 1.39 N lg N مدخلات عشوائية (مُتوقَّع مع الخلط)
أسوأ حالة ~ ½ N² مرتب مسبقًا أو مرتب عكسيًا (بدون خلط)
🎲 الترتيب السريع هو خوارزمية عشوائية من نوع لاس فيغاس: مضمون أن يكون صحيحًا، مع وقت تشغيل يعتمد على الخلط العشوائي.

التحليل الرياضي

الافتراض: متوسط عدد المقارنات CN لترتيب مصفوفة تحتوي على N مفاتيح متميزة هو ~ 2N ln N (وعدد التبادلات هو ~ ⅓ N ln N).

لماذا؟

  • كل خطوة تقسيم تقوم بـ N مقارنة
  • في المتوسط، التقسيمات متوازنة بشكل معقول
  • ينشئ شجرة تكرار بعمق ~ lg N
  • العمل الكلي: N مقارنة × lg N مستوى ≈ N lg N

أوقات التشغيل التجريبية

الخوارزمية 1K عنصر 1M عنصر 1B عنصر
ترتيب الإدراج لحظي 2.8 ساعة 317 سنة
ترتيب الدمج لحظي 1 ثانية 18 دقيقة
الترتيب السريع لحظي 0.6 ثانية 12 دقيقة

📊 رؤى أداء رئيسية

  • 39% مقارنات أكثر من ترتيب الدمج في المتوسط
  • أسرع عمليًا بسبب حركة بيانات أقل
  • في نفس المكان: يستخدم فقط مساحة إضافية لوغاريتمية للتكرار
  • كفاءة الذاكرة المخبئية: توطين جيد للمرجعيات

⚠️ سيناريو أسوأ حالة

بدون خلط، يتدهور الترتيب السريع إلى O(N²) على المدخلات المرتبة أو المرتبة عكسيًا. ومع ذلك، مع الخلط المناسب، فإن احتمالية حدوث أسوأ سلوك أقل احتمالًا من أن تصيب صاعقة برق حاسوبك أثناء التنفيذ!

6. التعامل مع المفاتيح المكررة

🔑 لماذا تهم المفاتيح المكررة

في التطبيقات الواقعية، غالبًا ما تحتوي المصفوفات على العديد من المفاتيح المكررة:

  • ترتيب السكان حسب العمر
  • إزالة التكرارات من قوائم البريد
  • ترتيب المتقدمين للوظائف حسب الكلية

خصائص نموذجية: مصفوفات ضخمة مع عدد صغير من قيم المفاتيح المميزة.

مشكلة المفاتيح المكررة

❌ خطأ: لا تتوقف عند المفاتيح المتساوية

النتيجة: ~ ½ N² مقارنة عندما تكون جميع المفاتيح متساوية

بعض تنفيذات الكتب المدرسية والتجارية ترتكب هذا الخطأ وتصبح تربيعية مع العديد من المفاتيح المكررة!

✅ صحيح: توقف عند المفاتيح المتساوية

النتيجة: ~ N lg N مقارنة عندما تكون جميع المفاتيح متساوية

هذا هو النهج الموصى به الذي يحافظ على أداء جيد.

مقارنة مرئية

السيناريو توقف عند المتساوي لا تتوقف عند المتساوي
جميع المفاتيح متساوية ~ N lg N ~ ½ N²
تكريرات كثيرة فعال تدهور تربيعي
جميعها متميزة ~ 2N ln N ~ 2N ln N

⚠️ تحذير

تحقق دائمًا من أن تنفيذ الترتيب السريع الخاص بك يتوقف عند المفاتيح المتساوية. هذا مهم خاصة عند استخدام تنفيذات المكتبات أو الكود من الكتب المدرسية.

7. الخصائص والمميزات

الترتيب في نفس المكان

الافتراض: الترتيب السريع هو خوارزمية ترتيب في نفس المكان.

الدليل:

  • التقسيم يستخدم مساحة إضافية ثابتة
  • عمق التكرار لوغاريتمي باحتمالية عالية
  • يمكن ضمان عمق لوغاريتمي بالتكرار أولاً على المصفوفة الفرعية الأصغر

الاستقرار

الافتراض: الترتيب السريع ليس مستقرًا.

مثال مضاد:

الأولي:  B₁ C₁ C₂ A₁
بعد:    A₁ B₁ C₂ C₁  ← تبادل ترتيب C₂ و C₁!

التبادلات بعيدة المدى أثناء التقسيم يمكن أن تغير الترتيب النسبي للمفاتيح المتساوية.

ملخص الخصائص

✅ المزايا

  • سريع عمليًا (أسرع خوارزمية ترتيب للأغراض العامة)
  • في نفس المكان (حد أدنى من النفقات الإضافية للذاكرة)
  • ضمان احتمالي لـ N lg N
  • كفاءة الذاكرة المخبئية
  • سهل التنفيذ

❌ العيوب

  • ليس مستقرًا
  • هش (يتطلب تنفيذًا دقيقًا)
  • تربيعي في أسوأ حالة (نادر مع الخلط)
  • أداء ضعيف على البيانات شبه المرتبة (بدون خلط)

8. المقارنة مع خوارزميات الترتيب الأخرى

الخوارزمية في نفس المكان؟ مستقر؟ أفضل متوسط أسوأ ملاحظات
ترتيب الاختيار ½ N² ½ N² ½ N² N تبادل
ترتيب الإدراج N ¼ N² ½ N² استخدم لـ N صغيرة
ترتيب شيل N log N ؟ c N^(3/2) كود مضغوط
ترتيب الدمج ½ N lg N N lg N N lg N ضمان N lg N
Timsort N N lg N N lg N يستغل الترتيب الجزئي
الترتيب السريع N lg N 2 N ln N ½ N² الأسرع عمليًا
🏆 الترتيب السريع: الفائز الواضح للترتيب للأغراض العامة عمليًا، على الرغم من هزيمته من قبل ترتيب الدمج في ضمانات أسوأ حالة نظرية.

لماذا الترتيب السريع أسرع عمليًا

  1. حركة بيانات أقل: يقوم الترتيب السريع عادةً بحوالي ⅓ N ln N تبادل مقابل N lg N وصول للمصفوفة في ترتيب الدمج
  2. في نفس المكان: لا توجد تخصيصات مصفوفة مساعدة ونسخ
  3. ملاءمة للذاكرة المخبئية: توطين أفضل للمرجعيات
  4. كفاءة الحلقة الداخلية: عمليات أبسط في المسار الحرج

9. التطبيقات العملية وترتيبات النظام

ترتيب النظام في جافا

Arrays.sort() في جافا 7+

  • طريقة للكائنات التي تنفذ Comparable
  • طرق مُحَمَّلة للأنواع البدائية
  • طريقة محمَّلة مع Comparator
  • طرق لترتيب مصفوفات فرعية

الخوارزميات المستخدمة:

نوع البيانات الخوارزمية السبب
الأنواع البدائية الترتيب السريع ثنائي المحور السرعة (الاستقرار غير مطلوب)
أنواع المرجع Timsort الاستقرار مهم للكائنات

🤔 سؤال تصميم

س: لماذا استخدام خوارزميات مختلفة للأنواع البدائية وأنواع المرجع؟

ج: الاستقرار مهم للكائنات (للحفاظ على ترتيب العناصر المتساوية بناءً على حقول أخرى)، لكن للأنواع البدائية، القيم المتساوية غير قابلة للتمييز، لذا فالاستقرار غير ذي صلة. ميزة السرعة للترتيب السريع تجعله الخيار الأفضل للأنواع البدائية.

أداء العالم الحقيقي: الدروس المستفادة

الدرس 1: الخوارزميات الجيدة أفضل من الحواسيب الخارقة

حاسوب منزلي يشغّل الترتيب السريع على 1 مليار عنصر: 12 دقيقة

حاسوب خارق يشغّل ترتيب الإدراج على 1 مليار عنصر: أسبوع

الدرس 2: الخوارزميات الرائعة أفضل من الخوارزميات الجيدة

الفرق بين خوارزميات N² و N log N يمكن أن يعني الفرق بين ساعات/أيام وثواني/دقائق للمجموعات الكبيرة.

متى تستخدم الترتيب السريع

✅ استخدم الترتيب السريع عندما:

  • تحتاج إلى أسرع أداء لحالة متوسطة
  • الذاكرة محدودة (يحتاج ترتيب في نفس المكان)
  • الاستقرار غير مطلوب
  • البيانات مرتبة عشوائيًا أو يمكن خلطها
  • ترتيب الأنواع البدائية

❌ تجنب الترتيب السريع عندما:

  • الاستقرار مطلوب (استخدم ترتيب الدمج أو Timsort)
  • ضمان أسوأ حالة حرج (استخدم ترتيب الدمج)
  • المدخلات مرتبة مسبقًا تقريبًا (ما لم تخلط أولاً)
  • لا يمكنك تحمل أسوأ حالة تربيعية نادرة

🎓 النقاط الرئيسية

1. الترتيب السريع هو خوارزمية فرّق تسد تقسم حول عنصر محوري
2. الحالة المتوسطة: ~ 1.39 N lg N مقارنة (39% أكثر من ترتيب الدمج لكن أسرع عمليًا)
3. الخلط ضروري لضمان الأداء
4. يجب التوقف عند المفاتيح المتساوية لتجنب السلوك التربيعي مع التكرارات
5. في نفس المكان (مساحة إضافية لوغاريتمية) لكن ليس مستقرًا
6. أسرع خوارزمية ترتيب للأغراض العامة عمليًا
7. مستخدم في Arrays.sort() في جافا للأنواع البدائية
أسميها خطأ المليار دولار. كانت اختراع المرجع الفارغ في 1965… أدى هذا إلى أخطاء لا تحصى، وثغرات، وتعطلات النظام، التي سببت على الأرجح مليار دولار من الألم والضرر في الأربعين سنة الماضية.

— توني هور
(حتى أعظم علماء الحاسوب يرتكبون أخطاء، لكن الترتيب السريع لم يكن واحدًا منها!)

📝 نصائح الدراسة

لإتقان الترتيب السريع:

  1. افهم عملية التقسيم - هذا هو قلب الترتيب السريع
  2. تدرب على التنفيذ - اكتبه من الصفر عدة مرات
  3. تتبع الأمثلة - ارسم المصفوفة في كل خطوة
  4. افهم التحليل - اعرف لماذا هو N lg N في المتوسط
  5. اعرف الحالات الطرفية - المفاتيح المكررة، البيانات المرتبة مسبقًا
  6. قارن مع خوارزميات ترتيب أخرى - افهم المقايضات

أسئلة امتحان شائعة:

  • اعرض محتويات مصفوفة بعد التقسيم
  • ما هو أسوأ مدخل للترتيب السريع؟
  • لماذا الخلط ضروري؟
  • ماذا يحدث مع جميع المفاتيح المكررة؟
  • هل الترتيب السريع مستقر؟ أثبت أو أعط مثال مضاد
  • قارن بين الترتيب السريع وترتيب الدمج