Radix сортировка vs подсчет сортировка vs сортировка ведра. Какая разница?

Я читаю определения radix, подсчета и сортировки ведра, и кажется, что все они являются только кодом ниже:

public static void sort(int[] a, int maxVal){
    int [] bucket=new int[maxVal+1];

    for (int i=0; i<bucket.length; i++){
        bucket[i]=0;
    }

    for (int i=0; i<a.length; i++){
        bucket[a[i]]++;
    }

    int outPos=0;
    for (int i=0; i<bucket.length; i++){
        for (int j=0; j<bucket[i]; j++){
            a[outPos++]=i;
        }
    }
}

Я знаю, что не могу быть прав, так что я упускаю? Покажите код, если вы думаете, что это может помочь объяснить в Java или C.

7 ответов


давайте начнем с некоторой переписывания вашего кода на C, потому что C более знаком для меня, чтобы объяснить. Итак, давайте вспомним ваш код с комментариями:

int
counting_sort(int a[], int a_len, int maxVal)
{
  int i, j, outPos = 0;
  int bucket_len = maxVal+1;
  int bucket[bucket_len]; /* simple bucket structure */

  memset(bucket, 0, sizeof(int) * bucket_len);

  /* one loop bucket processing */
  for (i = 0; i < a_len; i++)
    {
      bucket[a[i]]++; /* simple work with buckets */
    }

  for (i=0; i < bucket_len; i++)
    {
      for (j = 0; j < bucket[i]; j++)
        {
          a[outPos++] = i;
        }
    }

  return 0;
}

Теперь давайте предложим этому парню некоторые реалистичные данные:

[126, 348, 343, 432, 316, 171, 556, 223, 670, 201]

на выходе у нас есть

[126, 171, 201, 223, 316, 343, 348, 432, 556, 670]

кажется, что все ок? Ещё нет. Давайте посмотрим на maxVal. Это 670 (!) Для сортировки массива из 10 элементов здесь мы использовали массив из 670 элементов, в первую очередь нули. Чрезвычайно. Чтобы справиться с этой проблемой подсчета, у нас есть два возможных способа обобщения:

1) Первый способ - сделать сортировку цифровой. Это называется radix-сортировка. Давайте покажем некоторый код, пытаясь сделать его как можно ближе к коду подсчета-сортировки. Еще раз посмотрите на комментарии:

int
radix_sort(int a[], int a_len, int ndigits)
{
  int i;
  int b[a_len];
  int expn = 1;

  /* additional loop for digits */
  for (i = 0; i != ndigits; ++i)
    {
      int j;
      int bucket[10] = {0}; /* still simple buckets */

      /* bucket processing becomes tricky */
      for (j = 0; j != a_len; ++j)
        bucket[ a[j] / expn % 10 ]++;

      for (j = 1; j != 10; ++j)
        bucket[j] += bucket[j - 1];

      for (j = a_len - 1; j >= 0; --j)
        b[--bucket[a[j] / expn % 10]] = a[j];

      for (j = 0; j != a_len; ++j)
        a[j] = b[j];

      expn *= 10;
    }
}

мы торгуем множителем около N для памяти. Прибыль? Возможно. Но в некоторых случаях очень важен множитель около N. Программа, работающая в день и работающая в неделю, сильно отличается от представления пользователей, даже если оба работают 1*O(N) и 7*O(N) соответственно. Итак, мы подходим ко второму обобщению:--4-->

2) второй путь -- сделать ведра более изощренным. Это называется ведро-сортировка.

снова начнем с кода. Я предпочитаю больше кода перед философскими аргументами. Все же посмотрите на комментарии, они необходимы.

