掌握清单:
- for while do..while 循环
- 计数器和累加器
- 用输出中间结果的方法调试
- 用计时函数测试程序的效率
- 用重定向方式读写文件
- 用fopen方式读写文件
- 用条件编译指示构建本地运行环境
- 用编译选项 -Wall 获得更多的警告信息
一、for循环
程序1:for循环输出
#include<stdio.h> int main(){ int n; scanf("%d",&n); for(int i = 1;i <= n;i++) printf("%d ",i); return 0; }
细节:变量i定义在循环语句中,因此i在循环体外不可见
# 建议议尽量缩短变量定义的范围 ------- 例如,在for循环中的初始化部分,定义循环变量
程序2:输出所有aabb形式的4位完全平方数
#include<stdio.h> #include<math.h> //aabb 是4位数字,所以a的范围——1-9,b的范围——0-9 int main(){ for (int a = 1;a <= 9;a++){ for(int b = 0;b <= 9;b++){ //开始判定,aabb是不是平方数 int n = a * 1100 + b * 11; //用一个变量m存储sqrt(n) 四舍五入后的整数,然后判定m*m是否等于n //floor(x)函数返回不超过x的最大整数 int m = floor(sqrt(n) + 0.5); if(m*m == n) printf("%d ",n); } }
return 0; }
结果:7744
floor(sqrt(n) + 0.5) 表示四舍五入
# 浮点数的运算有可能存在误差,假设在经过大量运算后,由于误差的影响,整数1 变成了 0.99999999,floor的结果会是0 而不是 1,为了减小误差的影响,一般改为 四舍五入,即floor(x + 0.5)
# 浮点运算可能存在误差,在进行浮点数 比较时,应该考虑到 浮点误差
另一个思路是 枚举平方根 x,从而避免开平方操作
#include<stdio.h> int main(){ for(int x = 1; ; x++){ int n = x *x; if (n < 1000) continue; if (n > 9999) break; int hi = n/100; int lo = n%100; if( hi/10 == hi%10 && lo/10 == lo%10){ printf("%d ",n); } } return 0; }
二、while do...while 循环
程序3: 3n+1问题
下列程序有Bug
#include<stdio.h> int main(){ int n,count = 0; scanf("%d",&n); while(n >1){ if(n%2 ==1) n = n *3 +1; else n /= 2; count++; } printf("%d",count); return 0; }
当输入较大的数时,如987654321,最后输出的是 1
采用“”输出中间结果“的方法差错
#include<stdio.h> int main(){ int n,count = 0; scanf("%d",&n); while(n >1){ if(n%2 ==1){ n = n *3 +1; printf(" %d ",n); }else{ n /= 2; } count++; } printf("%d",count); return 0; }
发现 ————> 乘法 溢出
# c99并没有规定int类型 的确切大小,但在当前流行的竞赛平台上,int 都是32位 ———— -2147483648 ~ 2147483647
而本题中n的上限 10^9 只比int的上界稍微小点,极容易溢出
所以采用 long long即可 解决问题 —— 范围是: -2^63 ~ 2^63 - 1 输入时为 %lld
后续详细讨论!
#include<stdio.h> int main(){ int n2,count = 0; scanf("%d",&n2); long long n = n2; while(n >1){ if(n%2 ==1) n = n *3 +1; else n /= 2; count++; } printf("%d",count); return 0; }
程序4:近似计算
#include<stdio.h> int main(){ double sum = 0; for(int i = 0;;i++){ double term = 1.0 / (i*2 + 1); if(i%2 ==0) sum += term; else sum -= term; if(term < 1e-6) break; } printf("%.6f ",sum); return 0; }
#include<stdio.h> int main(){ double sum = 0; int i = 0; double term = 0; do{ term = 1.0/(i*2+1); if(i%2 == 0) sum += term; else sum -= term; i++; }while(term > 1e-6); printf("%.6f",sum); return 0; }
三、循环的代价
程序5:阶乘之和
#include<stdio.h> int main(){ int n,s = 0; scanf("%d",&n); for(int i = 1;i <= n;i++){ int factorial = 1; //在循环体开始处定义的变量,每次执行循环体的时候都会宠幸声明并初始化 for(int j =1;j <=i;j++) factorial *= j; s += factorial; } printf("%d ",s % 1000000); //因为只要末6位,所以输出时需要对10^6 取模 return 0; }
很显然,极容易溢出
当 n = 10^6 时,更会溢出,但速度 极慢
补充:要计算只包含加法、减法和乘法的整数表达式除以正整数n的余数,可以在每步计算之后对n取余,结果不变
下面,把程序改成“每步取模“的形式,然后加一个计时器,看看速度:
#include<stdio.h> #include<time.h> int main(){ const int MOD = 1000000; int n,s = 0; scanf("%d",&n); for(int i = 1;i <= n;i++){ int factorial = 1; for(int j = 1;j <=i;j++){ factorial = (factorial * j %MOD); } s = (s + factorial) %MOD; } printf("%d ",s); printf("Time used = %.2f ",(double) clock() /CLOCKS_PER_SEC); return 0; }
计时函数 clock() ———— 该函数返回程序目前为止运行的时间,在程序结束之前调用此函数,就可以获得整个程序的运行时间,再除以常数 CLOCKS_PER_SEC后,得到的值以秒s 为单位
键盘的输入时间也被计算在内,为了避免输入数据的时间影响测试结果,可以使用一种称为“管道”的小技巧:
在windows命令行中执行 echo 20|abc ,系统就会自动把20输入,abc是程序名
四、算法竞赛中的输入、输出框架
程序6:数据统计
输入一些整数,求出他们的min max 和平均值(保留三位小数)。输入保证这些数都是 不超过1000的整数
【有Bug】
#include<stdio.h> int main(){ int x,n =0,min,max,s = 0; while(scanf("%d",&x) == 1){ s += x; if(x <min) min = x; if(x >max) max = x; n++; } printf("%d %d %.3f",min,max,(double) s / n); return 0; }
# scanf() 返回的是 成功输入的变量个数
测试一下
5002320 从哪里来的? ———— 变量在没有赋值之前的值是不确定的!
解决的办法就是 在使用之前赋初值
比较好的办法就是用文件 ———— 把输入数据保存在文件之中,输出数据也保存在文件之中。只要把事先输入数据保存在文件之中,就没必要每次重新输入了
使用文件最简单的方法就是 使用输入输出重定向
只需要在main 函数入口处加上:
freopen("input.txt","r",stdin) freopen("output.txt","w",stdout)
上处语句使得scanf() 从 input.txt读入,printf() 写入 output.txt
算法竞赛中,选手应该严格遵守比赛的文件名规定,尤其是路径【不能加路径,哪怕是相对路径】
方法:在本机测试时使用文件重定向,但一旦提交到比赛就自动“”删除“”重定向语句
#define LOCAL #include<stdio.h> #define INF 1000000000 int main(){ #ifdef LOCAL freopen("data.in","r",stdin); freopen("data.out","w",stdout); #endif // LOCAL int x,n =0,min =INF,max =-INF,s = 0; //使用INF的原因是:给定一个假想的无穷大 while(scanf("%d",&x) == 1){ s += x; if(x <min) min = x; if(x >max) max= x; /* printf("x = %d,min = %d,max = %d ",x,min,max); */ n++; } printf("%d %d %.3f ",min,max,(double) s/ n); return 0; }
如果比赛 要求用文件输入输出,但禁止使用重定向的方式:
#include<stdio.h> #define INF 1000000000 int main(){ FILE *fin,*fout; fin = fopen("data.in","r"); fout = fopen("data.out","wb"); int x,n = 0,min = INF,max = -INF,s = 0; while(fscanf(fin,"%d",&x)== 1){ s += x; if(x < min) min = x; if(x >max) max = x; n++; } fprintf(fout,"%d %d %.3f ",min,max,(double) s / n); fclose(fin); fclose(fout); return 0; }
用fopen("con","r")的方法打开标准输入输出不是可移植的,在Linux下是无效的!!!!
程序7: 多组数据问题
#include<stdio.h> #define INF 1000000000 int main(){ int x,n =0,min = INF,max = -INF,s = 0,kase = 0; while(scanf("%d",&n) == 1&& n){ int s= 0; for(int i = 0;i <n;i++){ scanf("%d",&x); s += x; if(x < min) min = x; if(x >max) max = x; } if(kase) printf(" "); printf("Case %d: %d %d %.3f ",++kase,min,max,(double) s/n); } return 0; }
要点分析: