《C语言大学教程(第八版)》第五章 课后习题 5.27 参考答案

前言
  这是一到非常有意思的题目,可以开阔思维。
误区
  在一开始遇到这道题目时,按照自己的思路,写出了自己的方法。
在这里插入图片描述
  代码如下:

#include <stdio.h>
#include <math.h>

int isPrime(int number);

int main(void)
{
    int a;

    a = 0;

    for (int i = 1; i <= 10; i++)
    {
        if (isPrime(i) == 1)
        {
            a += 1;
            printf("isPrime: %d\n", i);
        }
    }

    printf("Num: %d", a);
}

int isPrime(int number)
{
    if (number == 2 || number == 3)
    {
        return 1;
    }
    
    if (number != 1 && number % 2 != 0 && number % 3 != 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

  因为,看到了,题目中的描述“例如,2、3、5、和7是素数,4、6、8和9就不是素数。
在这里插入图片描述
  于是,我在上述代码中,把条件写为小于等于10,方便查看代码执行结果。运行代码,结果如下:在这里插入图片描述
  当时,看到这个运行结果,符合题目中的描述。然后,把条件改写为题目中(b)项的要求:打印1~10000之间所有的素数。代码运行结果如下:
在这里插入图片描述
  就在我以为一切都在掌握之中的时候,直到,我看到了这道题目的©项内容:起初你也许会认为,为确定n是否是素数,需要判断的上限是n/2。但事实上,只需要判断到n的平方根即可。修改程序使其能够以上面两种方法运行,并评价后一种方法带来的性能改善。
在这里插入图片描述

  读完上面这段话,我整个人,当场就傻了,我完全搞不明白,为什么会扯到n/2和n的平方根,我根本不知道作者在讲什么,我甚至一度自信的以为,自己找到了更流弊的解法。
  可是,我转念一想,我为什么学习这本教材,就是为了把当年落下的东西补回来。而且,这一次,不想自己欺骗自己,不嫌麻烦,把遇到的每个问题都搞懂,不留漏洞,把基础打扎实。因此,我回过头来,静下心想了想:假设作者不会出错,那作者既然讲到了n/2和n的平方根,那作者一定有其用意,我没有能够想到作者提到的这两点,那自然是自身还有所不足,那就跟着作者的思路,去尝试着把心中的疑问解决一下。至此,有了后续的故事。
探索
  虽然,上面已经对自己的心态进行了调整。可是,真正去朝着作者的思路去思考时,内心不知道怎么回事,有一股气,很不爽,我总觉得自己的方法不错,为啥还要再来搞这个。于是,我起身上了趟厕所,转移一下注意力,缓解一下内心的压抑。回来后,重新调整心态,开始思考。
  首先,我想不明白的地方,为什么作者会提到:起初你也许会认为,为确定n是否是素数,需要判断的上限是n/2。我心里想着,先把这一部分,用代码实现了。可我偏偏就是想不明白,这个n/2是怎么成为判断的上限的。
  追本溯源,想不通这个n/2,就从这道题的本身出发,题目要求我们找到素数,再根本一些,从什么是素数,即从素数的概念出发:
  1.素数(也称质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
  2.合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。
  3.素数(也称质数)相对的是合数,1既不属于素数(也称质数)也不属于合数。
  先把这些基本的概念搞清楚,再来接着思考,为什么作者会提到n/2。
  其实,我在书写这篇文章的时候,已经是把所有问题都思考明白之后的事情了。现在回想起来,我当时到这一步,依然想不明白n/2的一个原因是:我依然在想着,我上面提到那个,我自己的解题方法。即,我当时到这一步,依然没有看到,我上面自己的解题方法,存在着明显的错误。
  后面,在网上查找了一番,又想了好一会儿,想到了为什么作者会提到n/2。
  当遇到一个数,在找寻它的因子时,因子必然是成对出现的,举例:数字6可以被2整除商为3,2是6的因子。同时,数字6也可以被3整除商为2,3也是6的因子。这听上去,像是一句废话。可是,这却是理解作者提到n/2的关键。因子必然是成对出现,成对,在数学上意味着2个。由此,可以联想到二分法
解决
  接着上面的思路往下想,既然联想到了二分法,那什么是二分法,简单理解:一分为二。那要如何一分为二,先用简单的理解方法:平均分配作为分割线,即n/2。到此,终于想通了作者为什么要提及n/2。
  以代码的形式,实现作者提及的以n/2为判断上限:

#include <stdio.h>
#include <math.h>

int isPrime(int number);

int main(void)
{
    int a;

    a = 0;

    for (int i = 1; i <= 10; i++)
    {
        if (isPrime(i) == 1)
        {
            a += 1;
            printf("isPrime: %d\n", i);
        }
    }

    printf("Num: %d", a);
}

int isPrime(int number)
{
    if (number <= 1)
    {
        return 0;
    }
    
    for (int i = 2; i <= number/2; i++)
    {
        if (number % i == 0)
        {
            return 0;
        }
    }

    return 1;
}

  同理,先把条件设置为10,方面查看代码运行结果:
在这里插入图片描述
  结果一致。
  接着,再来想,为什么作者在此基础上,又提到了“但事实上,只需要判断到n的平方根即可。
  结合上面提到的:因子必然是成对出现的,以这个作为思路的基础,再往下想。
  友情提示,下面的一段陈述,可能理解起来较为麻烦,不用担心,先通读一遍,后续会跟上图片解析,方便理解。然后,可以回过头来,重新阅读下面的这段陈述,以此加深理解记忆。
  我们知道素数是只能被1和自身整除的大于1的整数。为了判断一个数n是否为素数,我们需要找到能整除n的因子。
  假设n有一个大于其平方根的因子m,即m > sqrt(n)。那么,可以得出另一个因子为n/m,因为因子必然是成对出现的,而它必然小于sqrt(n)。如果n可以被m整除,即n % m == 0。那么,n/m就是一个小于sqrt(n)的因子,也能整除n。
  换句话说,如果一个数n不是素数,它一定可以分解成两个因子,其中一个因子小于等于sqrt(n),另一个因子大于等于sqrt(n)。所以,如果在sqrt(n)之前没有找到能整除n的因子,那么在sqrt(n)之后也不会再有,因为因子必然是成对出现的
  因此,在素数判断算法中,我们只需要遍历2到sqrt(n)之间的数来检查是否存在能整除n的因子。如果存在这样的因子,那么n不是素数;如果在这个范围内没有找到能整除n的因子,那么n是素数。
  通过将循环的上限设置为该数的平方根,可以有效地缩小搜索范围,提高算法的效率。在实际应用中,这种优化对于较大的数尤为重要,因为它可以减少不必要的计算量。
  上面,我们提到了二分法,提到了简单的理解方法:以平均分配作为分割线,进行二分法。这里,我们再次对二分法的分割线进行了修改,以平方根作为分割线,进行二分法
  我们可以进行简单的计算验证,一个数的平方根是小于等于一个数的二分之一。因此,以平方根作为分割线,可以减少判断的范围
  文字描述看起来比较不容易理解,画几张简单的图:
  以平均分配作为分割线,进行二分法,只有以下一种情况:
在这里插入图片描述
  以平方根作为分割线,进行二分法,有两种情况:
  情况一:例如,数字4。
在这里插入图片描述
  情况二:其余数字。
在这里插入图片描述  从上述两张简单的图片中,能够清晰的看到,以平方根作为分割线的前半部分,要小于等于,以平均分配作为分割线的前半部分。因此,减少了判断的范围,提升了效率。
  此处,可能会有疑问:有些数字平方根无法取整,怎么办。这个没关系,我们直接取整即可。因为,我们本身找寻的素数就是整数。
  以代码的形式,实现作者提及以n的平方根为判断上限:

#include <stdio.h>
#include <math.h>

int isPrime(int number);

int main(void)
{
    int a;

    a = 0;

    for (int i = 1; i <= 10; i++)
    {
        if (isPrime(i) == 1)
        {
            a += 1;
            printf("isPrime: %d\n", i);
        }
    }

    printf("Num: %d", a);
}

int isPrime(int number)
{
    if (number <= 1)
    {
        return 0;
    }

    for (int i = 2; i <= sqrt(number); i++)
    {
        if (number % i == 0)
        {
            return 0;
        }
    }

    return 1;
}

  同理,先把条件设置为10,方面查看代码运行结果:
在这里插入图片描述
  此时,我们把n/2和n的平方根,两种方法的判断条件修改为题目中要求的1-10000,运行,以肉眼的感官,如果你的机器硬件配置本身很高的话,很难察觉出速度上的变化。因此,我们把1-10000修改为1-1000000,进行一百万次的计算。注意,要根据自身电脑的硬件配制来进行此项测试。否则,可能会导致机器卡死。此时,就能够明显感官到运行速度的提升。
  这里,我们再进行更深一步的探索,以我本人的电脑为例,当我在使用n/2的方式运行程序时,我的电脑CPU利用率在60%左右。当我在使用n的平方根运行程序时,我的电脑CPU利用率飙到了100%。
  做一个大胆的猜想:虽然,n的平方根比n/2的方式,减少了搜索范围,提升了运行速度。但是,n的平方根算法比n/2的算法,对CPU硬件的消耗也变多了。
  再做一个更进一步大胆的猜想:当我们的判断条件,所使用的算法更加复杂时,程序对硬件的消耗也会上升,拿本道题目为例,判断条件i <= sqrt(number);,显然比判断条件i <= number/2;更加消耗硬件的性能。即,以消耗硬件性能来提升程序运行速度。这块儿,隐约觉得,已经涉及到计算机别的部分了,数据结构/算法设计、程序运行效率、程序对硬件性能的消耗等等因素之间的关系。
  这就又引发了一个思考,当我们还是学校的学生时,我们想的更多的是,能写出解决问题的代码即可,更多的是理论层面。当我们出社会之后,对我们的要求是,不仅要写出解决问题的代码,同时还要考虑,所写代码对于硬件性能方面的损耗,理论与实际的一种结合。因此,编程,不仅仅是会打两行代码这么简单。随着学习不断地深入,会感到要考虑的问题会很多。
  以上两个大胆的猜想以及引发的思考,仅是我个人当前阶段所学知识的猜想而已,仅作为学习过程中的有感而发,定位只是学习交流而已。
  最后,我们回过头来,重新审视一下,我在最开始,自己写的那个解题方法:

#include <stdio.h>
#include <math.h>

int isPrime(int number);

int main(void)
{
    int a;

    a = 0;

    for (int i = 1; i <= 10000; i++)
    {
        if (isPrime(i) == 1)
        {
            a += 1;
            printf("isPrime: %d\n", i);
        }
    }

    printf("Num: %d", a);
}

int isPrime(int number)
{
    if (number == 2 || number == 3)
    {
        return 1;
    }
    
    if (number != 1 && number % 2 != 0 && number % 3 != 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

  这时,我们就能很明显的发现其问题所在,这一个判断语句if (number != 1 && number % 2 != 0 && number % 3 != 0),会丢失很多情况,比如:数字25,就会被漏掉,产生误判。
  一道课后练习题,给到的感悟很多,学习编程,还是需要戒骄戒躁,怀着一颗学徒的心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值