Не удается заставить алгоритм сортировки по основанию работать в С++

Имея n 32-битных целых числа (предположим, что они положительные), вы хотите отсортировать их, сначала просматривая наиболее значащие shift в общем количестве битов и рекурсивно сортируя каждую корзину, созданную отсортированными целыми числами по этим битам.

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

Мой код следующий:

void radix_sortMSD(int start, int end, 
          int shift, int currentDigit, int input[])
{

    if(end <= start+1 || currentDigit>=32) return;

    /*
     find total amount of buckets
     which is basically 2^(shift)
    */
    long long int numberOfBuckets = (1UL<<shift);

    /*
     initialize a temporary array 
     that will hold the sorted input array
     after finding the values of each bucket.   
    */

    int tmp[end];

   /*
     Allocate memory for the buckets.
   */
   int *buckets = new int[numberOfBuckets + 1];

   /*
       initialize the buckets,
        we don't care about what's 
     happening in position numberOfBuckets+1
   */
   for(int p=0;p<numberOfBuckets + 1;p++)
         buckets[p] = 0;

   //update the buckets
   for (int p = start; p < end; p++)
      buckets[((input[p] >> (32 - currentDigit - shift)) 
                &   (numberOfBuckets-1)) + 1]++;

   //find the accumulative sum
   for(int p = 1; p < numberOfBuckets + 1; p++)
       buckets[p] += buckets[p-1];

   //sort the input array input and store it in array tmp   
   for (int p = start; p < end; p++){ 
    tmp[buckets[((input[p] >> (32 - currentDigit- shift)) 
            & (numberOfBuckets-1))]++] = input[p];
    }

   //copy all the elements in array tmp to array input
   for(int p = start; p < end; p++)
          input[p] = tmp[p];

   //recurse on all the groups that have been created
   for(int p=0;p<numberOfBuckets;p++){
       radix_sortMSD(start+buckets[p], 
       start+buckets[p+1], shift, currentDigit+shift, input);
    }

    //free the memory of the buckets
    delete[] buckets;
}

  int main()
  {

        int a[] = {1, 3, 2, 1, 4, 8, 4, 3};
        int n = sizeof(a)/sizeof(int);
        radix_sortMSD(0,n, 2,0,a);
        return 0;
   }

Я могу представить только две проблемы в этом коде.

Первая проблема заключается в том, действительно ли я получаю правильные биты целых чисел на каждой итерации. Я сделал предположение, что если я нахожусь в позиции currentDigit, где, если currentDigit = 0, это означает, что я нахожусь в бите 32 моего целого числа, то, чтобы получить следующие shift бит, я делаю сдвиг вправо на 32 - currentDigit - shift разрядов, а затем применяю операцию И к получить shift наименее значащих битов, а это именно те биты, которые мне нужны.

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

любые отзывы по этому поводу будут оценены.

заранее спасибо.

РЕДАКТИРОВАТЬ: добавлена ​​основная функция, чтобы показать, как вызывается моя функция счисления.


person jsguy    schedule 25.02.2015    source источник
comment
Вы можете сэкономить несколько строк ввода, если замените выделение (int buckets = new int[...];), последующий цикл инициализации и в конечном итоге delete[] buckets;, если вы использовали std::vector<int> buckets(...);. Кроме того, это сделало бы ваш код безопасным для исключений, и вы получили бы дополнительные проверки операций индекса за пределами границ. Я не помню, чтобы когда-либо использовал новый массив в C++, лучше использовать обычный вектор.   -  person Ulrich Eckhardt    schedule 26.02.2015


Ответы (1)


Еще одно обновление, преобразованное в шаблон для типа массива. Массив Tmp теперь передается как параметр. Этапы копирования были убраны, и добавлена ​​вспомогательная функция для возврата буфера, в который попадают отсортированные данные. Протестировано с 4 миллионами 64-битных целых чисел без знака, это работает, но медленно. Наименьшее время достигается при numberOfBits = 4. numberOfBits больше не нужно точно делить количество битов на элемент.

Чтобы объяснить, почему MSD first работает медленно, я воспользуюсь аналогией со сортировщиком карт. Представьте, что у вас есть 1000 карточек, каждая из которых содержит 3 цифры от 000 до 999 в случайном порядке. Обычно вы проходите через сортировщик с 3-й цифрой, заканчивая 100 картами в каждой из ячеек, ячейка 0 содержит карты с «0», ... ячейка 9 содержит карты с «9». Затем вы объединяете карты из ячейки 0 в корзину 9 и снова пропускаете их через сортировщик, используя 2-ю цифру и снова используя 1-ю цифру, в результате чего получается отсортированный набор карточек. Это 3 прогона по 1000 карт в каждом, так что всего через сортировщик прошло 3000 карт.

