بسم الله الرحمن الرحيم
الفرز السريع Quicksort
ü لماذا أسلوب الفرز السريع؟
ü التقسيم partitioning .
ü الاستدعاء الذاتي ( recursive ) من أجل الفرز السريع .
ü بعض طرق تعين محور التقسيم (pivot) للحصول على فرز أسرع .
ü الاستعانة بطرق فرز أخرى لتحسين أداء الفرز السريع.
لماذا أسلوب الفرز السريع ؟
الترتيب بأسلوب الفرز السريع يعتبر الأكثر استخداما من جميع خوارزميات الفرز الأخرى لأنها الأسرع وأقل استهلاكا للذاكرة .
إذ هي تتفوق على الفرز بأسلوب الفقاعة(bubble sort) و الفرز بأسلوب الإدخال(insertion sort) و الفرز بأسلوب الاختيار( selection sort )بالسرعة وتتميز عن الفرز بأسلوب الدمج (merge sort) بالسرعة وتوفيرها للذاكرة بالرغم من تفوق الأخير على نظرائه الثلاثة بالسرعة أيضا إلا انه يستهلك الذاكرة أكثر .
التقسيم partitioning :
يعتمد أسلوب Quicksort على تقسيم المقسم وتجزئة المجزأ لعدة مرات حتى تحصل على ترتيب للعناصر داخل القائمة تصاعديا أو تنازليا.
التقسيم هنا هو تقسيم قائمة إلى جزأين أصغر وأكبر حيث هناك يكون مايسمى محور التقسيم (pivot ) وعليها يتم تحديد أصغر أو أكبر لا يشترط أن تكون عدد العناصر في كلا القسمين متساوية أو مرتبة ترتيبا تصاعديا أو تنازليا بل ما هو مهم في مرحلة التقسيم هو جعل الأصغر من محور التقسيم في قسم والأكبر في قسم آخر.
مثال على ذلك تقسيم درجات الطلاب الحاصلين على أقل من 50 درجة والحاصلين على أكثر من 50 درجة حيث القيمة 50 هو محور التقسيم .
أحد أكثر الطرق نجاحا في التقسيم هي طريقة الأكبر يبدأ من اليمين باتجاه اليسار والأصغر يبدأ من اليسار باتجاه اليمين فلو كان لديك 5 عناصر من 0 إلى 4 الأكبر من محور التقسيم يبدأ من 4 ثم 3 ثم 2 …. , والأصغر يبدأ من 0 ثم 1 …. , حتى يلتقي مؤشر اليمين مع مؤشر اليسار هنا تنتهي عملية التقسيم .
مثال/ خمسة طلاب درجاتهم على التوالي 80 ثم 40 ثم 90 ثم 55 ثم 30 ومحور التقسيم 50 يتم التقسيم بالطريقة التالية …
اللون الأحمر هو الذي يتحرك من اليسار الى اليمين مع كل مرة يجد قيمة أصغر واللون الأخضر يتحرك من اليمين الى اليسار كلما وجد قيمة أكبر وسوف نبدأ باختبار القيم من اليمين الى اليسار …
القيمة 80 أكبر من 50 فلذلك نقلناها الى أقصى اليمين و العنصر فيأقصى اليمين أخذ مكانها وحركنا المؤشر الأخضر للعنصر التالي …
القيمة 30 أصغر من 50 فذلك لا حاجة لعملية استبدال أماكن فقط نقوم بتحريك المؤشر الأحمر خطوة إلى اليمين وهو مايعني أننا سوف نختبر القيمة 40 في الخطوة التالية …
40 أصغر من 50 فلذلك نقوم بتحريك اللون الأحمر خطوة إلى اليمين لأحظ أنه لا يمكن الاستمرار لأن اللونين الأخضر والأحمر التقيا مما يعني ان عملية التقسيم انتهت فلو افترضنا أن القيمة عند اللون الأحمر أصغر فإن هذا لن يؤثر على التقسيم في حال الاستمرار وان كانت أكبر فأيضا ذلك لن يؤثر على التقسيم في حال الاستمرار فكلما عالجنا الأمور بخطوات اقل كان ذلك أفضل …
لاحظ أن القيم أصغر من 50 تكون في اليسار والقيم الأكبر في اليمين .
ويمكن تمثيل الطريقة السابقة برمجيا بشفرة ++ c على الشكل التالي:-

leftCurrentيعبر عن اللون الأحمر و rightCurrent عن اللون لأخضر تتبع عمل الشيفرة على مثال درجات الطلاب , ترجع الدالة partitioning رقم العنصر الذي يبدأ فيه الفصل بين الأصغر والأكبر …
فلو قمنا بتجربة مثال درجات الطلاب كما في الدالةmain

