为什么我用基数 2^20 实现基数排序对 500 万大小的数组进行排序时,这个程序 运行 陷入无限循环?

Why does this program run into an infinite loop when I implement radix sort with base 2^20 to sort an array of size 5 million?

我将尝试尽可能清楚地解释问题:

有人可以帮我检查一下我的排序算法吗?同样,现在我选择基数为 2^20 进行基数排序。我们是根据什么来选择底座的?数组中最大数的长度? n=5000000 和 q=1000000000 的正确输出是 973061125(仅供参考,如果有人决定 运行 程序并检查)。

#include<iostream>
#include<algorithm>

using namespace std;


void countsort(long int* arr, long int n,long int shift) 
{
    long int* count = new long int[1048576];
    for (int i = 0; i < 1048576; i++)
        count[i] = 0;
    long int *output=new long int[n];
    long int i, last;

    for (i = 0; i < n; i++) 
    {
        ++count[(arr[i] >> shift) & 1048575];
    }
    for (i = last = 0; i < 1048576; i++)
    {
        last += count[i];
        count[i] = last - count[i];
    }

    for (i = 0; i < n; i++)
    {
        output[count[(arr[i] >> shift) & 1048575]++] = arr[i];
    }
    for (i = 0; i < n; i++)
    {
        arr[i] = output[i];
    }
    delete[] output;
    delete[] count;
}

int main()
{
    int trials = 0;
    cin >> trials;
    while (trials--)
    {
        long int n = 0;
        long int q = 0;
        cin >> n;
        cin >> q;

        long int first = 0, second = 1, fib = 0;
        long int* arr = new long int[n];
        arr[0] = second;
        long int m = 0;
        for (long int i = 1; i < n; i++)
        {
            fib = (first + second) % q;
            first = second;
            second = fib;
            arr[i] = fib;
            if (m < arr[i])
                m = arr[i];
        }
        //m is the largest integer in the array
        // this is where radix sort starts
        for (long int shift = 0; (m >> shift) > 0; shift += 20)
        {
            countsort(arr, n, shift);
        }

        long long int sum = 0;
        for (long int i = 0; i < n; i++)
        {
            sum = sum + ((i + 1) * arr[i]) % q;
        }

        sum = sum % q;
        cout << sum << endl;
    }
}

死循环问题出在这一行

    for (long int shift = 0; (m >> shift) > 0; shift += 20)

假设这是 运行 在 X86 处理器上,仅使用移位计数的低位,因此对于 32 位整数,仅使用移位的低 5 位(0 到 31)使用 count,对于 64 位整数,仅使用低 6 位(0 到 63)。大多数编译器不会补偿此限制。 (原来的8086/8088/80186没有屏蔽shift count,这个是从80286开始的)

您先前问题的另一个问题是 (i + 1) * arr[i] 可以大于 32 位。先前的问题将 sum 定义为 long long int。代码也可以将我定义为 long long int(或者它可以在进行乘法运算之前使用强制转换)。评论中指出的修复。我不知道 sum 是否应该是 % q,所以我将它保留为 long long int 值。

        for (int shift = 0; m > 0; shift += 20) // fix
        {
            countsort(arr, n, shift);
            m >>= shift;                        // fix
        }

        long long int sum = 0;                  // fix (long long)
        for (long long int i = 0; i < n; i++)   // fix (long long)
        {
            sum = sum + ((i + 1) * arr[i]) % q;
        }
        sum = sum;
        cout << sum << endl;

不知道你用的是什么编译器,所以不知道long int是32位的还是64位的。您之前的问题代码将 sum 声明为 long long int,用于在 Visual Studio 的情况下声明 64 位整数。我不知道其他编译器。如果 long int 是 32 位,那么这个 like 就是一个潜在的问题:

    fib = (first + second) % q;

因为 first + second 的和可以是负数,余数的符号与被除数的符号相同。负数将成为您使用的基数排序代码的问题。将 fib 声明为 unsigned int 或 long long int 将避免此问题。


至于选择基数,最好将所有逻辑都放在计数排序中,并将其重命名为基数排序。使用 base 2^8 并进行 4 次传递会更快(由于适合 L1 缓存的计数/索引)。正如我上面提到的,arr 和输出都应该声明为无符号整数。基数排序的方向将随着 4 次遍历中的每一次而改变:arr->output、output->arr、arr->output、output->arr,无需复制。


另一个优化是混合 MSD(最高有效数字)/LSD(最低有效数字)基数排序,用于比所有缓存大得多的数组。假设使用 base 2^8 == 256,那么第一遍创建 256 个逻辑 bin,然后每个逻辑 bin 都适合缓存,然后使用 3 LSD 基数排序遍对 256 个逻辑 bin 中的每一个进行排序。在我的系统(Intel 3770K,Win 7 Pro 64 位)上,排序 3600 万个 32 位无符号整数的时间减少了不到 6%,从 0.37 秒减少到 0.35 秒,一个递减点 returns.