فرز الأساس
آخر تحديث
فرز الأساس هو فرز بلا مقارنات للأعداد الصحيحة. فبدلًا من مقارنة القيم، يفرز الأرقام رقمًا برقم. تعالج نسخة الرقم الأقل أهمية (LSD) خانة الآحاد أولًا، ثم العشرات، ثم المئات، مستخدمةً فرز عدّ مستقرًّا عند كل رقم. ولأن كل تمريرة مستقرة، فإن المصفوفة كلها تصبح مرتّبة بمجرد معالجة الرقم الأكثر أهمية. اضغط تشغيل في الأعلى لتشاهد كيف تعيد كل تمريرة رقمية ترتيب الأعمدة.
يعمل فرز الأساس في زمن O(d·(n + k))، حيث d هو عدد الأرقام وk هو الأساس (10 هنا). وبالنسبة للأعداد الصحيحة ذات العرض الثابت يكون هذا خطيًّا فعليًّا - ويمكنه التفوق على فرز المقارنة بزمن O(n log n) - لكنه يعمل فقط على البيانات التي يمكن تفكيكها إلى أرقام أو مفاتيح.
التعقيد الزمني والمكاني
| الحالة | التعقيد | ملاحظات |
|---|---|---|
| الزمن | O(d·(n + k)) | d رقمًا، أساس k (خطي عند ثبات d) |
| المساحة | O(n + k) | مصفوفة الإخراج + عدّات الأرقام |
| مستقر | نعم | كل تمريرة رقمية هي فرز عدّ مستقر |
| قائم على المقارنة؟ | لا | يفرز حسب الرقم لا بمقارنة القيم |
| يعمل على | أعداد صحيحة/مفاتيح | ليس على الكائنات القابلة للمقارنة عمومًا |
خطوة بخطوة
| الخطوة | ما الذي يحدث |
|---|---|
| 1 | إيجاد القيمة العظمى لمعرفة عدد الأرقام المطلوب معالجتها. |
| 2 | البدء بالرقم الأقل أهمية (خانة الآحاد). |
| 3 | فرز المصفوفة فرزًا مستقرًّا حسب ذلك الرقم باستخدام فرز العدّ. |
| 4 | الانتقال إلى الرقم التالي الأكثر أهمية. |
| 5 | التكرار حتى معالجة جميع مواضع الأرقام. |
مثال محلول
فرز [170, 45, 75, 90, 2, 24, 66]:
| التمريرة | المصفوفة | الإجراء |
|---|---|---|
| البداية | [170, 45, 75, 90, 2, 24, 66] | القيمة العظمى هي 170، لذا يلزم ثلاث تمريرات رقمية. |
| الآحاد | [170, 90, 2, 24, 45, 75, 66] | فرز مستقر حسب خانة الآحاد: 0, 0, 2, 4, 5, 5, 6. |
| العشرات | [2, 24, 45, 66, 170, 75, 90] | فرز مستقر حسب خانة العشرات: 0, 2, 4, 6, 7, 7, 9 (يحتفظ 170 بتقدّمه على 75). |
| المئات | [2, 24, 45, 66, 75, 90, 170] | فرز مستقر حسب خانة المئات؛ 170 وحده يملك 1 فينتقل إلى الآخر. تمّ الفرز. |
متى تستخدم فرز الأساس
| استخدمه عندما | تجنّبه عندما |
|---|---|
| تكون المفاتيح أعدادًا صحيحة أو سلاسل ثابتة الطول يمكن تقسيمها إلى أرقام. | يجب عليك فرز كائنات عشوائية باستخدام مقارن مخصّص. |
يكون عدد أرقام المفاتيح d صغيرًا ومحدودًا، بحيث يتفوق O(d·(n + k)) على O(n log n). | تكون المفاتيح طويلة جدًّا أو غير محدودة، مما يجعل d كبيرًا والتمريرات مكلفة. |
تحتاج إلى فرز مستقر ويمكنك تحمّل مساحة إضافية بمقدار O(n + k). | تكون الذاكرة شحيحة وتكون مخازن O(n + k) غير مقبولة. |
يكون مدى القيم أو الأساس k معتدلًا نسبةً إلى n. | يكون k ضخمًا، بحيث تهيمن كل تمريرة فرز عدّ على زمن التنفيذ. |
كود Radix Sort
تنفيذ نظيف وقابل للتشغيل لخوارزمية Radix Sort بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Radix Sort بلغة Python
1def radix_sort(a):2 # Sort by each decimal digit, least significant first3 max_value = max(a)4 exp = 15 while max_value // exp > 0:6 a = sort_by_digit(a, exp)7 exp *= 108 return a9
10
11def sort_by_digit(a, exp):12 buckets = [[] for _ in range(10)]13 for value in a:14 digit = (value // exp) % 1015 buckets[digit].append(value)16 # Concatenating buckets 0..9 keeps the sort stable17 return [value for bucket in buckets for value in bucket]18
19
20nums = [170, 45, 75, 90, 802, 24, 2, 66]21print("Before:", nums)22print("After: ", radix_sort(nums))كود Radix Sort بلغة JavaScript
1function radixSort(arr) {2 let a = [...arr];3 const max = Math.max(...a);4 // One counting pass per digit, least significant first5 for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {6 const buckets = Array.from({ length: 10 }, () => []);7 for (const x of a) {8 buckets[Math.floor(x / exp) % 10].push(x);9 }10 a = buckets.flat();11 }12 return a;13}14
15const data = [170, 45, 75, 90, 802, 24, 2, 66];16console.log("Before:", data);17console.log("Sorted:", radixSort(data));كود Radix Sort بلغة Java
1import java.util.Arrays;2
3public class Main {4 static void radixSort(int[] arr) {5 int max = 0;6 for (int v : arr) max = Math.max(max, v);7 // One stable counting pass per decimal digit8 for (int exp = 1; max / exp > 0; exp *= 10) countingPass(arr, exp);9 }10
11 static void countingPass(int[] arr, int exp) {12 int n = arr.length;13 int[] out = new int[n];14 int[] count = new int[10];15 for (int v : arr) count[(v / exp) % 10]++;16 for (int i = 1; i < 10; i++) count[i] += count[i - 1];17 // Walk backwards to keep the pass stable18 for (int i = n - 1; i >= 0; i--) {19 int digit = (arr[i] / exp) % 10;20 out[--count[digit]] = arr[i];21 }22 System.arraycopy(out, 0, arr, 0, n);23 }24
25 public static void main(String[] args) {26 int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};27 System.out.println("Before: " + Arrays.toString(arr));28 radixSort(arr);29 System.out.println("After: " + Arrays.toString(arr));30 }31}كود Radix 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
10// Stable counting sort on one decimal digit (exp = 1, 10, 100, ...)11void countingPass(std::vector<int>& a, int exp) {12 std::vector<int> output(a.size());13 std::vector<int> count(10, 0);14 for (int x : a) ++count[(x / exp) % 10];15 for (int d = 1; d < 10; ++d) count[d] += count[d - 1];16 for (int i = static_cast<int>(a.size()) - 1; i >= 0; --i) {17 int digit = (a[i] / exp) % 10;18 output[--count[digit]] = a[i];19 }20 a = output;21}22
23void radixSort(std::vector<int>& a) {24 int maxVal = *std::max_element(a.begin(), a.end());25 for (int exp = 1; maxVal / exp > 0; exp *= 10) {26 countingPass(a, exp);27 }28}29
30int main() {31 std::vector<int> data = {170, 45, 75, 90, 802, 24, 2, 66};32 std::cout << "Before: ";33 printVec(data);34 radixSort(data);35 std::cout << "After: ";36 printVec(data);37 return 0;38}كود Radix 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
9// Stable counting sort on one decimal digit (exp = 1, 10, 100, ...)10void countingPass(int a[], int n, int exp) {11 int* output = malloc(n * sizeof(int));12 int count[10] = {0};13 for (int i = 0; i < n; i++) count[(a[i] / exp) % 10]++;14 for (int d = 1; d < 10; d++) count[d] += count[d - 1];15 for (int i = n - 1; i >= 0; i--) {16 int digit = (a[i] / exp) % 10;17 output[--count[digit]] = a[i];18 }19 for (int i = 0; i < n; i++) a[i] = output[i];20 free(output);21}22
23void radixSort(int a[], int n) {24 int maxVal = a[0];25 for (int i = 1; i < n; i++) {26 if (a[i] > maxVal) maxVal = a[i];27 }28 for (int exp = 1; maxVal / exp > 0; exp *= 10) {29 countingPass(a, n, exp);30 }31}32
33int main(void) {34 int data[] = {170, 45, 75, 90, 802, 24, 2, 66};35 int n = sizeof(data) / sizeof(data[0]);36 printf("Before: ");37 printArr(data, n);38 radixSort(data, n);39 printf("After: ");40 printArr(data, n);41 return 0;42}أسئلة شائعة عن فرز الأساس
ما هو التعقيد الزمني لفرز الأساس؟
O(d·(n + k))، حيث d هو عدد الأرقام وk هو الأساس. وبالنسبة للأعداد الصحيحة ذات العرض الثابت يكون هذا فعليًّا O(n)، وهو ما قد يكون أسرع من فرز المقارنة. ويستخدم مساحة إضافية بمقدار O(n + k).هل فرز الأساس مستقر؟
متى يمكنني استخدام فرز الأساس؟
كيف يختلف فرز الأساس عن فرز العدّ؟
k)، مما يمكّنه من التعامل مع مديات قيم كبيرة يعجز عنها فرز العدّ البسيط.