为啥当我实现以 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 万的数组进行排序时,该程序会陷入无限循环?的主要内容,如果未能解决你的问题,请参考以下文章