为啥当我实现以 2^20 为底的基数排序以对大小为 500 万的数组进行排序时,该程序会陷入无限循环?

Posted

技术标签:

【中文标题】为啥当我实现以 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 为底的基数排序以对大小为 500 万的数组进行排序时,该程序会陷入无限循环? 【发布时间】:2020-04-29 07:03:40 【问题描述】:

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

用户输入 2 个数字,n 和 q。 我们将前 n 个斐波那契数和它们中的每一个与 q 取模,并将取模结果放在 arr 中。所以 arr 现在有 n 个元素。直到这里程序运行良好。 我们现在必须使用基数排序对 arr 进行排序。当测试用例很小时,例如 n=5 q=100、n=15 q=13、n=1000000 q=1000000,基数排序工作得很好,我得到了正确的输出。 当 n=5000000 和 q=1000000000(数组长度为 5000000)且数组中最大数为 999999973 时,程序在排序时进入无限循环。 排序排序后,有一些模算术计算可以忽略,因为它们工作正常且没有错误。

有人可以帮我检查我的排序算法吗?同样,现在我选择基数为 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;
    

【问题讨论】:

m 的定义是什么?看起来n 每次运行时都会以 2^20 倍的倍数呈指数级增长,这可能会很快变得非常巨大。 @tadman m 是数组中的最大数。抱歉,我发布了部分代码。有一个函数可以找到数组中的最大数并将其存储在 m 这个问题有太多的“想象”和“假设”。请发帖minimal reproducible example。 非常感谢您的评论。我将根据您的要求编辑问题 删除然后重新发布基本上相同的问题会将您推向问题禁令。可能退后一步,弄清楚您是否可以将问题简化为多个较小的问题,并一次发布一个,它们之间有足够的时间来消化您在每个步骤中获得的 cmets 和答案。可能还会在How to ask.附近查看help center 【参考方案1】:

无限循环问题就在这一行

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

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

您之前问题的另一个问题是 (i + 1) * arr[i] 可以大于 32 位。前面的问题将 sum 定义为 long long int。该代码也可以将 i 定义为 long long int (或者它可以在进行乘法之前使用强制转换)。在 cmets 中记录的修复。我不知道 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 位,那么这就是一个潜在的问题:

    fib = (first + second) % q;

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


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


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

【讨论】:

非常感谢!对于 n=5000000 q=1000000000,它现在可以正常工作。如果你不介意的话,我还有一个问题。假设我有一个包含 n 个元素的数组,并且我知道这个数组中最大的元素是 m。根据这些信息,我如何选择基数排序的基数(基本上是移位变量),这将节省时间和内存? 不要堆积。如果您无法弄清楚,请提出一个新问题。 @rcgldr 我必须向在线法官提交此问题,但我无法找到该在线法官的详细信息。我知道已经为这个问题分配了 87500 KB。我必须根据 arr 和 arr 中的最大元素有效地更改每次试验中基数排序的基数 @CS99 - 请作为一个单独的问题询问基数排序基础。 注明。对任何不便表示歉意

以上是关于为啥当我实现以 2^20 为底的基数排序以对大小为 500 万的数组进行排序时,该程序会陷入无限循环?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Redshift 中计算以 2 为底的对数

将一个以 10 为底的数字转换为 N

实现以(-2)代替2为底的二进制表示

给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字

SPSS非线性回归中输入以2为底的对数和以2为底的指数

如何在任意基数中创建数字的零填充字符串表示? [复制]