نجد أن النتيجة تكون على الشكل التالي :-
والنتيجة مشابهة للتي حصلنا عليها يدويا …
الاستدعاء الذاتي ( recursive ) من أجل الفرز السريع :
كما ذكرنا سابقا يقوم الفرز السريع على تقسيم المقسم الى عدة مرات حتى نحصل على قائمة مرتبة تصاعديا أو تنازليا ويمكن تمثيل العملية هذه في c++ على الشكل التالي:-
لاحظ ان الوسيط الثالث وهو الذي يستقبل قيمة محور التقسيم pivot في الدالة partitioning لم نضع مكانها شيئ في الحقيقة نحن لانعرف ماذا نضع مكانها ؟؟ في حال أردت وضع متوسط أعلى وأدنى قيمة فإن السرعة أنتفت عن هذا المثال للحاجة الى عمليات أكثر من الإسلوب ذاته .
بعض طرق تعين محور التقسيم (pivot ) للحصول على فرز أسرع :
إن أفضل حل لتعيين محور التقسيم هو أن يكون من داخل القائمة قيمة وموقع ويفضل أن يكون العنصر الأول في يسار القائمة بحيث تعتمد الخطة الجديدة على أن يكون العنصر الأول في نهاية التقسيم في المنتصف بين الأكبر والأصغر وهذا من شأنه حل المشكلة السابقة ولكن ليس دائما دعنا الآن نعيد صياغة الشفرات السابقة لتناسب نمط الخطة الجديدة …

سوف يبدأ الاختبار من بعد أول عنصر (العنصر الثاني من اليسار) وأي عنصر أصغر منه يتم استبدال موقع المحور مع موقعه حتى يكون موقع محور التقسيم في النهاية الأمر بين الأصغر من والأكبر من وترجع الدالة في النهاية موقع محور التقسيم …
نفس المثال السابق:
سوف نبدأ الإختبار من القيمة 80 وهي الثانية من اليسار والقيمة 50 هي محور التقسيم وموقعه أول القائمة من اليسار.
|
30
|
55
|
90
|
40
|
80
|
50
|
|
80
|
55
|
90
|
40
|
30
|
50
|
|
80
|
55
|
90
|
40
|
50
|
30
|
|
80
|
55
|
90
|
50
|
40
|
30
|
|
80
|
90
|
55
|
50
|
40
|
30
|
لاحظ أن القيمة 50 تقع بين الأكبر من والأصغر من ويفيدنا هذا التقسيم في تقليص عدد الخطوات بحيث لا حاجة لأن ندخل المحور في عملية التقسيم مرة أخرى ولو طبقنا المثال السابق برمجيا فإننا نحصل على نفس الترتيب ..
إذا يمكن أيضا إعادة صياغة الدالة Quicksort بطريقة أخرى :

إلى الآن لم نصل إلى الطريقة الأمثل لتحديد المحور من الأفضل أن تكون القيمة في الوسط بين أعلى وأصغر قيمة ولكن كما قلنا سابقا إن هذا الأسلوب يستخدم لزيادة السرعة بهذا الأسلوب سوف تقل السرعة كثيرا…
فلذا خرج لنا أسلوب أسرع وأنجع من سابقه وهو متوسط ثلاثة median of three حيث نختار ثلاثة عناصر وهي أول القائمة وآخر القائمة وعنصر وسط القائمة ثم نقوم بعملية ترتيب لهم الأصغر في اليسار والأكبر في اليمين والقيمة التي أصغر من الأكبر وأكبر من الأصغر يستبدل مع ثاني عنصر من اليسار حيث يكون هو المحور لماذا؟؟
لقد قلصنا بهذه الطريقة عدد العناصر الخاضعة لعملية التقسيم حيث الأول والأخير لا يخضعان لعملية التقسيم لأنهم مقسمان سلفا أكبر وأصغر من المحور إذا سوف يكون المحور بعد استثناء الأطراف من التقسيم في الطرف الأيسر من القائمة حيث سوف يخضع لعملية التقسيم بطريقتنا السابقة …
مثال
|
30
|
55
|
90
|
40
|
80
|
|
90
|
55
|
40
|
80
|
30
|
القيمة 80 هي المحور و القيمتين 30 و 90 خارج عملية التقسيم …
وفي c++ نكتب الشفرة على الشكل التالي :

الاستعانة بطرق فرز أخرى لتحسين أداء الفرز السريع:
قبل أن نبدأ ببناء فئة تقوم بجميع تلك الوضائف يجب أنن ننوه لموضوع مهم جدا وهو أن الطريقة السابقة يفضل إستخدامها مع قوائم طويلة إن هذا الأسلوب لن يؤثر على قائمة من عنصرين ستجد انهما غير مرتبين فإذا ما واجهت قائمة من عنصرين فلا تستخدم هذه الطريقة ويفضل أن تحدد في حال كانت العناصر في القائمة 10 أو أقل أن تستخدم أسلوب insertion sort
أما الآن فالفئة التي سوف نبنيها تعتمد بالكامل على أسلوب الفرز السريع ونستثني منها حال وجد عنصرين فإننا نرتبهم بأسلوب يدوي(تبديل الأماكن فيما بينهم إن لم يكونوا مرتبين)
انقر هنا لتحميل ملفات الشفرة المصدرية
وفي أمان الله