int
bucket_sort(int a[], int a_len, int maxVal)
{
  int i, aidx;

  typedef struct tag_list {
    int elem;
    struct tag_list *next;
  } list_t, *list_p;

  list_p bucket[10] = {0}; /* sophisticated buckets */

  /* one loop simple processing with one more inner loop 
    to get sorted buckets (insert-sort on lists, Cormen-style) */
  for (i = 0; i != a_len; ++i)
    {
      int bnum = (10 * a[i]) / maxVal;
      list_p bptr = bucket[bnum];
      list_p belem = malloc(sizeof(list_t));
      belem->elem = a[i];
      if (bptr == 0)
        {
          bucket[bnum] = belem;
          belem->next = 0;
          continue;
        }
      else if (a[i] <= bptr->elem)
        {
          belem->next = bptr;
          bucket[bnum] = belem;
          continue;
        }
      else
        {
          while (bptr != 0)
            {
              if ((bptr->elem <= a[i]) && ((bptr->next == 0) || (bptr->next->elem > a[i])))
                {
                  belem->next = bptr->next;
                  bptr->next = belem;
                  break;
                }
               bptr = bptr->next;
            }
         }
    }

  /* one loop (looks as two) to get all back */
  aidx = 0;

  for (i = 0; i != 10; ++i)
    {
      list_p bptr = bucket[i];
      while (bptr)
        {
          list_p optr = bptr;
          a[aidx] = bptr->elem;
          aidx += 1;
          bptr = bptr->next;
          free(optr);
        }
    }

  return 0;
}

Итак, что у нас здесь? Мы торгуем некоторыми сложная структура ковша и требование к динамически выделяемой памяти, но выигрышная статическая память и множитель около N в среднем.

Теперь давайте вспомним, что мы видели в коде:

  1. подсчет сортировки -- простые ведра, простая обработка, накладные расходы памяти
  2. сортировка Radix -- простые ведра, сложная обработка, накладные расходы скорости (и все еще нужна дополнительная статическая память)
  3. вид ведра -- изощренные ведра, простые обработка, требует динамической памяти, Хорошая в среднем

Radix и ковшовые сорта, таким образом, являются двумя полезными обобщениями подсчета сортировки. У них много общего с подсчетом рода и друг с другом, но в каждом случае мы что-то теряем и что-то выигрываем. Разработка программного обеспечения - это баланс между этими возможностями.


Radix сортировка против подсчета сортировки против сортировки ведра. Какая разница?

сортировка по ведрам помещает ключи или элементы для сортировки в ведра. Как они места в ведрах произвольны и могут быть частями составного ключа и любого распределения, которое вам нравится. Отдельные ведра, возможно, потребуется сортировать дальше.

сортировка в памяти быстрее, чем на диске. Однако, если у вас больше данных, чем поместится в памяти, вам нужен другой вариант. Что? вы можете сделать сортировку ведра, где ведра достаточно малы, чтобы поместиться в память. т. е. в каждом ведре есть большое количество записей. Эти вы можете быстро сортировать индивидуально.

Radix sort-это определенный тип сортировки ведра. Он начинается с верхних N-разрядных или n-разрядных цифр и может сортировать эти ведра с помощью сортировки radix и т. д., пока каждая запись не будет отсортирована.

подсчет сортировки похож на использование сортировки radix, за исключением использования всего значения. Вместо того, чтобы записывать каждый объект, он имеет ведро для каждого объекта, и оно просто подсчитывает количество вхождений. Это хорошо работает, когда у вас есть ограниченное количество возможных ключей и у вас есть много дубликатов.


ваш код-простой вариант подсчета сортировки без данных, просто ключи.

сортировка Radix основана на этом методе. Проблема с подсчетом сортировки-это требование к памяти:int [] bucket=new int[maxVal+1];. Radix sort решает эту проблему. Идея состоит в том, чтобы использовать подсчет сортировки несколько раз, сначала для нижних цифр, а затем для более высоких. Например, для сортировки 32-разрядных целых чисел можно использовать:

sort(a, 65535) using lower half as key
sort(a, 65535) using higher half as key

