25.3 优化时要避免出现新的错误

一旦对程序进行了优化,就要重新测试程序,以便因优化考虑不周带来新的问题。当然,有些局部优化也可能影响全局的效果。总之,不能认为只是局部优化就无需测试验证。

【例25.3】将1~100内的所有素数存入数组,然后输出全部素数、最大素数和素数数量。

“素数”,又称“质数”,是指除1和其自身之外,没有其他约数的正整数。如2,3,5,7,11,13,17,19,23,29等。2是最小的质数,也是唯一的偶质数。与素数对应的是“合数”,合数是除1和其自身之外,仍有其他约数的正整数。并且规定0既不是质数,也不是合数。数字1很特殊,也不称为质数。著名的高斯“唯一分解定理”是说任何一个整数都可以写成一串质数相乘的积。

根据质数的定义,可以编写出满足要求的程序。


#include <stdio.h>
int main
()
{
      int i=0
, j=-1
, a[50]
;
      int num=100
;
      for
(num=1
;num<=100
;num++
)
      {
            for
(i=2
;i<=num
;i++
)
            {
                 if
(num % i == 0
)
                      break
;
            }
            if
(i == num
)
            {
               ++j
;
               a[j] = num
;
            }
      }
      for
(i=1
; i<=j+1 
;i++
)
      {
          printf
(\"%2d \"
, a[i-1]
);
          if
( i % 5 == 0 
) printf
(\"n\"
);
      }
      printf
(\"n
最大素数是%dn\"
,a[j]
);
      printf
(\"
有素数%d
个n\"
,j+1
);
      return 0
;
}
  

程序运行结果如下:


2  3  5  7  11
13 17 19 23 29
31 37 41 43 47
53 59 61 67 71
73 79 83 89 97
最大素数是97
有素数25
个
  

【分析】程序中有许多是无效的运算,第1个循环的循环次数至少可以减少一半,显然,第2个循环语句的循环次数至少也可以与第1个循环一样减少一半,再细推敲,将该数的平方根取整作为循环次数即可。


//
优化的程序
#include <stdio.h>
#include <math.h>
int main
()
{
       int i=0
, j=-1
, a[50]
;
       int num=0
, temp=0
;
       for
(num=1
; num<=100
; num += 2
)     //
步长为2
,剔除偶数
       {
          temp=
(int
)sqrt
(num
);          //
循环限制为其平方根取整
          for
(i=2
; i<=temp
; i++
)
          {
               if
(num %  i == 0
)          //
排除合数
                    break
;
          }
          if
(i == temp+1
)
          {
                ++j
;               //
质数计数
                a[j] = num
;          //
将质数存入数组
          }
       }
       //
输出结果
       for
(i=1
; i<=j+1 
;i++
)
       {
             printf
(\"%2d \"
, a[i-1]
);
             if
( i % 5 == 0 
)  printf
(\"n\"
);
       }
       printf
(\"n
最大素数是%dn\"
,a[j]
);
       printf
(\"
有素数%d
个n\"
,j+1
);
       return 0
;
}
  

程序运行结果如下:


1  3  5  7  11
13 17 19 23 29
31 37 41 43 47
53 59 61 67 71
73 79 83 89 97
最大素数是97
有素数25
个
  

如果只是求素数数量和最大素数,优化成功。但程序要求输出素数,则就存在错误了。从程序输出结果可见,少了素数2,多了数字1。

由此可见,在优化时,正确的做法是每修改一处都要立即进行验证。一定不要怕麻烦,就像排错一样,每改一处必须从头测试。

现在是多次优化的结果,产生错误的地方就应从头开始验证了。

程序从引入temp变量入手,第1个循环的步长可以先修改,然后使用temp变量。


//
第1
次优化及其运行结果
#include <stdio.h>
#include <math.h>
int main
()
{
       int i=0
, j=-1
, a[50]
;
       int num=0
, temp=0
;
       for
(num=1
; num<=100
; num += 2
)
       {
           temp=num-1
;
           for
(i=2
;i<=temp
;i++
)
           {
                if
(num % i == 0
)
                     break
;
           }
           if
(i == temp+1
)
           {
                ++j
;
                a[j] = num
;
           }
      }
      for
(i=1
; i<=j+1 
;i++
)
      {
           printf
(\"%2d \"
, a[i-1]
);
           if
( i % 5 == 0 
)  printf
(\"n\"
);
      }
      printf
(\"n
最大素数是%dn\"
,a[j]
);
      printf
(\"
有素数%d
个n\"
,j+1
);
      return 0
;
}
3  5  7  11 13
17 19 23 29 31
37 41 43 47 53
59 61 67 71 73
79 83 89 97
最大素数是97
有素数24
个
  

