一.循环
①(代码)从1加到N的和
任务描述
有数学王子之称的著名数学家高斯,和阿基米德、牛顿、欧拉并列为世界四大数学家,
一生成就极为丰硕。
![](https://i-blog.csdnimg.cn/blog_migrate/4a4d0b4d8a47e7c942ead66b7f0bd818.png)
高斯10岁的时候,数学课上老师布置了一道题数学题:从1一直加到100等于多少。全班只有高斯算出了答案5050,而且很快。起初老师并不相信高斯算出了正确答案,高斯就解释他是如何找到答案的,他发现:1+100=101,2+99=101,3+98=101,……,49+52=101,50+51=101,一共有50对和为101的数目,所以答案是50×101=5050。
现在,老师给你布置一道编程题,输出从1加到N的和。
输入格式:
一个整数N(1=<N<=100000)。
输出格式:
一个整数,从1加到N的和。
任务分析
我们都知道,从1到n的自然数是一个等差数列,首项是1,公差也是1,共n项。所以根据等差数列的求和公式S,很容易得到从1加到 n的和为 。
解法1:根据等差数列公式直接求解
#include<stdio.h>
int main()
{
int n, s;
scanf("%d", &n); //输入整数n
s = n * (n + 1) / 2; //公式直接计算从1加到n的和s
printf("%d", s); //输出结果
return 0;
}
代码测试与分析
在Dev-C++中执行:输入:1 输出:1 (测试边界数据,可能的最小值)输入:10 输出:55输入:100 输出:5050输入:1000 输出:500500输入:0 输出:0 (尽管任务中说明不可能有0,这里只为测试用)
以上代码大家一定都能理解,现在我们来讨论另一种解法,就是我们首先设置一个和变量s=0,用来存储最终的和,初值为0。然后设置计数器变量i,我们让i的值从1到n变化,每一次都把i累加到变量s里(s=s+i)。最后,变量s的值就是所求,输出即可。这就像初始时s是一个空的篮子,我们让计数器i从1数到n,每数一次就扔i个小球到篮子里。最后,篮子里的小球数量就是答案。每次扔i个小球到篮子里,这是重复操作,计数器i从1数到n是重复的次数。这里的重复操作就是循环的思想。
我们可以用下面的算法来描述从1加到n的求解过程:
(1)输入变量n,定义和变量s=0(初值),计数器变量i=1(初值);
(2)如果i<=n成立就向下执行,如果不成立转到(6);
(3)s=s+i;(4)i=i+1;(5)转到(2);(6)输出s。以上就是求解从1加到n的和算法的形式化描述,其中的步骤(3)和(4)就是被重复执行的部分,我们称为循环体。计数器变量i称为循环变量,i<=n称为循环条件。为了实现以上算法中的循环结构,C语言为我们设计了while语句、do-while语句和for语句三种循环结构,专门处理这类问题。
相关知识 while循环(当型循环)
C语言中用while语句用来实现“当型”循环结构,它的一般形式如下:while(循环条件表达式){ 循环体语句;}
(1)首先求解循环条件表达式的值,若值为真,则执行循环体,否则结束循环。
(2)每一次的循环体执行完成后,自动跳转到循环开始(while)处,再次求解循环条件表达式的值,如果成立就开始下一次循环,如此往复。
(3)循环体只能是一个语句,所以如果有多个语句,则应该用大括号将其括起来使之成为一个复合语句;如果循环体只是一个语句,大括号也可以省略。
解法2:while语句
#include<stdio.h>
int main()
{
int n, s, i; //定义变量
scanf("%d", &n); //输入整数n ---算法步骤(1)
s = 0; i = 1; //和变量s,计数器变量i赋初值 ---算法步骤(1)
while (i <= n) //满足循环条件就进入循环 ---算法步骤(2)
{
s = s + i; //将变量i的值累加到和变量s中 ---算法步骤(3)
i = i + 1; //计数器i向后计数 ---算法步骤(4)
} // ---算法步骤(5)
printf("%d", s); //输出结果 ---算法步骤(6)
return 0;
}
代码测试与分析
在Dev-C++中执行:输入:1 输出:1 (测试边界数据,可能的最小值)输入:10 输出:55输入:100 输出:5050输入:1000 输出:500500输入:0 输出:0 (尽管任务中说明不可能有0,这里只为测试用)
结合while语句的语法规则,结合上文中的算法,这个代码是不是很好理解呢?这就是循环结构的代码,让循环变量i从1变到n,循环体执行n次的代码框架,我们可以称为计次循环(循环次数固定可数),一定要牢记: i=1; //循环变量赋初值 while(i<=n){ //进入循环的条件 循环体; i=i+1; //循环变量的增量 }
相关知识 do-while循环(直到型循环)
C语言还为我们提供了do-while语句用来实现“直到型”循环结构,它的一般形式如下:
do
循环体语句;
}while(循环条件表达式);
(1)在此结构中do相当于一个标号,标志循环结构开始。
(2)首先无条件地执行一次循环体,然后求解循环条件表达式的值。若表达式的值为真,则跳转到do处再次执行循环体;若循环条件表达式的值为假则结束循环。
(3)如果循环体只有一条语句,花括号可以省略。
(4)do-while结构整体上是一条语句,所以while的括号后应加上分号。
![](https://i-blog.csdnimg.cn/blog_migrate/722bfab381b73f8d0a939bdf7952a06e.png)
以上两种循环比较,可以理解为:while循环是在入口处判断条件,do-while循环是在出口处判断条件。
解法3:do-while语句
#include<stdio.h>
int main()
{
int n, s, i;
scanf("%d", &n); //输入整数n
s = 0; i = 1; //和变量s赋初值0,计数器变量i赋初值1
do
{ //循环开始的标记
s = s + i; //将变量i的值累加到和变量s中
i = i + 1; //计数器i向后计数
} while (i <= n); //满足循环条件就再次进入循环
printf("%d", s); //输出结果
return 0;
}
代码测试与分析
在Dev-C++中执行:输入:1 输出:1 (测试边界数据,可能的最小值)输入:10 输出:55输入:100 输出:5050输入:1000 输出:500500输入:0 输出:1 (尽管任务中说明不可能有0,这里只为测试用)
解法3的代码同样可以实现任务要求的功能,程序的原理和执行过程和解法2是完全一致的,只不过do-while循环是先执行1次循环体,再判断循环条件,对于输入的n>=1的时候,两种解法的输出结果都是一致的,都可以实现题目要求。小白弟弟,请思考:如果我们输入的是小于1的整数(例如输入0)送给变量n,情况会怎么样呢?以上测试数据中已经显示出如果输入0,执行结果是:解法1输出0;解法2输出1。为什么会这样呢?因为解法1的while循环是在循环开始处判断(1<0)不成立,循环立即结束,循环体被执行0次,最后输出的s的值为0;解法2的do-while循环是在循环出口处判断循环条件,循环体无论如何要执行1次,才能到出口处,执行的这1次循环体已经给变量s累加了1,最后输出的s的值为1。任务中的描述说输入的n是大于等于1的,所以解法2在此情况下才是正确的,如果输入的n可能为0,解法2就不正确了。
相关知识 for循环
除了while语句和do-while语句,C语言还为我们提供了另外的一个使用更为广泛的循环语句——for语句。for语句的一般形式为:for(表达式1;循环条件表达式2;表达式3){ 循环体语句;}(1)首先求解表达式1(该表达式只在这一步骤处被求解一次)。(2)求循环条件解表达式2,若为真则执行循环体,否则结束for语句。(3)循环体执行结束后,求解表达式3,并转向步骤(2)。(4)循环体如果只是一条语句,花括号可以省略。
解法4:for语句
#include<stdio.h>
int main()
{
int n, s, i;
scanf("%d", &n); //输入整数n
s = 0; //和变量s赋初值0
for (i = 1;i <= n;i++)
{ //典型的计次循环,i从1开始到n结束,每次加1
s = s + i; //循环体
}
printf("%d", s); //输出结果
return 0;
}
代码测试与分析
在Dev-C++中执行:输入:1 输出:1 (测试边界数据,可能的最小值)输入:10 输出:55输入:100 输出:5050输入:1000 输出:500500输入:0 输出:0 (尽管任务中说明不可能有0,这里只为测试用)
从以上代码和测试结果可以看出,for循环可以方便地表达计次循环,for(i=1;i<=n;i++)清楚地说明了循环变量i从1开始,到n结束,每次加1。
从执行逻辑上看,先执行i=1,然后进入循环“i<=n→s=s+i→i++”,直到i<=n不成立。这跟while语句的逻辑是相同的,可见while循环和for循环是可以互相转换的:
![](https://i-blog.csdnimg.cn/blog_migrate/4cdeef20e6485fb0185924cc9665f3eb.png)
while循环和for循环可以方便地互相转换,我们在设计程序时可以根据问题实际需要选择一种使用。小白弟弟同样也要牢记,让循环变量i从1变到n,循环体执行n次的计次循环(循环次数固定可数)代码框架: for(i=1;i<=n;i++){ 循环体; }
相关知识 穷举法
有一类问题在进行归纳推理时,如果需要逐个考察某类事件的所有可能情况,即将所有可能情况一一列举,这种方法叫做穷举法。穷举法将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,合适就保留,不合适就丢弃。例如,输出从1加到N的和,可以将1到N之间的所有整数一一列举,无需判断,每个数都累加到变量S中,最后S中的数就是所求;例如,输出自然数N的阶乘,需要将1到N之间的所有整数一一列举,无需判断,每个数都累乘到变量F中,最后F中的数就是所求;例如,输出1到N之间的奇数,可以将1到N之间的所有整数一一列举(穷举),符合条件(奇数)输出,不符合条件就略过;例如,输出1到N之间的素数,需要将1到N之间的所有整数一一列举,是素数的输出,不是素数的略过;例如,输出N的所有约数,需要将1到N之间的所有整数一一列举,如果是N的约数就输出,不是则略过。
相关知识 程序测试方法
我们在测试一个程序的时候,除了使用题目中给出的输入样例做为测试数据以外,还要尽可能地增加多组输入测试数据,尤其是一些边界数据(可能的最大值、最小值),或者特殊数据(0值、特殊意义的值等)。
②(代码)素数判断
任务描述
一个大于1的自然数p,除了1和本身p以外,不能被其他自然数整除,称p为素数(又称质数,prime number),称p为合数。已知素数有无限多个,但是到目前为止,人们未找到一个公式可求出所有质数。
2016年1月,发现世界上迄今为止最大的质数,长达2233万位,如果用普通字号将它打印出来长度将超过65公里。
素数从小到大排列,有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······
输入一个大于1的正整数N,输出其是否为素数,如果是输出YES,否则输出NO。
任务分析
素数的定义是只有1和它本身两个约数的自然数。从定义出发,我们可以做如下统计:如果一个大于1的自然数p的约数个数是2个,那么它就是素数。而统计p的约数个数,我们可以穷举从1到p的所有自然数,这就是计次循环,可以用for语句实现。
解法1:穷举(for语句)
#include<stdio.h>
int main()
{
int p, i, s;
scanf("%d", &p); //输入整数n
s = 0; //统计约数个数的变量s,赋初值0
for (i = 1;i <= p;i++) //穷举循环变量i从1到p
{
if (p % i == 0)
{
s++; //如果i是p的约数,则计数
}
}
if (s == 2) //如果s==2输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
代码测试与分析
在Dev-C++中执行或者在PTA中执行自定义测试:输入:2 输出:YES (边界值,可能的最小值)输入:53 输出:YES输入:15 输出:NO
代码中通过for循环穷举从1到p的自然数i,循环体内如果i是p的约数,就计数。循环结束后,如果计数结果s的值等于2,就说明p是素数,输出YES,否则输出NO。因为1和p是当然是p的约数,所以,我们也可以统计从2到p-1的范围内,p的约数是否为0个,来判断p是否为素数。
解法2:穷举(for语句)
#include<stdio.h>
int main()
{
int p, i, s;
scanf("%d", &p); //输入整数n
s = 0; //统计约数个数的变量s,赋初值0
for (i = 2;i < p;i++) //穷举循环变量i从2到p-1
{
if (p % i == 0) s++; //如果i是p的约数,则计数
}
if (s == 0) //如果s==0输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
解法2的代码可以提交正确,和解法1比较,对于同样的输入数据p,循环少执行2次。其实,还可以更少。我们可以知道,一个自然数p,如果在p以下没有约数,则在以上不可能有约数。因为不可能有2个都大于p的数乘积为p。所以,想知道p有没有约数,只要穷举2到p之间的数就可以了。
解法3:穷举(for语句)
#include<stdio.h>
#include<math.h>
int main()
{
int p, i, s;
scanf("%d", &p); //输入整数n
s = 0; //统计约数个数的变量s,赋初值0
for (i = 2;i <= sqrt(p);i++) //穷举循环变量i从2到根号p
{
if (p % i == 0)
{
s++; //如果i是p的约数,则计数
}
}
if (s == 0) //如果s==0输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
解法3代码的逻辑是统计从2到之间p的约数个数s,如果s==0,说明p就是素数。解法3代码的循环次数已经大大减少了,以输入的p=10000为例,解法1循环10000次,解法2循环9998次,而解法3穷举的是从2到100,循环99次。可见,解法3比解法1和解法2效率更高,执行时间更短。对于判断一个数是否为素数,我们可以采用反证法,就是它在2到p的区间内只要有一个约数,就说明它已经不是素数了,没必要统计所有的约数个数。
解法4:穷举(for语句)
#include<stdio.h>
#include<math.h>
int main()
{
int p, i, f;
scanf("%d", &p); //输入整数n
f = 1; //标志变量f,赋初值1
for (i = 2;f == 1 && i <= sqrt(p);i++) //穷举i从2到根号p,进入循环时需要f==1
{
if (p % i == 0)
{
f = 0; //如果i是约数则置f为0(改变标志,下次循环进不来)
}
}
if (f == 1) //如果f==1输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
解法4中应用了一个特别的技术:设置标志变量f。f的初值是1,如果从2到p之间找到1个约数,就立刻将f的值改变成0,而一旦f的值变为0,则下一次循环就进不来了。因为循环条件是f==1&&i<=sqrt(p),只要f的值不是1,条件就不成立,循环就结束了。最后,如果f==1成立,就说明在区间内没有找到约数,p肯定是素数,否则就不是素数。小白弟弟请思考:假设输入的p是10000,那么解法4的循环次数是多少次呢?对了,是1次。因为当第1次循环时,i的值是2,此时i正是p的约数,f会被赋值成0,下一次循环条件不成立,循环结束了。所以,解法4是最优的。因为它只找第一个素数,而不是找所有的素数。
相关知识 设置标志变量
在一些编程任务中,通常需要识别某种状态,例如上例任务中识别自然数p是否有约数,设置标志变量是一个好办法,它能准确记录状态的变化,通过条件控制程序进程,从而简化程序设计。
③(代码)斐波那契数列
任务描述:
Fibonacci(斐波那契)数列指的是这样一个数列:1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,17711,28657,46368... ...这个数列前2项是1,从第3项开始,每一项都等于前两项之和。
Fibonacci数列的递推公式为:
![](https://i-blog.csdnimg.cn/blog_migrate/262b0911298abb8fd76a7b35152a759d.png)
编程读入整数n(1<=n<=40),输出Fibonacci数列的前n项。
#include<stdio.h>
int main()
{
int n, i;
int f1, f2, f3;
f1 = 1;
f2 = 1;
scanf("%d", &n);
if (n == 1)
{
printf("1");
}
if (n == 2)
{
printf("1,1");
}
if (n > 2)
{
printf("1,1,");
for (i = 3;i <= n;i++)
{
f3 = f1 + f2;
f1 = f2;
f2 = f3;
if (i < n)
{
printf("%d,", f3);
}
else
{
printf("%d", f3);
}
}
}
return 0;
}
二.循环控制(break和continue)
①(代码)从A加到B的和
任务描述
编程输入正整数A和B(A<=B),输出从A加到B的和,即从A到B的连续自然数的和。
输入格式:
两个正整数A和B(A<=B)。
输出格式:
一个整数,从A加到B的和。
你注意到了吗,前面关卡中我们设计的循环程序,都是从循环条件表达式处,判定是否进入循环,如果条件不成立才结束循环。C语言还为我们提供了一个能从循环体内部跳出循环的break语句,就像跳出switch语句一样,当执行到break语句时,跳出循环结构,结束循环。
相关知识 break语句
break语句用于从循环体内跳出循环结构,一般形式是:break;(1)在循环体内,当程序执行到break语句时,会立即跳出循环结构,结束循环。(2)break语句通常出现在某个if语句的分支中,以实现有条件的结束循环。(3)break语句只能用于switch结构内部或循环结构内部。
相关知识 continue语句
C语言还提供了另一个用于控制循环结构的语句:continue语句,用于结束当次循环,直接跳到循环开始处,开始下次循环,一般形式是:continue;(1)在循环体内,当执行到continue语句时,会立即结束本次循环(跳过循环体中后面的部分不执行),接着跳转到循环开始处,执行下一次循环。(2)continue语句通常出现在某个if语句的分支中。(3)continue语句只能用于循环结构的内部。
解法1:穷举(while循环)
#include<stdio.h>
int main()
{
int a, b, s, i;
scanf("%d%d", &a, &b); //输入整数a,b
s = 0; //和变量s赋初值0
i = a; //循环变量i赋初值a
while (i <= b) //循环判断条件(穷举从a到b的自然数)
{
s = s + i; //循环体将i累加到和变量中
i++; //循环变量i自加1
}
printf("%d", s); //输出结果
return 0;
}
代码测试与分析
除任务题目中给出的测试数据外,在Dev-C++或PTA中执行:输入:1 1
输出:1 (可能的最小值组合)输入:1 100 输出:5050输入:1 99 输出:4950
以上代码,你一定理解吧,这是一个非常普通的计次循环穷举问题,循环变量从a开始到b结束,每次加1。有的时候,我们也可以不用在循环入口处关心循环什么时候结束,而是在循环体内通过条件判断来决定什么时候跳出循环。
解法2:使用break语句
#include<stdio.h>
int main()
{
int a, b, s, i;
scanf("%d%d", &a, &b); //输入整数a,b
s = 0; //和变量s赋初值0
i = a; //循环变量i赋初值a
while (1) //循环条件永为真(好像死循环)
{
s = s + i; //循环体将i累加到和变量中
i++; //循环变量i自加1
if (i > b)
{
break; //如果i>b跳出循环,实现从a到b的穷举
}
}
printf("%d", s); //输出结果
return 0;
}
代码分析
代码中通过while(1){ }结构构造一个形式上的死循环,就是循环条件永远为真,永远也不会结束的循环。在循环体内部,通过if(i>b) break;语句,实现当i<=b时继续下一次循环并累加,当i>b时跳出循环。
②(代码)重做素数判断
任务描述
一个大于1的自然数p,除了1和本身p以外,不能被其他自然数整除,称p为素数(又称质数,prime number),称p为合数。已知素数有无限多个,但是到目前为止,人们未找到一个公式可求出所有质数。
2016年1月,发现世界上迄今为止最大的质数,长达2233万位,如果用普通字号将它打印出来长度将超过65公里。
素数从小到大排列,有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······
输入一个大于1的正整数N,输出其是否为素数,如果是输出YES,否则输出NO。
任务分析
任务要求输入一个大于1的正整数p,若p是素数输出YES,否则输出NO。判断p是否为素数,可以通过穷举从2到的自然数,如果存在p的约数则p就不是素数,为了识别是否有约数,设置标志变量。
解法1:使用break语句
#include<stdio.h>
#include<math.h>
int main()
{
int p, i, f;
scanf("%d", &p); //输入整数n
f = 1; //标志变量f,赋初值1
for (i = 2;i <= sqrt(p);i++) //穷举循环变量i从2到
{
if (p % i == 0)
{
f = 0; //如果i是约数则置f为0
break; //跳出循环
}
}
if (f == 1) //如果f==1输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
代码分析
代码中的循环条件处,还原回最初的样子,只是表达了i从2穷举到,比较清晰。循环体内,通过break跳出循环也在情理之中,很好理解。下面我们一起来看看应用continue语句的代码。
解法2:使用continue语句
#include<stdio.h>
#include<math.h>
int main()
{
int p, i, f;
scanf("%d", &p); //输入整数n
f = 1; //标志变量f,赋初值1
for (i = 2;i <= sqrt(p);i++) //穷举循环变量i从2到根号p
{
if (p % i != 0) //如果i不是约数
{
continue; //就直接执行下次循环(跳到i++再判断循环条件)
}
else
{ //能执行到这i肯定是约数
f = 0; //置标志变量f为0
break; //跳出循环
}
}
if (f == 1) //如果f==1输出YES,否则输出NO
{
printf("YES");
}
else
{
printf("NO");
}
return 0;
}
代码分析
continue语句的功能就是直接跳至下一次循环,在解法2的代码循环体中,执行逻辑是:如果i不是p的约数,直接continue后进入下一次循环,否则标志变量改值,并跳出循环