он работает, потому что подсчет сортировки стабилен - он сохраняет порядок данных с равными ключами. Это как сортировка в электронной таблице:sort by B; sort by A дает вам элементы, отсортированные по A и B, когда As равны.

сортировка ведра-это обобщение сортировки подсчета. Вы можете использовать его для сортировки действительных чисел из некоторого предсказуемого распределения вероятностей (например. униформа!--3-->). Идея состоит в том, чтобы использовать подсчет сортировки (используя floor(x*N_BUCKETS) как ключ), а затем только сортировать каждое ведро независимо.


согласно Geekviewpoint:

Radix:http://www.geekviewpoint.com/java/sorting/radixsort

Radix sort, как и подсчет сортировки и сортировки ведра, является целочисленным алгоритмом (т. е. значения входного массива считаются целыми числами). Следовательно, радикс сортировки является одним из самых быстрых алгоритмов сортировки, в теории. Особое различие для сортировки radix заключается в том, что он создает ведро для каждого шифра (т. е. цифры); как таковой, аналогично сортировка ведра, каждое ведро в сортировке radix должно быть расширяемым списком, который может принимать разные ключи.

ведро:http://www.geekviewpoint.com/java/sorting/bucketsort

сортировка ведра на самом деле очень хороша, учитывая, что подсчет сортировки разумно говорит о его верхней границе. А считать сортировку очень быстро. Особое различие для сортировки ведра заключается в том, что он использует хэш-функцию для разделения ключей входного массива, так что несколько ключи могут хэшироваться в одном ведре. Следовательно, каждое ведро должно быть эффективно растущим списком; подобно сортировке radix.

подсчет:http://www.geekviewpoint.com/java/sorting/countingsort

особое различие для подсчета сортировки заключается в том, что он создает ведро для каждого значения и держит счетчик в каждом ведре. Затем каждый раз, когда значение встречается во входной коллекции, соответствующий счетчик увеличивается. Потому что подсчет сортировки создает ведро для каждого значения, накладывая ограничение, чтобы максимальное значение во входном массиве было известно заранее.

Они объясняют это более подробно на своем сайте.

Edit:

  • Если вы используете сортировку radix и ваши числа десятичные, то вам нужно 10 ведер, по одному для каждой цифры от 0 до 9.

  • Если вы используете подсчет сортировки, то вам нужно ведро для каждого уникального значения на входе (на самом деле вам нужно ведро для каждого значения между 0 и max).

  • Если вы используете bucketsort, вы не знаете, сколько ведер вы будете использовать. Любая хэш-функция, которую вы используете, будет определять количество ведер.


во-первых, давайте посмотрим на разницу между сортировкой Radix и сортировкой ведра, потому что это, как правило, запутанная вещь, потому что идея кажется одинаковой. Затем мы посмотрим на подсчет сортировки, которая похожа на первичную версию этих двух и какие проблемы с подсчетом сортировки вызывают использование двух других

начальный проход как Radix, так и сортировки ведра одинаковы.Элементы помещаются в "ведра" i.е 0-10, 11-20,...и так далее, в зависимости от количества знаков в самом большом нет, Я. e радикс. Однако в следующем проходе сортировка ведра упорядочивает эти "ведра" и добавляет их в один массив. Однако метод сортировки radix добавляет ведра без дальнейшей сортировки и "повторно ведра" на основе второй цифры (место десяти) чисел. Следовательно, сортировка ведра более эффективна для "плотных" массивов, в то время как сортировка Radix может хорошо обрабатывать разреженные массивы. Ну подумайте о ведро рода, как это

Предположим, у вас есть список из n записей с ключом, который является числом от 1 к k (мы немного обобщаем задачу, поэтому k не обязательно равно n).

мы можем решить эту проблему, создав массив связанных списков. Мы перемещаем каждую входную запись в список в соответствующей позиции массива, а затем объединяем все списки вместе в порядке.

 bucket sort(L)
    {
    list Y[k+1]
    for (i = 0; i <= k; i++) Y[i] = empty
    while L nonempty
    {
        let X = first record in L
        move X to Y[key(X)]
    }
    for (i = 0; i <= k; i++)
    concatenate Y[i] onto end of L
    }

