以优化嵌套循环

to optimize the nested loops

本文关键字:嵌套循环 优化      更新时间:2023-10-16
for( a=1; a <= 25; a++){
num1 = m[a];
for( b=1; b <= 25; b++){
num2 = m[b];
for( c=1; c <= 25; c++){
num3 = m[c];
for( d=1; d <= 25; d++){
num4 = m[d];
for( e=1; e <= 25; e++){
num5 = m[e];
for( f=1; f <= 25; f++){
num6 = m[f];
for( g=1; g <= 25; g++){
num7 = m[g];
for( h=1; h <= 25; h++){
num8 = m[h];
for( i=1; i <= 25; i++){
num = num1*100000000 + num2*10000000 +
num3*  1000000 + num4*  100000 +
num5*    10000 + num6*    1000 +
num7*      100 + num8*      10 + m[i];
check_prime = 1;
for ( y=2; y <= num/2; y++)
{
if ( num % y == 0 )
check_prime = 0;
}
if ( check_prime != 0 )
{  
array[x++] = num;  
}
num = 0;  
}}}}}}}}}

上面的代码花了很多时间来完成执行。。事实上,它甚至还没有完成执行,我能做些什么来优化循环并加快执行速度??我是cpp的新手。

将此代码替换为使用合理算法的代码,例如Eratosthenes筛。最重要的"优化"首先是选择正确的算法。

如果你的数字排序算法是随机交换,直到它们有序,那么你对随机条目的选择、交换或检查它们是否有序进行了多少优化都无关紧要。不管怎样,糟糕的算法都意味着糟糕的性能。

您正在检查25个9=3814697265625数字是否为素数。这是很多主要的测试,而且总是需要很长时间。即使在最好的情况下(为了性能),当所有数组条目(在m中)都为0时(不要介意测试将0视为素数),这样试除法循环就永远不会运行,运行也需要数小时。当m的所有条目都是正数时,代码将按原样运行数百或数千年,从那时起,每个数字将被50000000多个数字试除。

查看基本检查,

check_prime = 1;
for ( y = 2; y <= num/2; y++)
{
if ( num % y == 0 )
check_prime = 0;
}

第一个明显的低效率是,即使在已经找到除数并且CCD_一旦知道结果,就立刻跳出循环

check_prime = 1;
for ( y = 2; y <= num/2; y++)
{
if ( num % y == 0 )
{
check_prime = 0;
break;
}
}

不幸的是,你测试的所有数字都是素数,这不会改变任何事情,但如果所有(或几乎所有,对于足够大的值几乎)的数字都是复合的,那么运行时间将至少减少5000倍。

下一件事是,你把它分成num/2。这是没有必要的。为什么你在num/2停,而不在num - 1停?因为你知道num的最大正约数不能大于num/2,因为如果(num >) k > num/2,那么2*k > numnum不是k的倍数。

这很好,不是每个人都看到了。

但你可以更进一步。如果num/2num的除数,则意味着num = 2*(num/2)(使用整数除法,num = 3除外)。但是num是偶数,它的复合性已经由除以2决定,所以如果num/2除法成功,就永远不会尝试。

那么,下一个需要考虑的最大除数的可能候选者是什么呢?当然是num/3。但如果这是num的除数,那么num = 3*(num/3)(除非num < 9)和除以3已经解决了这个问题。

继续,如果k < √numnum/knum的除数,那么num = k*(num/k)和我们看到num有一个较小的除数——即k(可能更小)。

因此num的最小非平凡除数小于或等于√num因此,循环只需要为y <= √numy*y <= num运行。如果在该范围内没有找到除数,则num是素数。

现在问题来了,是否循环

for(y = 2; y*y <= num; ++y)

root = floor(sqrt(num));
for(y = 2; y <= root; ++y)

第一个需要在每次迭代中对循环条件进行一次乘法运算,第二个需要在循环外对平方根进行一次计算。

哪个更快?

这取决于num的平均大小,以及许多是否为素数(更准确地说,取决于最小素数的平均大小)。计算平方根需要比乘法长得多的时间,为了补偿这一成本,循环必须运行多次迭代(平均而言)——比如说,"多次"是指超过20次、超过100次还是超过1000次,这取决于具体情况。由于num大于10^8,可能与这里的情况一样,计算平方根可能是更好的选择。

现在,我们已经将试除法循环的迭代次数限制为√num,无论num是复合的还是素数,并且将运行时间减少了至少5000倍(假设所有num0,所以总是num >= 10^8),而不管测试的数字中有多少素数。如果num取的大多数值都是具有小素数因子的复合物,则折减因子要大得多,在正常情况下,运行时间几乎完全用于测试素数。

可以通过减少除数候选的数量来获得进一步的改进。如果num可被4、6、8、…整除。。。,那么它也可以被2整除,所以即使对于y > 2num % y也不会产生0。这意味着所有这些分歧都是多余的。通过特殊的外壳2并在2的步骤中递增除数候选,

