Counting Sort
آخر تحديث
فرز العد هو فرز بلا مقارنات للأعداد الصحيحة ضمن نطاق معروف ومحدود. يحسب عدد مرات ظهور كل قيمة، ثم يستخدم تلك الأعداد لكتابة كل قيمة مباشرة في موضعها المرتّب - دون أي مقارنات. اضغط تشغيل بالأعلى لتشاهد القيم وهي تُحصى ثم تُوضع بالترتيب.
يعمل فرز العد بزمن O(n + k)، حيث k هو نطاق قيم الإدخال. عندما لا يكون k أكبر بكثير من n يكون سريعًا للغاية وقد يتفوق على خوارزميات الفرز بالمقارنة ذات O(n log n)، لكن إذا كان نطاق القيم ضخمًا فإن مصفوفة العد ذات O(k) تجعله غير عملي.
التعقيد الزمني والمكاني
| الحالة | التعقيد | ملاحظات |
|---|---|---|
| الزمن | O(n + k) | n عنصرًا، k = نطاق القيم |
| المساحة | O(n + k) | مصفوفة العد + مصفوفة الإخراج |
| مستقر | نعم | عند الوضع من اليمين إلى اليسار باستخدام مجاميع البادئة |
| مقارنة؟ | لا | يفرز بالعد لا بالمقارنة |
| الأفضل لـ | نطاق أعداد صحيحة صغير | k قريب من n |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | إيجاد القيمة القصوى لتحديد حجم مصفوفة العد. |
| 2 | حساب عدد مرات ظهور كل قيمة. |
| 3 | (اختياري) تحويل الأعداد إلى مجاميع بادئة لتحقيق الاستقرار. |
| 4 | كتابة كل قيمة في الإخراج بعدد مرات ظهورها. |
| 5 | أصبحت مصفوفة الإخراج مرتّبة بالكامل الآن. |
مثال محلول
فرز [1, 4, 1, 2, 4] (القيم تتراوح من 0 إلى 4، لذا تحتوي مصفوفة العد على 5 خانات):
| المرور | الحالة | الإجراء |
|---|---|---|
| مسح الإدخال | count = [0, 2, 1, 0, 2] | حساب مرات الظهور: 1 يظهر مرتين، و2 مرة واحدة، و4 مرتين. |
| مجاميع البادئة | count = [0, 2, 3, 3, 5] | تحمل كل خانة الآن عدد القيم التي هي <= لدليلها، مما يعطي المواضع النهائية. |
وضع 4 | output = [_, _, _, _, 4] | القراءة من اليمين إلى اليسار: count[4] = 5، لذا يذهب 4 إلى الدليل 4؛ خفّض إلى 4. |
وضع 2 | output = [_, _, 2, _, 4] | count[2] = 3، لذا يذهب 2 إلى الدليل 2؛ خفّض إلى 2. |
وضع 1 | output = [_, 1, 2, _, 4] | count[1] = 2، لذا يذهب 1 إلى الدليل 1؛ خفّض إلى 1. |
وضع 4 | output = [_, 1, 2, 4, 4] | count[4] = 4، لذا يذهب هذا 4 إلى الدليل 3؛ خفّض إلى 3. |
وضع 1 | output = [1, 1, 2, 4, 4] | count[1] = 1، لذا يذهب هذا 1 إلى الدليل 0. أصبحت المصفوفة مرتّبة. |
متى تستخدم فرز العد
| استخدمه عندما | تجنّبه عندما |
|---|---|
| تفرز أعدادًا صحيحة (أو مفاتيح قابلة للتعيين إلى أعداد صحيحة) ضمن نطاق صغير ومعروف. | يكون نطاق القيم k أكبر بكثير من عدد العناصر n. |
تحتاج إلى زمن خطي O(n + k) وبإمكانك تحمّل المصفوفات الإضافية. | تكون الذاكرة محدودة - إذ تكلّف مصفوفة العد O(k) بغض النظر عن n. |
| تحتاج إلى فرز مستقر كإجراء فرعي (مثلًا داخل فرز الأساس radix sort). | تكون المفاتيح أعدادًا عشرية أو سلاسل نصية أو كائنات عشوائية بلا تعيين إلى أعداد صحيحة. |
| تكون القيمة القصوى محدودة ورخيصة الحساب مسبقًا. | يكون النطاق مجهولًا أو غير محدود، فلا يمكنك تحديد حجم مصفوفة العد. |
كود Counting Sort
تنفيذ نظيف وقابل للتشغيل لخوارزمية Counting Sort بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Counting Sort بلغة Python
1def counting_sort(a):2 # Works for non-negative integers with a small max value3 counts = [0] * (max(a) + 1)4 for value in a:5 counts[value] += 16 # Prefix sums turn counts into final positions7 for i in range(1, len(counts)):8 counts[i] += counts[i - 1]9 out = [0] * len(a)10 for value in reversed(a): # reversed keeps equal values stable11 counts[value] -= 112 out[counts[value]] = value13 return out14
15
16nums = [4, 2, 2, 8, 3, 3, 1]17print("Before:", nums)18print("After: ", counting_sort(nums))كود Counting Sort بلغة JavaScript
1function countingSort(arr) {2 // Count occurrences of each value, then rebuild in order3 const max = Math.max(...arr);4 const count = new Array(max + 1).fill(0);5 for (const x of arr) count[x]++;6 const out = [];7 count.forEach((c, value) => {8 for (let k = 0; k < c; k++) out.push(value);9 });10 return out;11}12
13const data = [4, 2, 9, 2, 7, 4, 1, 4];14console.log("Before:", data);15console.log("Sorted:", countingSort(data));كود Counting Sort بلغة Java
1import java.util.Arrays;2
3public class Main {4 static int[] countingSort(int[] arr) {5 int max = 0;6 for (int v : arr) max = Math.max(max, v);7 int[] count = new int[max + 1];8 for (int v : arr) count[v]++;9 // Prefix sums turn counts into final positions10 for (int i = 1; i <= max; i++) count[i] += count[i - 1];11 int[] out = new int[arr.length];12 // Walk backwards so equal values keep their order (stable)13 for (int i = arr.length - 1; i >= 0; i--) {14 out[--count[arr[i]]] = arr[i];15 }16 return out;17 }18
19 public static void main(String[] args) {20 int[] arr = {4, 2, 2, 8, 3, 3, 1};21 System.out.println("Before: " + Arrays.toString(arr));22 int[] sorted = countingSort(arr);23 System.out.println("After: " + Arrays.toString(sorted));24 }25}كود Counting Sort بلغة C++
1#include <algorithm>2#include <iostream>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
10void countingSort(std::vector<int>& a) {11 if (a.empty()) return;12 int maxVal = *std::max_element(a.begin(), a.end());13 // count[v] = how many times v appears14 std::vector<int> count(maxVal + 1, 0);15 for (int x : a) ++count[x];16 // Rebuild the array from the counts17 size_t idx = 0;18 for (int v = 0; v <= maxVal; ++v) {19 while (count[v]-- > 0) a[idx++] = v;20 }21}22
23int main() {24 std::vector<int> data = {4, 2, 2, 8, 3, 3, 1, 7};25 std::cout << "Before: ";26 printVec(data);27 countingSort(data);28 std::cout << "After: ";29 printVec(data);30 return 0;31}كود Counting Sort بلغة C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9void countingSort(int a[], int n) {10 int maxVal = a[0];11 for (int i = 1; i < n; i++) {12 if (a[i] > maxVal) maxVal = a[i];13 }14 // count[v] = how many times v appears15 int* count = calloc(maxVal + 1, sizeof(int));16 for (int i = 0; i < n; i++) count[a[i]]++;17 // Rebuild the array from the counts18 int idx = 0;19 for (int v = 0; v <= maxVal; v++) {20 while (count[v]-- > 0) a[idx++] = v;21 }22 free(count);23}24
25int main(void) {26 int data[] = {4, 2, 2, 8, 3, 3, 1, 7};27 int n = sizeof(data) / sizeof(data[0]);28 printf("Before: ");29 printArr(data, n);30 countingSort(data, n);31 printf("After: ");32 printArr(data, n);33 return 0;34}الأسئلة الشائعة حول فرز العد
ما هو التعقيد الزمني لفرز العد؟
O(n + k)، حيث n هو عدد العناصر وk هو نطاق القيم الممكنة. عندما يكون k = O(n) فإن هذا زمن خطي. ويستخدم مساحة إضافية بمقدار O(n + k).هل فرز العد مستقر؟
متى ينبغي أن أستخدم فرز العد؟
k أكبر بكثير من عدد العناصر، فإن مصفوفة العد تهدر الذاكرة ويكون الفرز بالمقارنة أفضل.ما الفرق بين فرز العد وفرز الأساس (radix sort)؟
10 للأرقام العشرية). يتعامل فرز الأساس مع نطاقات قيم كبيرة تجعل فرز عد واحد غير عملي.لماذا لا يكون فرز العد أسرع دائمًا من الفرز السريع (quicksort)؟
O(n + k)، لذا فهو يتفوق فقط عندما يكون نطاق القيم k مقاربًا لـ n. إذا كان k ضخمًا - مثلًا فرز 100 قيمة في النطاق من 0 إلى 1,000,000,000 - فإن مصفوفة العد ذات O(k) تهيمن وتهدر الذاكرة، بينما يبقى فرز بالمقارنة بتعقيد O(n log n) مثل الفرز السريع سريعًا وموفّرًا للمساحة.هل يمكن لفرز العد التعامل مع الأعداد السالبة؟
value - min، بحيث تُعيَّن أصغر قيمة إلى الدليل 0. ويصبح حجم مصفوفة العد max - min + 1. ونسيان هذه الإزاحة خطأ شائع يتسبب في الانهيار مع المدخلات السالبة.