Что делать, когда k большой? Подумайте о десятичном представлении числа x = a + 10 b + 100 c + 1000 d + ... где a,b, C и т. д. Все в диапазоне 0..9. Эти цифры легко достаточно малы делать сортировку ведер.

   radix sort(L):
    {
    bucket sort by a
    bucket sort by b
    bucket sort by c
    ...
    }

или просто

radix sort(L):
{
while (some key is nonzero)
{
    bucket sort(keys mod 10)
    keys = keys / 10
}
}

почему мы сначала делаем сортировку наименее важной цифры? Если уж на то пошло, почему мы делаем больше, чем одну сортировку ведер, поскольку последняя-та, которая все расставляет по местам? Ответ: если мы пытаемся сортировать вещи вручную, мы, как правило, делаем что-то другое: сначала делаем сортировку ведра, затем рекурсивно сортируем значения, имеющие общую первую цифру. Это работает, но менее эффективно, поскольку оно разбивает проблему на много подзадач. Напротив, сортировка radix никогда не разбивает список; она просто применяет сортировку ведра несколько раз к одному и тому же списку. В сортировке radix последний проход сортировки ковша является тем, который больше всего влияет на общий порядок. Поэтому мы хотим, чтобы он использовал самые важные цифры. Предыдущие проходы сортировки ковша используются только для того, чтобы позаботиться о случае, когда два элемента имеют один и тот же ключ (mod 10) на последнем проходе.

Теперь у нас есть, что из пути все Подсчет сортировки - это сохранение вспомогательного массива C С K элементами, инициализированными до 0.

мы делаем один проход через входной массив A и для каждого элемента i в A то, что мы видим, мы увеличиваем C[i] на 1. После того, как мы переберем n элементы A и update C, значение по индексу j of C соответствует сколько раз j появлялся в A. Этот шаг занимает O (n) времени для итерации через A. Как только у нас есть C, мы можем построить отсортированную версию A по переборем C и вставка каждого элемента j в общей сложности C[j] раз в новый список (или сам по себе). Итерация через C занимает O (k) время.Этот конечным результатом является сортировка A, и в общей сложности для этого потребовалось O(n + k) времени.

падение подсчета сортировки заключается в том, что это может быть не слишком практично, если диапазон элементов слишком велик. Например, если диапазон из n элементов надо было от 1 до n 3, тогда просто создание вспомогательного массива C займет O (n^3) времени, а подсчет сортировки будет асимптотически хуже, чем сортировка вставки. Это также занимает o (n^3) пространство, которое значительно больше любого пространства, используемого любым другим алгоритмом сортировки, который мы узнали до сих пор. Radix sort помогает решить эту проблему, сортируя элементы по цифрам

Примечание: источники для ответа и дальнейшего чтения:

http://htmltolatex.sourceforge.net/samples/sample4.html

первый ответ на: в чем разница между сортировкой ведра и сортировкой radix?


Radix sort использует форму подсчета сортировки в качестве подпрограммы (хорошо, можно использовать, но чаще всего это будет подсчет сортировки).

Countingsort-это особая форма сортировки ведра,как ответил касавбере.

и Bucketsort делит ключи на ведра, а затем сортирует ведра по отдельности.


для сортировки массива с помощью count sort:

#define MAX_INPUT 1000

void sort(int arr[100], int n)
{
    static int hash[MAX_INPUT], i, j;

    memset(hash, 0, sizeof hash);

    for (i = 0; i < n; ++i) ++hash[arr[i]];

    j = 0;
    for (i = 0; i < MAX_INPUT; ++i)
        while (hash[i]--)
           arr[j++] = i;
}

Это просто O(MAX_INPUT), таким образом сортируя в линейном времени. Для сортировки ведра это совсем другое. Вот реализация