优化嵌套循环

时间:2012-09-04 10:30:36

标签: c++ primes

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的新手。

3 个答案:

答案 0 :(得分:6)

使用合理的算法(例如Sieve of Eratosthenes)将此代码替换为代码。最重要的“优化”是首先选择正确的算法。

如果您对数字进行排序的算法是随机交换它们直到它们按顺序排列,那么优化选择随机条目,交换它们或检查它们是否有序无关紧要。糟糕的算法无论如何都意味着糟糕的表现。

答案 1 :(得分:1)

您是否正在检查25 9 = 3,814,697,265,625这些数字是否为素数。这是很多主要测试,并且总是需要很长时间。即使在最佳情况下(性能方面),当所有数组条目(在m中)为0时(更不用说测试认为0是素数),因此试验分割循环永远不会运行,运行将花费数小时。当m的所有条目都是正数时,代码将会运行数百年或数千年,从那以后每个数字将被试验除以超过50,000,000个数字。

查看主要检查,

check_prime = 1;

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

第一个明显的低效率是即使在找到除数并且num的复合度建立之后,循环也会继续。 一旦知道结果,就会突破循环。

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的除数较小,即{ {1}}(可能更小)。

所以 k的最小非平均除数小于或等于num因此,循环只需运行√num或{{1 }}。如果在该范围内未找到除数,则y <= √num为素数。

现在问题是否循环

y*y <= num

num

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

哪个更快?

这取决于for(y = 2; y*y <= num; ++y) 的平均大小以及是否有许多是素数(更确切地说,是最小素数的平均大小)。计算平方根需要比乘法更长的时间,以补偿成本,循环必须运行多次迭代(平均) - 无论是多少&#34;很多&#34;意味着超过20,超过100或超过1000,取决于。如果root = floor(sqrt(num)); for(y = 2; y <= root; ++y) 大于num,可能就是这里的情况,可能计算平方根是更好的选择。

现在我们已经将试验分割循环的迭代次数限制为num 10^8是复合还是素数,并将运行时间减少了至少5000(假设所有{{} 1}},所以总是√num),无论测试数字中有多少素数。如果大多数值num采用的是具有小素因子的复合材料,则缩减因子要大得多,通常情况下,运行时间几乎完全用于测试质数。

通过减少除数候选者的数量可以获得进一步的改进。如果m[index] > 0可以被4,6,8,...整除,那么它也可以被2整除,因此num >= 10^8永远不会为num产生0。这意味着所有这些划分都是多余的。通过特殊的套管2并以2的步长递增除数候选,

num

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

现在,只要num % y是3的倍数(3本身除外),y > 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; } } } 不是3的倍数时计算出来,所以这些划分也是多余。您也可以通过特殊套管3消除它们,让y只运行不能被3整除的奇数(以num % y开头,交替增加2和4)。这可以减少剩余工作的大约三分之一(如果存在足够的坏情况)。

继续进行淘汰过程,我们只需将num除以{em> primes 而不超过y,以确定它是否为素数。

所以通常最好找到不超过你检查的最大y = 5的平方根的素数,将它们存储在一个数组中并循环

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; } } 是否在常数时间是素数(如果你只有奇数的标志,那么2 ^ 31位的筛子需要256 MB [需要特殊的套管来检查是否{{1}如果是偶数],你只需要128 MB就可以在恒定时间内检查数字num的素数,可以进一步减少筛子所需的空间。

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

如果num < 2^31数组包含可被2或5整除的数字,则可能值得重新排序循环,将num的循环放在最外层,并跳过内部循环{{1可以被2或5整除 - 所有其他数字在加法之前乘以10的幂,所以num将是2的倍数。 5而不是素数。

但是,尽管如此,运行代码仍需要很长时间。九个嵌套循环充满了错误的设计。

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

答案 2 :(得分:0)

我们可以通过计算数字的每个部分来消除大量的冗余计算。这也显示了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,62525*24*23*...*17 = 741,354,768,000进行测试的数字计数。

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