if (num % 2 == 0)
{
check_prime = 0;
} else {
root = floor(sqrt(num));
for(y = 3; y <= root; y += 2)
{
if (num % y == 0)
{
check_prime = 0;
break;
}
}
}

要执行的除法数量和运行时间大致减半(假设有足够多的坏情况,偶数的工作量可以忽略不计)。

现在,每当y是3的倍数(而不是3本身)时,只有当num不是3的倍数时才会计算num % y,因此这些除法也是多余的。您还可以通过特殊的大小写3来消除它们,并让y只遍历不能被3整除的奇数(从y = 5开始,交替递增2和4)。这减少了大约三分之一的剩余工作(如果有足够多的坏案例)。

继续这个消去过程,我们只需要将num除以不超过√num素数,就可以确定它是否是素数。

因此,通常情况下,最好找到不超过最大num平方根的素数,将其存储在数组中并循环

root = floor(sqrt(num));
for(k = 0, y = primes[0]; k < prime_count && (y = primes[k]) <= root; ++k)
{
if (num % y == 0)
{
check_prime = 0;
break;
}
}

除非num可以取的最大值足够小,否则,例如,如果您总是有num < 2^31,那么你应该在比特筛中找到这个极限的素数,这样你就可以在恒定时间内查询num是否是素数(如果你只有奇数的标志,2^31比特的筛需要256MB)[需要特殊的大小写来检查num是否是偶数],您只需要128 MB就可以在恒定时间内检查数字< 2^31的素性,可以进一步减少筛子所需的空间)。

到目前为止,主要测试本身。

如果m数组包含可被2或5整除的数字,则可能值得对循环进行重新排序,将i的循环放在最外面,如果m[i]可被2和5整除,则跳过内部循环-所有其他数字在相加之前都乘以10的幂,因此num将分别是2的倍数。5而不是素数。

但是,尽管如此,运行代码仍然需要很长时间。九个嵌套循环散发着错误设计的臭味。

你想做什么?也许我们可以帮助找到正确的设计。

我们可以通过计算数字的每个部分来消除大量冗余计算。这也显示了在2-3个轮子上进行的初等性试验划分测试,直到一个数字的平方根:

// array m[] is assumed sorted in descending order      NB!
// a macro to skip over the duplicate digits
#define I(x) while( x<25 && m[x+1]==m[x] ) ++x;
for( a=1; a <= 25; a++) {
num1 = m[a]*100000000; 
for( b=1; b <= 25; b++) if (b != a) { 
num2 = num1 + m[b]*10000000;
for( c=1; c <= 25; c++) if (c != b && c != a) {
num3 = num2 + m[c]*1000000; 
for( d=1; d <= 25; d++) if (d!=c && d!=b && d!=a) {
num4 = num3 + m[d]*100000; 
for( e=1; e <= 25; e++) if (e!=d && e!=c && e!=b && e!=a) {
num5 = num4 + m[e]*10000;
for( f=1; f <= 25; f++) if (f!=e&&f!=d&&f!=c&&f!=b&&f!=a) {
num6 = num5 + m[f]*1000;
limit = floor( sqrt( num6+1000 ));   ///
for( g=1; g <= 25; g++) if (g!=f&&g!=e&&g!=d&&g!=c&&g!=b&&g!=a) {
num7 = num6 + m[g]*100; 
for( h=1; h <= 25; h++) if (h!=g&&h!=f&&h!=e&&h!=d&&h!=c&&h!=b&&h!=a) { 
num8 = num7 + m[h]*10; 
for( i=1; i <= 25; i++) if (i!=h&&i!=g&&i!=f&&i!=e&&i!=d
&&i!=c&&i!=b&&i!=a) {
num = num8 + m[i];
if( num % 2 /= 0 && num % 3 /= 0 ) {
is_prime = 1;
for ( y=5; y <= limit; y+=6) {
if ( num %  y    == 0 ) { is_prime = 0; break; }
if ( num % (y+2) == 0 ) { is_prime = 0; break; }
}
if ( is_prime ) { return( num ); } // largest prime found
}I(i)}I(h)}I(g)}I(f)}I(e)}I(d)}I(c)}I(b)}I(a)}

此代码还消除了重复的索引。正如您在评论中所指出的,您可以从5x5网格中选择您的数字。这意味着您必须使用所有不同的索引。这将使要测试的数字计数从25^9 = 3,814,697,265,625减少到25*24*23*...*17 = 741,354,768,000

由于您现在已经指出m[]数组中的所有条目都小于10,因此肯定存在重复项,在搜索时需要跳过这些项。正如Daniel所指出的,从顶部搜索,第一个找到的素数将是最大的。这是通过按降序对m[]数组进行预排序来实现的。