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 לספרות עשרוניות). Radix sort מתמודד עם טווחי ערכים גדולים שהיו הופכים מיון מנייה יחיד ללא מעשי.למה מיון מנייה לא תמיד מהיר יותר מ-quicksort?
O(n + k), ולכן הוא מנצח רק כשטווח הערכים k דומה בגודלו ל-n. אם k עצום, למשל מיון של 100 ערכים בטווח 0 עד 1,000,000,000, מערך הספירה של O(k) שולט ומבזבז זיכרון, ואילו מיון מבוסס השוואה של O(n log n) כמו quicksort נשאר מהיר וחסכוני בזיכרון.האם מיון מנייה יכול להתמודד עם מספרים שליליים?
value - min, כך שהערך הקטן ביותר ממופה לאינדקס 0. גודל מערך הספירה הופך ל-max - min + 1. שכחה של ההיסט הזה היא באג נפוץ שקורס על קלטים שליליים.