从输出结果可知,少了一个素数2。与上一个优化相比,那个程序是少了素数2,多了数字1。由此可推知多的数字1是由取平方根引起的。分析一下可知,num为1和2时,temp均为1,由此造成多了个1。针对这两个问题采取相应对策即可。


//
正确优化的程序
#include <stdio.h>
#include <math.h>
int main
()
{
      int i=0
, j=0
, a[50]
;
      int num=100
,temp=0
;
      a[0]=2
;                    //
找回2
      for
(num=1
;num<=100
; num += 2
)
      {
            temp=
(int
)sqrt
(num
);
            for
(i=2
;i<=temp
;i++
)
            {
                    if
(num % i == 0
)
                         break
;
            }
            if
(i == temp+1
)
            {
                  if
( num 
!= 1 
)          //
排除1
                  {
                       ++j
;
                       a[j] = num
;
                  }
              }
        }
        for
(i=1
; i<=j+1 
;i++
)
        {
             printf
(\"%2d \"
, a[i-1]
);
             if
( i % 5 == 0 
) printf
(\"n\"
);
       }
       printf
(\"n
最大素数是%dn\"
,a[j]
);
       printf
(\"
有素数%d
个n\"
,j+1
);
       return 0
;
}
  

这个算法每次都要调用sqrt函数,花费时间较多。下面的优化减少了调用次数,改善了程序性能。因为增加了prime函数,结构化也更好些。


//
减少开平方次数的算法
#include <stdio.h>
#include <math.h>
int prime
(int num
);
int main
()
{
    int i=0
,  j=0
,  a[50]
;
    int num=100
;
    for
(num=1
; num<=100
; num = num+2
)
    {
          if 
( num == 1 
)
          {
                a[j]=2
;
                j++
;
                continue
;
          }
          if 
( prime
(num
) 
)
          {
                  a[j] = num
;
                  ++j
;
          }
     }
     for
(i=1
; i<j+1 
;i++
)
     {
          printf
(\"%2d \"
, a[i-1]
);
          if
( i % 5 == 0 
) printf
(\"n\"
);
     }
     printf
(\"n
最大素数是%dn\"
,a[j-1]
);
     printf
(\"
有素数%d
个n\"
,j
);
     return 0
;
}
int prime
(int num
)
{
     int temp=0
, i=0
;
     if
(num % 2 == 0
)          //
多余,见下节
          return 
( num == 2
);
     if
(num % 3 == 0
)
          return 
( num == 3
);
     if
(num % 5 == 0
)
          return 
( num == 5
);
     temp = 
(int
)sqrt
(num
);
     for
(i=7
; i<=temp
; i=i+2
)
           if
(num % i == 0
)
                return 0
;
     return 1
;
}
  

如果把数组改为a[200],num=1000,则得到最大素数是997,有素数168个。第1个素数是2,全部正确。

如果使用乘法代替开平方,速度会更快。下面是求1~1000的质数的程序。


//
使用乘法的算法
#include <stdio.h>
#include <math.h>
int prime
(int num
);
int main
( 
)
{
     int i=0
, j=0
, a[200]
;
     int num=100
;
     for
(num=1
;  num<=1000
;  num = num+2
)
     {
          if 
( num == 1 
)
          {
                a[j]=2
;
                j++
;
                continue
;
          }
          if 
( prime
(num
) 
)
          {
                a[j] = num
;
                ++j
;
          }
     }
     for
(i=1
; i<j+1 
;i++
)
    {
        printf
(\"%2d \"
, a[i-1]
);
        if
( i % 5 == 0 
) printf
(\"n\"
);
    }
    printf
(\"n
最大素数是%dn\"
,a[j-1]
);
    printf
(\"
有素数%d
个n\"
,j
);
    return 0
;
}
int prime
(int num
)
{
     int temp = 0
,  i = 0
;
     if
(num % 2 == 0
)                  //
多余
          return 
( num == 2
);
     if
(num % 3 == 0
)
          return 
( num == 3
);
     if
(num % 5 == 0
)
          return 
( num == 5
);
     for
(i=7
; i*i<=num
; i=i+2
)
          if
(num % i == 0
)
               return 0
;
     return 1
;
}
  

由此可见,不同算法的效率大不一样,优化时一定要仔细考虑,选取效率高的算法。这也是为什么总是建议先编写一个正确求解的程序,在编写过程中不要急于优化,要把注意力集中在正确求解,而把优化放在后面。如上所示,后面两种算法将判定质数提取出来作为一个函数,在函数里集中解决算法效率。

优化时出现漏解和多解也是正常的事情,关键是分析如何补救。其实,上面的程序含有多余的语句。


if
(num % 2 == 0
)
     return 
( num == 2
);
  

的目的是使num=2时,不会被丢掉(2是质数)。其实在主程序里,循环取值是奇数,而且已经补上a[0]=2,所以这个判别是不起作用的,可以删除。

《C语言解惑》