Quick Sort
آخر تحديث
الترتيب السريع خوارزمية «فرّق تسُد» تُرتِّب حول «محور». تختار عنصرًا محوريًا، ثم تقسّم المصفوفة بحيث يأتي كل ما هو أصغر قبله وكل ما هو أكبر بعده - مما يثبّت المحور في موضعه النهائي المرتّب. ثم تُكرِّر على القسمين الأيسر والأيمن. يستخدم هذا التصور مخطط Lomuto مع العنصر الأخير كمحور. اضغط تشغيل لمشاهدة التقسيم ووضع المحور.
عادةً ما يكون الترتيب السريع أسرع ترتيب عام الغرض في الممارسة بفضل سلوك الذاكرة المخبئية الجيد والتقسيم في المكان، بمتوسط O(n log n). أسوأ حالاته O(n²) (مثل مصفوفة مرتّبة مسبقًا مع اختيار سيئ للمحور)، وهو ما تتجنّبه استراتيجيات المحور الجيدة مثل وسيط الثلاثة أو العشوائية.
التعقيد الزمني والمكاني
| الحالة | التعقيد | ملاحظات |
|---|---|---|
| أفضل حالة | O(n log n) | أقسام متوازنة |
| الحالة المتوسطة | O(n log n) | ترتيب عشوائي |
| أسوأ حالة | O(n²) | محاور غير متوازنة باستمرار |
| المساحة | O(log n) | مكدس العودية (تقسيم في المكان) |
| مستقر | لا | مبادلات التقسيم تعيد ترتيب العناصر المتساوية |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | اختر محورًا (هنا، العنصر الأخير من المدى). |
| 2 | التقسيم: انقل إلى يساره كل العناصر الأصغر من المحور. |
| 3 | بادل المحور إلى الحد الفاصل - أصبح الآن في موضعه النهائي. |
| 4 | طبّق الترتيب السريع عوديًا على القسم الأيسر. |
| 5 | طبّق الترتيب السريع عوديًا على القسم الأيمن. |
مثال محلول
ترتيب [5, 2, 4, 1] باستخدام مخطط Lomuto (العنصر الأخير كمحور):
| التمريرة | المصفوفة | الإجراء |
|---|---|---|
| البداية | [5, 2, 4, 1] | قسّم المدى كاملًا؛ المحور هو 1 (العنصر الأخير). |
| 1 | [1, 2, 4, 5] | لا شيء أصغر من 1، لذا بادل 1 إلى الفهرس 0؛ أصبح المحور 1 نهائيًا. كرّر يمينًا على [2, 4, 5]. |
| 2 | [1, 2, 4, 5] | قسّم [2, 4, 5] بالمحور 5؛ كلٌّ من 2 و4 أصغر، لذا يبقى 5 في النهاية ويصبح نهائيًا. كرّر يسارًا على [2, 4]. |
| 3 | [1, 2, 4, 5] | قسّم [2, 4] بالمحور 4؛ 2 أصغر، لذا يبقى 4 في مكانه ويصبح نهائيًا. 2 عنصر مفرد، لذا فهو مرتّب بالفعل. |
| انتهى | [1, 2, 4, 5] | كل محور مثبّت في مكانه؛ المصفوفة مرتّبة. |
متى تستخدم الترتيب السريع
| استخدمه عندما | تجنّبه عندما |
|---|---|
| تحتاج إلى ترتيب سريع وعام الغرض في الذاكرة بعوامل ثابتة صغيرة. | تحتاج إلى زمن O(n log n) مضمون في أسوأ حالة (استخدم ترتيب الكومة أو ترتيب الدمج). |
الذاكرة محدودة - التقسيم يتم في المكان ولا يحتاج سوى O(log n) من مساحة المكدس. | تحتاج إلى ترتيب مستقر يحافظ على ترتيب المفاتيح المتساوية. |
| تكون البيانات بترتيب عشوائي أو غير معروف وتستخدم محورًا عشوائيًا أو وسيط الثلاثة. | يكون الإدخال مرتّبًا مسبقًا أو شبه مرتّب والمحور ثابتًا، مما يُطلق O(n²). |
| تكون محلية الذاكرة المخبئية مهمة، لأن الترتيب السريع يصل إلى الذاكرة تسلسليًا. | تُرتّب قائمة مترابطة، حيث يتجنّب ترتيب الدمج الوصول العشوائي الذي يعتمد عليه الترتيب السريع. |
كود Quick Sort
تنفيذ نظيف وقابل للتشغيل لخوارزمية Quick Sort بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Quick Sort بلغة Python
1def quick_sort(a, low=0, high=None):2 if high is None:3 high = len(a) - 14 if low < high:5 p = partition(a, low, high)6 quick_sort(a, low, p - 1)7 quick_sort(a, p + 1, high)8 return a9
10
11def partition(a, low, high):12 # Lomuto partition: everything < pivot moves left of it13 pivot = a[high]14 i = low15 for j in range(low, high):16 if a[j] < pivot:17 a[i], a[j] = a[j], a[i]18 i += 119 a[i], a[high] = a[high], a[i]20 return i21
22
23nums = [10, 7, 8, 9, 1, 5]24print("Before:", nums)25quick_sort(nums)26print("After: ", nums)كود Quick Sort بلغة JavaScript
1function quickSort(a, lo = 0, hi = a.length - 1) {2 if (lo >= hi) return a;3 const p = partition(a, lo, hi);4 quickSort(a, lo, p - 1);5 quickSort(a, p + 1, hi);6 return a;7}8
9// Lomuto partition: last element is the pivot10function partition(a, lo, hi) {11 const pivot = a[hi];12 let i = lo;13 for (let j = lo; j < hi; j++) {14 if (a[j] < pivot) {15 [a[i], a[j]] = [a[j], a[i]];16 i++;17 }18 }19 [a[i], a[hi]] = [a[hi], a[i]];20 return i;21}22
23const data = [5, 2, 9, 1, 7, 3];24console.log("Before:", data);25console.log("Sorted:", quickSort([...data]));كود Quick Sort بلغة Java
1import java.util.Arrays;2
3public class Main {4 static void quickSort(int[] arr, int low, int high) {5 if (low >= high) return;6 int p = partition(arr, low, high);7 quickSort(arr, low, p - 1);8 quickSort(arr, p + 1, high);9 }10
11 // Lomuto partition: last element is the pivot12 static int partition(int[] arr, int low, int high) {13 int pivot = arr[high];14 int i = low - 1;15 for (int j = low; j < high; j++) {16 if (arr[j] < pivot) swap(arr, ++i, j);17 }18 swap(arr, i + 1, high);19 return i + 1;20 }21
22 static void swap(int[] arr, int a, int b) {23 int tmp = arr[a];24 arr[a] = arr[b];25 arr[b] = tmp;26 }27
28 public static void main(String[] args) {29 int[] arr = {10, 7, 8, 9, 1, 5};30 System.out.println("Before: " + Arrays.toString(arr));31 quickSort(arr, 0, arr.length - 1);32 System.out.println("After: " + Arrays.toString(arr));33 }34}كود Quick Sort بلغة C++
1#include <iostream>2#include <utility>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10// Lomuto partition: place the pivot in its final position11int partition(std::vector<int>& a, int lo, int hi) {12 int pivot = a[hi];13 int i = lo;14 for (int j = lo; j < hi; ++j) {15 if (a[j] < pivot) std::swap(a[i++], a[j]);16 }17 std::swap(a[i], a[hi]);18 return i;19}20
21void quickSort(std::vector<int>& a, int lo, int hi) {22 if (lo >= hi) return;23 int p = partition(a, lo, hi);24 quickSort(a, lo, p - 1);25 quickSort(a, p + 1, hi);26}27
28int main() {29 std::vector<int> data = {10, 7, 8, 9, 1, 5};30 std::cout << "Before: ";31 printVec(data);32 quickSort(data, 0, static_cast<int>(data.size()) - 1);33 std::cout << "After: ";34 printVec(data);35 return 0;36}كود Quick Sort بلغة C
1#include <stdio.h>2
3void printArr(const int a[], int n) {4 for (int i = 0; i < n; i++) printf("%d ", a[i]);5 printf("\n");6}7
8void swap(int* x, int* y) {9 int tmp = *x;10 *x = *y;11 *y = tmp;12}13
14// Lomuto partition: place the pivot in its final position15int partition(int a[], int lo, int hi) {16 int pivot = a[hi];17 int i = lo;18 for (int j = lo; j < hi; j++) {19 if (a[j] < pivot) swap(&a[i++], &a[j]);20 }21 swap(&a[i], &a[hi]);22 return i;23}24
25void quickSort(int a[], int lo, int hi) {26 if (lo >= hi) return;27 int p = partition(a, lo, hi);28 quickSort(a, lo, p - 1);29 quickSort(a, p + 1, hi);30}31
32int main(void) {33 int data[] = {10, 7, 8, 9, 1, 5};34 int n = sizeof(data) / sizeof(data[0]);35 printf("Before: ");36 printArr(data, n);37 quickSort(data, 0, n - 1);38 printf("After: ");39 printArr(data, n);40 return 0;41}أسئلة شائعة حول Quick Sort
ما هو التعقيد الزمني للترتيب السريع؟
O(n log n) وهو O(n log n) في أفضل حالة، لكنه يتدهور إلى O(n²) في أسوأ حالة عندما تكون الأقسام غير متوازنة باستمرار. المحاور العشوائية أو وسيط الثلاثة تجعل أسوأ حالة نادرة جدًا.هل الترتيب السريع مستقر؟
لماذا يكون الترتيب السريع أسرع غالبًا من ترتيب الدمج؟
O(n log n) لكنه يدفع ثمن مخزن مؤقت بحجم O(n) ونقل بيانات أكثر.الترتيب السريع أم ترتيب الدمج - أيهما أستخدم؟
O(n log n)، أو عند ترتيب قوائم مترابطة أو بيانات خارجية لا تتسع في الذاكرة العشوائية.لماذا يصبح الترتيب السريع O(n²) على مصفوفة مرتّبة؟
n من مستويات العودية بدلًا من log n. اختيار المحور عشوائيًا أو بوسيط الثلاثة يكسر هذا النمط ويعيد سلوك O(n log n).