Теперь снова начните со случайно упорядоченных карт и отсортируйте по 1-й цифре. Вы не можете объединить наборы, потому что карты с более высокими 1-ми цифрами, но более низкими 2-ми цифрами оказываются не по порядку. Итак, теперь вам нужно сделать 10 прогонов по 100 карт в каждом. В результате получается 100 комплектов по 10 карточек в каждом, которые вы снова пропускаете через сортировщик, в результате чего получается 1000 комплектов по 1 карточке в каждом, и теперь карточки отсортированы. Таким образом, количество карт, прошедших через сортировщик, по-прежнему равно 3000, как и выше, но вам нужно было сделать 111 прогонов (1 с набором из 1000 карт, 10 с набором из 100 карт, 100 с набором из 10 карт).

template <typename T>
void RadixSortMSD(size_t start, size_t end, 
          size_t numberOfBits, size_t currentBit, T input[], T tmp[])
{
    if((end - start) < 1)
        return;

    // adjust numberOfBits if currentBit close to end element
    if((currentBit + numberOfBits) > (8*sizeof(T)))
        numberOfBits = (8*sizeof(T)) - currentBit;

    // set numberOfBuckets
    size_t numberOfBuckets = 1 << numberOfBits;
    size_t bitMask = numberOfBuckets - 1;
    size_t shift = (8*sizeof(T)) - currentBit - numberOfBits;

    // create bucket info
    size_t *buckets = new size_t[numberOfBuckets+1];
    for(size_t p = 0; p < numberOfBuckets+1; p++)
        buckets[p] = 0;
    for(size_t p = start; p < end; p++)
        buckets[(input[p] >> shift) & bitMask]++;
    size_t m = start;
    for(size_t p = 0; p < numberOfBuckets+1; p++){
        size_t n = buckets[p];
        buckets[p] = m;
        m += n;
    }

    //sort the input array input and store it in array tmp   
    for (size_t p = start; p < end; p++){ 
        tmp[buckets[(input[p] >> shift) & bitMask]++] = input[p];
    }

    // restore bucket info
    for(size_t p = numberOfBuckets; p > 0; p--)
        buckets[p] = buckets[p-1];
    buckets[0] = start;

    // advance current bit
    currentBit += numberOfBits;
    if(currentBit < (8*sizeof(T))){
        //recurse on all the groups that have been created
        for(size_t p=0; p < numberOfBuckets; p++){
            RadixSortMSD(buckets[p], buckets[p+1],
                numberOfBits, currentBit, tmp, input);
        }
    }

    //free buckets
    delete[] buckets;
    return;
}

template <typename T>
T * RadixSort(T *pData, T *pTmp, size_t n)
{
size_t numberOfBits = 4;
    RadixSortMSD(0, n, numberOfBits, 0, pData, pTmp);
    // return the pointer to the sorted data
    if((((8*sizeof(T))+numberOfBits-1)/numberOfBits)&1)
        return pTmp;
    else
        return pData;
}
person rcgldr    schedule 25.02.2015
comment
У меня работает наименее значимая версия, и, как вы сказали, это просто цикл for, но я хотел бы провести несколько экспериментов с наиболее значимой версией (что, насколько я понимаю, требует рекурсии), чтобы получить представление о производительности кеша. . Я думаю, что сортировка по основанию с наиболее значащей цифрой будет выполнять сортировку по основанию с наименьшим значением, но я хотел бы провести некоторые эксперименты, чтобы увидеть, прав я или нет. После установки и запуска версии LSD, версия MSD вызвала у меня очень сильную головную боль... К сожалению, я не очень хорошо разбираюсь в битовых сдвигах. - person jsguy; 25.02.2015
comment
Но конкатенация произойдет автоматически, верно? Это похоже на сортировку слиянием, вы сортируете подзадачи, но каждая подзадача будет частью вашего входного массива. Моя версия LSD не оптимизирована, но я хотел бы запустить версию MSD, чтобы увидеть поведение кэша. Кроме того, что, если вы объедините MSD и LSD вместе? Типа запустить МСД на 1-2 прохода а потом на остальные цифры ЛСД? Я предполагаю, что этот подход будет более эффективным на практике из-за того, как вы будете обращаться к памяти. - person jsguy; 25.02.2015
comment
Согласен, что можно сделать много интересных оптимизаций, но проблема в том, что я не могу заставить даже простую версию нормально работать :( - person jsguy; 25.02.2015
comment
насчет индексов, это была ошибка, должно быть ведрами. Я обновил свой код, возникла проблема с переформатированием, извините. - person jsguy; 25.02.2015
comment
не могли бы вы объяснить, почему нам нужно предположение, что количество битов типа массива точно кратно numberOfBits? - person jsguy; 26.02.2015
comment
Большое спасибо за вашу помощь, я ценю это. Я добавил дополнительный оператор if, и он отлично работает для всех смен. Кажется, что этот массив сегментов на самом деле вызывает много проблем......... к сожалению, я не думаю, что мы можем оптимизировать его дальше, чтобы избежать создания всех этих копий сегментов каждый раз, когда мы спускаемся на наши уровни рекурсии. - person jsguy; 26.02.2015
comment
Я обновил пример, чтобы исключить копии, но проблема не в этом. Я объясняю проблему, используя аналогию с сортировщиком карт в ответе выше. - person rcgldr; 26.02.2015