4.1.1 循环
写程序在写的是步骤,一步一步走。
判断一个数的位数
人vs计算机
人一看就出来了,很快啊
计算机:判断数的范围来确定数的位数(100~999范围内是三位数)但人对数字处理比文字弱
但是位数太多也不能用这种方法了。改用一步步查位数的方法。
所以从最左边开始约,每次约一位,
if(n!=0)
{
n/=10;i++;
}
if(n!=0)
{
n/=10;i++;
}
……不过这样也是无限循环。
所以我们可以引入新函数,while。条件满足,就会不断重复大括号里的内容。
(尝试的时候不要拿太大的数去试,计算机里的整数是有范围的。)
4.1.2 while循环
数字位数的算法
- 用户输入x;
- 初始化n为0;
- X/=10,去掉个位;
- cnt++;
- 如果x>0,回到3;
- 否则cnt就是结果。
do-while循环:进入循环的时候不做检查,而是执行完一轮循环体的代码之后再来检查循环条件是否满足,满足的话继续,不满足的话结束循环
(while();最后一定要有分号!!!)
while和do while很像,区别在于do while在循环体执行结束时才来判断条件。也就是说无论如何,循环都会执行至少一遍。
4.1.3 do-while循环
if和while的区别在于,if判断只一次,不管结果如何都结束了。
While一定要有结束循环的条件!!否则会一直循环,超时
While可以翻译为当。循环有可能一次都没有被执行。条件成立是循环成立的条件。
如何看出程序运行结果?
人脑模拟计算机的运行,在纸上列出所有变量,随着程序的进展不断重新计算变量的值。当程序运行结束时,留在表格最下面的就是程序的最终结果。
测试程序常常使用边界数据,如有效范围两端的数据、特殊的倍数等等。
(此题求位数,特殊数据可以是个位数、10、0、负数)
然后可以发现,0是1位数,但是用while算法的话算出来是0位数。那我们可以用do-while就能得到1位数了。
或者if(x>0),做while;else位数=1,单独列出x=0的情况。
另一种调试方法:在适当的位置加上printf输出
作用不只有输出数据。比如在while括号内加一个printf(“in loop”);证明程序到这个地方了,也就是进入while循环了(还可以看循环了几次)
4.2.1 循环计算
编程难在小问题。
如:有的时候可能需要保存原始数据。
求log2x:x/=2,计数(当x>1时)
但是如果printf(“log2 of %d is %d.”,x,计数);最后输出的x总是1,因为循环算完的时候x总是1
所以我们开始要把原始的x保存一下。又是一个小细节~诸如此类,还有很多。
如:
- While可以用do while吗?
- 为什么计数从0开始,可以从1开始吗?
- 为什么while判断条件是x>1?
- 循环最后输出的是多少?
#include<stdio.h>
int main()
{
int x,ret=0;
scanf("%d",&x);
int t=x;
while(x>1){
x/=2;
ret++;
}
printf("log2 of %d is %d.",t,ret);
return 0;
}
其实都是相互牵扯的。
1、 当x=1的时候,结果是0.也就是说我们希望当x=1时不要进入这个循环。
2、 计数ret是我们希望进入这个循环是最小的数。
如果想改成while(x>2),那我们的计数ret就要相应改成初始值=1.但是x=1时条件不满足。为了兼顾两种情况,还得用上面的方法。
也可以ret=-1,while(x>0)
编程肯定会有很多不同的方法。
对于很大次数的循环,我们可以模拟较少的循环次数,然后做出推断,解决上面提到的四个问题。因为很多小细节,要多加注意。
4.2.2 猜数游戏
计算机想一个数,用户来猜,猜不对的话告诉用户大了还是小了,最后猜中了告诉用户猜了多少次。
1) 因为要不断重复去猜,所以我们要用到循环
2) 实际写出程序之前,我们可以先用文字描述出程序的思路。
3) 核心重点是循环的条件。
4) 人们往往会考虑循环终止的条件。
flowchat
st=>start: 开始
i=>inputoutput: 用户输入猜的数
ocnt=>operation: count++
o=>operation: 计算机随机想一个数,记在number里
c=>condition: 判断a是否等于number
e=>end: 结束
o2=>inputoutput: 告诉用户大了还是小了
oe=>operation: 输出cnt猜的次数
st->o->i->ocnt->c
c(yes)->oe->e
c(no)->o2(right)->i
循环的条件是a!=number
用函数rand()召唤随机整数
使用方法:
//先加入两个头文件#include<stdlib.h>和#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
srand(time(0));//Main里加上srand(time(0)); 先不用管什么意思
int a=rand();//如果想要a是100以内的整数:用取余即可(a%=100)
int number=rand()%100+1;//这样召唤出来的数范围是1~100
//不管怎样用户都要进入这个循环,输入至少一个数;所以应该用do-while循环。
}
4.2.3 算平均数
读入一系列正整数,输入-1时终止。然后输出他们的平均数
变量->算法->流程图->程序
num:我们需要读到的那一个数。
sum(总和):每读到一个数把他加到sum里就完事了
另一个变量count记录累加的次数,最后算平均数÷count
flowchat
st=>start: sum=0,cnt=0
i1=>inputoutput: 读num
c=>condition: num!=-1?
o=>operation: sum+=num, cnt++
op=>inputoutput: 计算和打印结果
e=>end: end
st->i1->c
c(yes)->o->i1
c(no,left)->op->e
注意转化成浮点数来求平均值。
浮点数转化方法: 1.0*sum/count
4.2.4 整数逆序
整数分解方法:%10得到个位数,/10;再%10得到十位数……
整数逆序问题注意结尾0的处理;
如果不用考虑0的话,我们可以每得到一位就输出一位。%10一下输出一下,/10,再%10输出一下,很快啊
考虑0:那就应该把逆序数算出来在输出了。搞一个ret=0,每次ret=ret*10+n%10;
5.1.1 for循环
阶乘n!=n*(n-1)*(n-2)*……*2*1
程序:输入n,输出n!
需要借助一个变量i从1~n.这里的循环,我们用for来处理。
for循环像一个计数器。达到一个数之前一直进行循环,在过程中i++
或i--
for(int i=0;i<n;i++)
n次循环
求和时,初始值为0;求积时,初始值为1
still, 可以尝试细节。
比如阶乘,第一项是1,乘不乘不变。如果去掉的话,可以吗?对all n有影响吗?
或者把方向反过来,从n乘到1可以吗?
5.1.2 循环的计算和选择
循环的起点终点对结果都有影响
有固定次数,明确起点与终点:for
至少执行一次:do while
可能一次不执行:while
5.2.1 循环控制
设定判定变量isprime=1;
如果出现可以整除,isprime=0;break;
break:结束循环
continue:可以跳过此循环剩下的部分,进入下一轮循环
5.2.2 嵌套的循环
如:输出100内的素数,for里有for(第一个for遍历1~100,第二个检验该数是不是素数)
int x;//再scan x
int isprime=1;
for(int i=2;i<x;i++){
if(x%i==0){
isprime=0;
break;
}
}//再根据isprime是不是1判断x是不是素数
除了特别设计,每一层循环的控制变量应该不一样(i),要注意。所以在第一层循环每层开始的时候重新赋值或定义int i,免得上次循环完的i这次拿来接着用,乱套了。
/t可以对齐,具体以后再讲
5.2.3 从嵌套的循环中跳出
凑硬币:用1,2,5元凑出100
如果我们想,发现了一种合适的就结束:在if内加break跳出(下图是错误示例)
for(one=1;one<x*10;one++)
{
for(two=1;two<x*10/2;two++)
{
for(five=1;five<x*10/5;five++)//break跳出的是这层循环,而没有跳出上面的两层循环
{
if(one+two*2+five*5==x*10)
{
printf("可以用%d个一角加%d个两角加%d个五角得到%d元\n",one,two,five,x);
break;
}
}
}
}
但是图中的break只跳出了第三层for循环;接着又进到了第二层for循环(图中蓝色位置),Break和continue都只能在他那一层循环里做;
如何一直跳出呢?
方法一:连环跳
加一个判定变量exit=0;
if内写 exit=1;
在两个for后面都写上if(exit)break;
//接力break
int x,one,two,five,x;
int exit=0;
scanf("%d",&x);
for(one=1;one<x*10;one++)
{
for(two=1;two<x*10/2;two++)
{
for(five=1;five<x*10/5;five++)
{
if(one+two*2+five*5==x*10)
{
printf("可以用%d个一角加%d个两角加%d个五角得到%d元\n",one,two,five,x);
exit=1;
break;
}
}
if(exit==1)break;
}
if(exit==1)break;
}
方法二:goto out
//goto
int x,one,two,five,x;
int exit=0;
scanf("%d",&x);
for(one=1;one<x*10;one++)
{
for(two=1;two<x*10/2;two++)
{
for(five=1;five<x*10/5;five++)
{
if(one+two*2+five*5==x*10)
{
printf("可以用%d个一角加%d个两角加%d个五角得到%d元\n",one,two,five,x);
goto out;
}
}
}
}
out:
return 0;
(除了这种多重break的结构建议使用goto,别的地方不建议。)
5.3.1 前n项和
求1+1/2+1/3+……+1/n.
在这个循环里,起点数字1,终点数字n都是明确的
#include<stdio.h>
int main()
{
int n;
double sum=0.0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
sum+=1.0/i;
}
printf("f(%d)=%f",n,sum);
return 0;
}
例2:1-1/2+1/3-1/4+……
解决方案:定义一个sign=1;
sum+=sign*1.0/i;
sign=-sign;
技巧:如果我们double sign=1.0
,算式中就出现小数了,就不用*1.0了。
5.3.2 整数分解
比如输入:12345
输出:1 2 3 4 5
考虑之前逆序输出的方法,我们可以先把这个数逆过来之后再逆序输出(鬼才)
即:
- 先求x的逆序数t
- 逆序逐位输出t
这种方法的缺陷在于:对于末尾有0出现的数字(如700)只能得到7
我们可以回想3位数的题,我们用三位数/100得到最高位数字。
所以我们知道位数之后,可以正序逐位得到每一位的数字。
可以先一个while循环求出x的位数和mask(位数),(还是记得提前把x保存下来)然后正序输出。
123456 / 100000 = 1
123456%100000 = 23456
23456 / 10000 = 2
……
5.3.3 求最大公约数
算法1:枚举。
循环t++
if(u/t==0&&v/t==0)gcd=t;//不断刷新gcd
if(t==u||t==v)break;//并输出gcd;
效率更高的算法2:辗转相除
- 如果b=0,计算结束,a就是最大公约数;
- 否则,让a=b,b=a%b;
- 回到第一步
(可以使用之前提到过的变量表格法来人工运行)
6.0.1 求符合给定条件的整数集
给定<=6的正整数A,从A开始的连续四个数字,请输出所有由它们组成的无重复数字的三位数。
输出满足条件的三位数,从小到大,且每行6个整数,行末无多余空格。
如:输入A=2
234 235 243 245 253 254
324 325 342 345 352 354
423 425 432 435 452 453
523 524 532 534 542 543
int i,a,j,k,cnt=0;
scanf("%d",&a);
i=a;
//思路:i,j,k都从a开始,从小到大逐渐增加到a+3,三者不能相等
while(i<=a+3){
j=a;
while(j<=a+3){
k=a;
while(k<=a+3){
if(i!=j&&j!=k&&i!=k){
printf("%d%d%d",i,j,k);
cnt++;
if(cnt==6){
printf("\n");
cnt=0;
}
else printf(" ");
}
k++;
}
j++;
}
i++;
}
6.0.2 水仙花数
水仙花数指一个N位正整数(N>=3),它的每位上的数字的N次幂之和等于它自己。如:153=1^3^+3^3^+5^3^;
给定一个N(3<=N<=7),按顺序输出所有N位水仙花数。
输入:3
输出:
153
370
371
第一个循环:遍历所有N位数。
第二个循环来求和,循环里面第三个循环来求每一位的N次幂(N次循环),最后if判断是否==sum。
6.0.3 打印乘法口诀表
a*b,a、b两重循环。
还要注意对齐问题。如果结果是一位数,输出两个空格;两位数输出一个空格。
6.0.4 统计素数并求和
给定M和N区间内的素数的个数并对它们求和。
读题后得知:包含两头[M,N]
先定义isprime=1;
当可以被比他小的数整除的时候就isprime=0。
这道题很好做。注意特殊情况,如m=1时,循环直接不判断,isprime=1
可以在m~n循环开始前判断,if(m==1)m++;
6.0.5 猜数游戏
输入要猜的数字和猜的最大次数,大了输出too big,小了输出too small,直到猜对或者次数用光或者用户输入负数为止
还有就是注意不同次数猜到的输出结果也有区别。
一次猜到:BINGO!
两次猜到:LUCKY YOU!
三次以上猜到:GOOD GUESS!
//也不难,1.考的是语文的阅读理解;2.你是否有足够的耐心。(文中条件太多)
6.0.6 n项求和
2/1+3/2+5/3+8/5+……的前n项之和(从第二项开始,每一项的分子是前一项的分子和分母之和,分母是前一项的分子)
dividend=2;//分母
divisor=1;//分子
for(i=1;i<=n;i++){
sum+=dividend/divisor;
t=dividend;
dividend+=divisor;
divisor=t;
}
printf("%.2f/n",sum);
因为分子分母变大的相当快,所以出于范围考虑,也建议使用double储存dividend(分子)和divisor(分母)
(inf是越界,即无穷;nan是无效数字)
6.0.7 约分最简分式:
输入一个分式,如32/45,输出最简形式。
- scanf("%d/%d")这样处理输入
- 辗转相除法寻找最大公约数;
念数字:比如输入-12:fu yi er
先判断-的情况之后switch case很好办。以后学会数组之后会有更优解。
求a的连续和:
输入a,n,计算a+aa+aaa+……+n个a的和
循环的每一轮a1=a1*10+a。