Алгоритм карманной сортировки (Bucket Sort): принцип работы

Модуль "Сортировки" • урок 14 из 17 •

Также упоминается как: Блочная сортировка, Корзинная сортировка

Суть урока. Карманная (или блочная) сортировка отлично работает, когда мы знаем, что элементы равномерно распределены в определенном диапазоне. Мы делим этот диапазон на равные интервалы - "карманы" (от англ. bucket - дословно "корзина" или "ведро"), раскидываем элементы по карманам, сортируем каждый карман по отдельности и затем сливаем их вместе.

Время работы: В среднем O(N + K), в худшем случае O(N²).

Идея метода сортировки

Представьте, что мы сортируем дробные числа на математическом полуинтервале [0, 1) - то есть от 0.0 (включительно) до 1.0 (не включительно). У нас 100 чисел. Мы можем создать 10 "карманов" (массивов или связных списков): первый для чисел от 0.0 до 0.1, второй от 0.1 до 0.2 и так далее.

Алгоритм карманной сортировки: распределение чисел по карманам
Карманная сортировка: вычисление индекса кармана по значению элемента и локальная сортировка каждого кармана.

В интерактивном блоке ниже видно, как карманная сортировка распределяет числа по карманам, наводит в них порядок и собирает все обратно.

Тренажер: алгоритм карманной сортировки

Отсортируем 0.29 0.12 0.58 0.23 0.81 0.45 по 5 интервальным карманам.

число в массиве раскладка / сортировка отсортировано
Фаза: Scatter Карман: - Шаг: 0 / 0
[U]
Bucket Sort
Блочная сортировка
Распределение элементов по карманам-диапазонам с последующей сортировкой внутри карманов и сборкой в единый результат.
Разобрать по шагам в большой версии →
Сравнение с Radix и Counting Sort
  • Сортировка подсчетом (Counting Sort) создает карманы для каждого уникального значения. Подходит для целых чисел с малым разбросом.
  • Поразрядная сортировка (Radix Sort) создает карманы для цифр текущего разряда. Подходит для любых целых чисел.
  • Карманная сортировка (Bucket Sort) создает карманы для интервалов значений. Прекрасно подходит для дробных чисел с плавающей точкой (float).

Анализ сложности

В лучшем и среднем случае, когда данные распределены равномерно, в каждый из K карманов попадет примерно N/K элементов. Сортировка малого количества элементов займет константное время O(1) для каждого кармана. Общее время составит O(N + K).

Однако, если мы отсортируем массив, в котором 99% чисел лежат в узком диапазоне (например, от 0.5 до 0.51), почти все они попадут в один карман. Тогда мы будем вынуждены применять Сортировку вставкой к массиву длины N, что даст худшее время O(N²).

Варианты и оптимизации

Базовая концепция распределения по карманам породила несколько интересных алгоритмов-наследников:

Реализация на C (для чисел с плавающей точкой)

Карманная сортировка: код целиком C, 53 строки
#include <stdio.h>
#include <stdlib.h>

// Узел для связного списка (кармана)
struct Node {
    float data;
    struct Node* next;
};

// Функция вставки в сортированный связный список (Сортировка вставкой)
void InsertSorted(struct Node** head, float val) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = val;
    
    if (*head == NULL || (*head)->data >= val) {
        newNode->next = *head;
        *head = newNode;
    } else {
        struct Node* current = *head;
        while (current->next != NULL && current->next->data < val) {
            current = current->next;
        }
        newNode->next = current->next;
        current->next = newNode;
    }
}

// Карманная сортировка
void BucketSort(float arr[], int n) {
    // Создаем N карманов
    struct Node** buckets = (struct Node**)calloc(n, sizeof(struct Node*));
    
    // Шаг 1: Раскладываем элементы по карманам
    // Формула: индекс = n * arr[i] (для чисел в диапазоне [0.0, 1.0))
    for (int i = 0; i < n; i++) {
        int bucketIndex = n * arr[i];
        // Сразу вставляем элемент в карман в отсортированном порядке
        InsertSorted(&buckets[bucketIndex], arr[i]);
    }
    
    // Шаг 3: Собираем элементы обратно
    int index = 0;
    for (int i = 0; i < n; i++) {
        struct Node* current = buckets[i];
        while (current != NULL) {
            arr[index++] = current->data;
            struct Node* temp = current;
            current = current->next;
            free(temp); // Очищаем память
        }
    }
    free(buckets);
}

Коротко: главное

  • Карманная сортировка опирается на предположение о равномерном распределении элементов.
  • Диапазон возможных значений разбивается на равные интервалы (карманы), по которым распределяются элементы.
  • Внутри каждого кармана элементы сортируются классическим алгоритмом (часто сортировкой вставкой).
  • В среднем случае время работы составляет O(N + K).
  • В худшем случае, если почти все элементы попадут в один карман, алгоритм деградирует до O(N²).

Проверьте себя

В каком случае Карманная сортировка покажет худшее время работы O(N²)?

Как вам урок? Спасибо за отзыв!