Цивільно-імперські послуги; структурне будівництво
Реалізація сортування для сортування великих наборів малих інтеграторів в C#
Table of Contents
Коли ваше сортування завдання включає в себе великі масиви малих цілих чисел - так, як рівні, віки, або категоричні коди - класика порівняння алгоритми, такі як QuickSort або MergeSort може відчувати себе як перенавищення. Ці алгоритми працюють в O(n log n) час, але якщо діапазон можливих значень обмежений, можна сортувати в лінійному O(n + k) час з Коретинг Сорт]. Цей алгоритм сортування некомпазона важе той факт, що ви можете розраховувати виникнення, а не порівняти елементи, що забезпечує стабільний сорт, який є як простим і незрівняно швидко для введення.
Як розрахувати Сортування робіт
Сортування дозволяє використовувати знання, що значення вводу є цілими, що намальовані з невеликого діапазону . Замість парних порівняння він будує частоту гістограму значень, а потім використовує, що йоготограма для розміщення кожного елемента в його правильну позицію сортування.
Основні підходи: Диреконструкція
Найстаріша версія Сортування робіт по двох переходах:
- Частота тітка] – Вийшов через масив вхідних даних і підсилює лічильник для кожного значення, який ви бачите.
- Overwrite the вхід – Пройдіть через лічильник масиву від найменших до найбільших і, для кожного значення, напишіть його в масив введення стільки разів, як його кількість.
Цей випускає сортований вихід, але робить не] збереження відносного порядку дублікатів (це не стабільний). Стабільність має значення, коли ви сортуєте на ключі, зберігаючи оригінальне замовлення записів з рівних ключів. Стійкий варіант, описаний нижче, є одним з найбільш часто використовуваних на практиці.
Стабільний мінливий: cummulative Counts
Щоб зробити графічний дизайн стабільним, ми додаємо третій прохід:
- Кількість частот як до.
- Перетворення частоти масиву в кумулятивний масив підрахунку. Після цього кроку має кількість елементів ≤ i.
- Встановіть в зворотному масиві (з останнього елемента до першого). Для кожного елемента використовуйте його мулятивний розрахунок, щоб знайти його позицію в масиві виведення, розмістити його і відхилити кількість.
Оскільки ми перевернулися в зворотному режимі, відносне замовлення рівних елементів зберігається. Вихідний масив відокремлений від входу, тому ця версія використовує O(n) додатковий простір для виходу, тоді як базова версія може сортувати в місці перезапису введення.
Реалізація Сортування графічних даних в C#
Нижче наведено два C#-впровадження: базова версія (для сценаріїв, де стабільність не потрібна) і стабільна версія, яка використовує допоміжний масив. Обидва вимагають заздалегідь знати максимальну вартість.
Базовий (нестійкий) Сортування
Цей варіант сортує вхідний масив безпосередньо без додаткового виходу буфера. Це пам'ять-ефективний, але не стабільний.
public static void CountingSortBasic(int[] array, int maxValue)
{
int[] counts = new int[maxValue + 1];
// Count each element's frequency
for (int i = 0; i < array.Length; i++)
{
counts[array[i]]++;
}
// Overwrite the original array in sorted order
int index = 0;
for (int value = 0; value <= maxValue; value++)
{
while (counts[value]-- > 0)
{
array[index++] = value;
}
}
}
Стабільний графічний Сорт
У стабільній версії передбачено вихідний масив одного розміру, як і вхід. Також використовується кумулятивні показники для правильно посадкових елементів.
public static int[] CountingSortStable(int[] array, int maxValue)
{
int[] counts = new int[maxValue + 1];
int[] output = new int[array.Length];
// Step 1: Count occurrences
foreach (int num in array)
{
counts[num]++;
}
// Step 2: Transform counts to cumulative counts
for (int i = 1; i <= maxValue; i++)
{
counts[i] += counts[i - 1];
}
// Step 3: Build the output array (iterate input in reverse for stability)
for (int i = array.Length - 1; i >= 0; i--)
{
int value = array[i];
output[counts[value] - 1] = value;
counts[value]--;
}
return output;
}
У обох реалізаціях є найбільшим цілим, що з'являється в масиві. Якщо справжній максимум невідомо, можна його скласти з підготовчим скануванням (O(n)). Стійка версія повертає новий сортований масив, залишаючи оригінальний незмінний.
Аналіз комплексності
n] k = макс. - хв + 1 ( діапазон можливих значень).
- Time:] Графічний Сорт працює O(n + k)] час. Фаза графінгу O(n), кумулятивний префікс O(k), а реконструкція O(n). Коли k O(n), алгоритм є лінійним.
- Космічна:] Основна версія використовує O(k) додатковий простір для масиву графа. Стійка версія використовує O(n + k) тому, що вона також виділяє вихідний масив. Це робить Сортування не підходить, коли діапазон є великим відносно кількості елементів.
- Компанія з іншими сортами: Порівняння формованих сортів, таких як QuickSort і MergeSort вимагають принаймні O(n log n) порівняння. Для малих к (наприклад, к < 10000 і n > 100,000), Сортування може бути замовлень швидше.
Варіанти і розширення
Обробка негативних інтегерів
Підрахунок Сортування рідно працює з нестійкими цілими цілостями. Для обробки негативних значень, перенести весь діапазон так, як мінімум стає нульовим. Наприклад, якщо кількість чисел коливається від -1000 до 1000, зміщуючи кожен елемент +1000. Кількість масиву потім має розмір .
public static int[] CountingSortWithNegative(int[] array)
{
if (array.Length == 0) return array;
int min = array.Min();
int max = array.Max();
int range = max - min + 1;
int[] counts = new int[range];
int[] output = new int[array.Length];
foreach (int num in array)
counts[num - min]++;
for (int i = 1; i < range; i++)
counts[i] += counts[i - 1];
for (int i = array.Length - 1; i >= 0; i--)
{
int value = array[i];
output[counts[value - min] - 1] = value;
counts[value - min]--;
}
return output;
}
Mapping Нетерти ключі
Підрахунок Сортувати вимагає цілих ключів. Якщо ваші дані складаються з символів (байтів), або енмутерації, які можна відлити до цілих цілих, ви все ще можете застосувати його. Для більших об'єктів ви можете витягти ціле ключ і сортувати об'єкти відповідно - це саме те, як Radix Сорт часто використовує Сортування, як його внутрішній підротутин.
Модель: HYIP-SB-SB-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-S-
Сортування Radix - це природний вибір для кожного проходу, коли основа (наприклад, 10 або 256) невелика. Це дозволяє лінійно-часовому сортування довільних цілих, не тільки малих.
Практичні питання в C#
Письмовий друк та великий к
Найбільший підводний водоспад – це зосереджуючий масив, більший ніж наявна пам'ять. Наприклад, сортування 1000 елементів з діапазоном 1,000,000 відпрацьованих місць. Завжди перевірте, що k не є наказами про величину більше, ніж n]]—іншвидше використовуйте порівняння сорту або гібридний підхід.
Паралелізм і Span<T>
Для надзвичайно великих масивів можна паралельноізувати фазу підрахунку, розділивши вхід по нитках. Кожна нитка підраховує її сегмент в приватний масив, а потім часткові результати агрегатуються. Використання і для масиву підрахунку може зменшити виділення шийки шийки торка, коли діапазон невеликий.
Крамниці
- Empty array] – повертаємо негайно.
- Сінгле елемент] – сортування тривіально.
- Всі ідентичні значення – масив графа має один запис нетермоле; реконструкція працює в O(n).
- Large діапазон, але пропалюють дані – Сортування стає неефективним, оскільки більшість записів нулі. Розглянемо хеш-на основі підрахунок підходу або Сорту пряже.
Рекомендації щодо продуктивності
Використовуйте Сортування графічних даних, коли ви знаєте, що цілі введення потрапляють в невеликий діапазон (наприклад,, марки 0–100, віки 0–120, або коди помилок 0–255). Для збільшення діапазонів розглянути Сорт Radix або гібрид, який повертається до QuickSort для розділів високого рівня.
Коли використовувати Сортування (і коли не потрібно)
| Situation | Recommendation |
|---|---|
| Small integer range (k ~ n) | Excellent choice – linear time, simple code. |
| Large integer range (k >> n) | Avoid – memory waste and O(k) overhead. |
| Need stability | Use the stable variant (cumulative counts). |
| Strings or objects | Consider Radix Sort or a comparison sort. |
| Extremely large datasets | Counting Sort can be parallelized; but watch memory. |
Визначні та результати
У типовому еталоні n = 1,000,000 і k = 1,000, Сортування заповнюється приблизно 20–30% часу, що приймається (який використовує інтросорт). Розгалуження ширше, як k зменшується. Нижче відбувається наближене порівняння (часи виконання на сучасному процесорі з .NET 8):
n = 1,000,000 | k = 1,000
Array.Sort (QuickSort variant) : 68 ms
CountingSortBasic : 12 ms
CountingSortStable : 18 ms
Коли діапазон виростає до 10000, Сортування все ще перемагає, але запас вузьких. Для k = 100,000, накладна пам'ять (≈ 400 КБ для масиву підрахунку) починає боляче кешу процесора, а продуктивність може деградувати.
Висновок
Сортування графінгу - це децептивно простий алгоритм, який забезпечує лінійну продуктивність при подачі даних, що відповідає її обмеженням. Для розробників C#, що працюють з великими масивами малих цілих, це цінний інструмент, який може різко зменшити час сортування. Тримайте око на діапазоні ваших даних: якщо це невелике і відомо, Сортування розфарбовування буде пропалювати будь-яку альтернативу порівняння. Для більш загального сортування використовуйте вбудоване , але завжди бути готовим до падіння графічних Сортів, коли цифри, які виділилися, -літерально і з'являються.
Для подальшого читання консультуйтеся з Вікіпедія статті про Сортування графінгу, Microsoft docs on Array.Sort, і практичний посібник з GeeksforGeeks.