【问题标题】:Can't get the radix sort algorithm to work in C++无法让基数排序算法在 C++ 中工作
【发布时间】:2015-04-27 21:56:06
【问题描述】:

给定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 位,我右移@987654329 @ 位置,然后我应用 AND 操作来获得 shift 最低有效位,这正是我想要的位。

第二个问题是递归的。我不认为我在正确的组上递归,但由于我不知道第一个问题是否真的得到了正确解决,目前我不能说更多。

我们将不胜感激。

提前谢谢你。

编辑:添加主函数以显示我的基数函数是如何被调用的。

【问题讨论】:

  • 如果您替换分配 (int buckets = new int[...];)、随后的初始化循环和最终的 delete[] buckets;(如果您使用 std::vector&lt;int&gt; buckets(...);),您可以节省几行输入。此外,它将使您的代码异常安全,并且您将获得针对越界索引操作的可选检查。我不记得曾经在 C++ 中使用过数组 new,一个常规向量是更好的选择。

标签: c++ algorithm sorting recursion radix


【解决方案1】:

另一个更新,转换为数组类型的模板。 Tmp 数组现在作为参数传递。复制步骤被消除,并添加了一个辅助函数来返回排序数据最终所在的缓冲区。用 400 万个 64 位无符号整数进行测试,它可以工作,但速度很慢。 numberOfBits = 4 时达到的最快时间。numberOfBits 不再需要精确除以每个元素的位数。

为了解释为什么 MSD 首先很慢,我将使用卡片分类器进行类比。想象一下,您有 1,000 张卡片,每张卡片都有 3 位数字,从 000 到 999,以随机顺序排列。通常,您使用第 3 位数字通过分拣机,最终每个箱中都有 100 张卡片,箱 0 存放带有“0”的卡片,...存放箱 9 存放带有“9”的卡片。然后,您将 bin 0 和 bin 9 中的卡片连接起来,并使用第 2 位和第 1 位再次通过分类器运行它们,从而生成一组已排序的卡片。这是 3 次运行,每次运行 1000 张卡片,因此共有 3000 张卡片通过了分拣机。

现在再次从随机排序的卡片开始,按第 1 位排序。您不能连接这些集合,因为具有较高第 1 位数字但较低第 2 位数字的卡最终会乱序。所以现在你必须运行 10 次,每次运行 100 张卡片。这将产生 100 组,每组 10 张卡片,您再次通过分类器运行它们,从而产生 1000 组每组 1 张卡片,现在卡片已分类。所以通过分拣机的卡片数量仍然是 3,000,与上述相同,但您必须运行 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;
}

【讨论】:

  • 我有最不重要的版本,就像你说的那样,它只是一个 for 循环,但我想在最重要的版本上运行一些实验,(据我所知需要递归),以便感受一下缓存的性能。我认为最高有效数字基数排序将执行最低有效基数排序,但我想进行一些实验,看看我是对还是错。在 LSD 版本启动并运行后,MSD 版本让我非常头疼……不幸的是,我不太擅长移位。
  • 但是连接会自动发生对吧?就像合并排序一样,您对子问题进行排序,但每个子问题都将成为您输入数组的一部分。我的 LSD 版本未优化,但我想启动并运行 MSD 版本以查看缓存行为。另外,如果将 MSD 和 LSD 结合在一起会怎样?比如,运行 MSD 1-2 次,然后运行其余数字 LSD?我想由于您访问内存的方式,这种方法在实践中会更有效。
  • 我同意可以做很多有趣的优化,但问题是我什至无法让一个简单的版本正常工作:(
  • 关于索引,搞错了,一定是“buckets”。我更新了我的代码,在重新格式化时遇到了问题,抱歉。
  • 你能解释一下为什么我们需要假设“假设数组类型的位数是 numberOfBits 的精确倍数”吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-07
  • 1970-01-01
相关资源
最近更新 更多