第2章 循环结构程序设计
学习要求和笔记:
√ 掌握for循环的使用方法
√ 掌握while和do-while循环的使用方法
√ 学会使用计数器和累加器
√ 学会用计时函数测试程序效率
√ 学会用重定向的方式读写文件
》尽量用const关键字声明常数
√ 学会用fopen的方式读写文件
√ 记住变量在赋值之前的值时不确定的
√ 学会用编译选项-Wall获得更多的警告信息
例题学习记录和源代码:
√ Example2-1 aabb
》双循环判定法
》枚举平方根法
√ Example2-2 3n+1问题
√ Example2-3 近似计算
√ Example2-4 阶乘之和
√ Example2-5 数据统计1
√ Example2-6 数据统计2
习题学习记录和源代码:
√ Exercise2-1 水仙花数(daffodil)
√ Exercise2-2 韩信点兵(hanxin)
√ Exercise2-3 倒三角形(triangle)
√ Exercise2-4 子序列的和(subsequence)
√ Exercise2-5 分数化小数(decimal)
√ Exercise2-6 排列(permutation)
// Example2-1 aabb完全平方数
/**
* 题目要求:输出所有形如aabb的四位完全平方数(即前两位数字相等,后两位数字也相等)。
**/
/**
* 题目及方法分析:
* 题目所要求的数字有两种条件,第一种,要有形式aabb,第二种,需要可开方得整数。
* 下面可以使用先定形式,再判断是否可开方的方法。
* 所谓定形式,是指,将所有符合aabb样式的数字逐个列出来,
* 然后,让这数开方,通过判断开方后的数是否为整数而判定结果。
* 学习笔记:
* ①一般开根号计算中为了减少误差采用四舍五入的方法floor(x + 0.5)
* ②尽量避免使用浮点型比较来判定,因为存在浮点误差
* ③sqrt原型为double sqrt(double)
**/
/*
//.cpp
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
for(int a = 1; a <= 9; a++)
{
for(int b = 1;b <= 9; b++)
{
int n = a*1100 + b*11;
double m = sqrt((double)n);
m = floor(m + 0.5);
if(m*m == n)
cout << n << endl;
}
}
return 0;
}
*/
//.c
#include <stdio.h>
#include <math.h>
int main()
{
for(int a = 1; a <= 9; a++)
{
for(int b = 1;b <= 9; b++)
{
int n = a*1100 + b*11;
double m = sqrt((double)n);
m = floor(m + 0.5);
if(m*m == n)
printf("%d\n", n);
}
}
return 0;
}
/*运行结果为7744
// Example2-1 aabb完全平方数2 枚举平方根法
/**
* 题目要求:输出所有形如aabb的四位完全平方数(即前两位数字相等,后两位数字也相等)。
**/
/**
* 题目及方法分析:
* 题目所要求的数字有两种条件,第一种,要有形式aabb,第二种,需要可开方得整数。
* 下面可以使用先平方,且满足大于1000小于9999再判断是否符合aabb形式。
* 所谓定形式,是指,将所有符合aabb样式的数字逐个列出来,这样有效避免开平方根操作
* 学习笔记:
* 当本身变量不好找上限时,巧妙运用无循环条件,设置其它变量的上限直接break跳出整个for循环
* continue只跳出自身的if循环
**/
/*
//.cpp
#include <iostream>
using namespace std;
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)
cout << n << endl;
}
return 0;
}
*/
//.c
#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", n);
}
return 0;
}
/*运行结果:7744
// Example2-2 3n+1问题
/**
* 题目名称:3n+1问题
* 题目描述:对于任意大于1的自然数,若n为奇数,则将n变为3n+1,否则变为n的一半。
* 经过若干次这样的变换后,一定会使n变为1。输入n,输出变换的次数。 n <= pow(10,9)
* 例如: 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
* 输入: 3
* 输出: 7
* 输入: 987654321
* 输出: 180
**/
/**
* 题目及方法分析:
* 题目要完成的工作要么是3n+1,要么就是n/2,也就是说,在统计次数时,只需要用上一个if-else语句就行了。
* 另外,需要注意的是,题目特意提出了n的取值范围是10的9次方,
* 需要知道的是,int的取值范围只到10的10次方,如果多次*3+1的话,很容易溢出,
* 那么,我们可以考虑使用long long型的数据作为这个n,
* 但是这里需要注意如果在输入之前就定义为long long型,我们scanf("%d")要改为%I64d这样就比较麻烦,
* 所以我们在输入之前定义int,时候再赋值为long long 型
**/
/*
//.cpp
#include <iostream>
using namespace std;
int main()
{
int n2, count = 0;
cin >> n2;
long long n = n2;
while(n > 1)
{
if(n % 2 == 1) n = n*3+1;
else
n /= 2;
count++;
}
cout << count << endl;
return 0;
}
*/
//.c
#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\n", count);
return 0;
}
// Example2-3 近似计算
/**
* 题目名称:近似计算 pi/4 = 1 - 1/3 + 1/5 - 1/7 + ......, 直到最后一项小于1e-6
* 题目描述:这里不一样的地方在于循环中止判断是在计算之后,而不是计算之前,这种情况很适合用do-while循环;
* 当然也可以采用for(不加循环条件),之后加一个判定,如果满足继续循环,如果不满足,直接break
**/
/* 这个do while代码有点问题
//.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double sum = 0;
double term = 0;
int i = 0;
do
{
i++;
double term = 1.0/(i*2+1);
if(i%2 == 0)
sum += term;
else
sum -= term;
}
while(term < 1e-6);
cout << setiosflags(ios::fixed) << setprecision(6) << sum << endl;
return 0;
}
*/
//.c
#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\n", sum);
return 0;
}
// Example2-4 阶乘之和.cpp
/**
* 题目名称:阶乘之和
* 题目描述:输入n,计算S = 1! + 2! + 3! + ... + n!的末6位(不含前导0)。 n <= pow(10,6); n!表示前n个正整数之积。
* 样例输入: 10
* 样例输出: 37913
* 学习笔记:
* ①在循环体开始定义的变量,每次执行循环体时会重新声明并且初始化
* ②可以使用time.h和clock()获得程序运行时间,常数CLOCKS_PER_SEC和操作系统相关,不要直接使用clock(),应总是除以CLOCKS_PER_SEC
* ③这里巧妙的先将每个factorial都取模值,这样防止溢出应该如果不这样当n = 100时,输出为-961703
**/
/*
//.c
#include<stdio.h>
#include <time.h>
int main()
{
int n,sum = 0;
scanf("%d", &n);
for(int i = 1;i <= n; i++)
{
int factorial = 1;
for(int j = 1; j <= i; j++)
{
factorial *= j;
factorial = factorial % 1000000;
}
sum = (sum + factorial)% 1000000;
}
printf("%d\n", sum);
printf("Time used = %.2f\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
*/
//.cpp
#include <iostream>
#include <time.h>
using namespace std;
int main()
{
int n,sum = 0;
cin >> n;
for(int i = 1;i <= n; i++)
{
int factorial = 1;
for(int j = 1; j <= i; j++)
{
factorial *= j;
factorial = factorial % 1000000;
}
sum = (sum + factorial)% 1000000;
}
cout << sum << endl;
cout << "Time used = " << (double)clock() / CLOCKS_PER_SEC << endl;
return 0;
}
// Example2-5 数据统计1
/**
* 题目名称:数据统计
* 题目描述:输入一些整数,求出最小值,最大值,平均值(保持3位小数)输入保证这些书都不超过1000的整数
* 样例输入: 2 8 3 5 1 7 3 6
* 样例输出: 1 8 4.375
* 学习笔记:
* ①这里关键在于输入整数个数不确定,所以我们采用while(scanf("%d", &n) == 1)来判定,这个需要注意的的是如果同时输入两个数据呢
* while(scanf("%d%d", &a, &b) != EOF)来判定,实际上它输入的时候返回值为2
* ②使用Ctrl+Z,再按Enter按键即可以结束输入。
* ③当不知道如何给min,或者max初始化时候,而且我们要知道必须要初始化,因为在C中变量在未赋值之前的值时不确定的,特别的它不一定等于0
* 所以,我们可以采用这个思路,min保存的是最小值,所以我们初始化给它一个很大的值,反过来max初始化给它一个很小的值INF=1000000000
* ④小技巧:如何方便检查程序,我们可以采用输出中间结果来检查
**/
//.c
/*
//#define LOCAL
#include <stdio.h>
#define INF 1000000000
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
int x,cnt = 0,sum = 0;
int min = INF;
int max = -INF;
while(scanf("%d", &x) == 1)
{
sum += x;
if(x < min)
min = x;
if(x > max)
max = x;
cnt++;
}
printf("%d %d %.3f\n", min, max, (double)sum/cnt);
return 0;
}
*/
//.cpp
#include <iostream>
#include <iomanip>
using namespace std;
#define INF 1000000000
int main()
{
int x,cnt = 0,sum = 0;
int min = INF;
int max = -INF;
while(cin >> x)
{
sum += x;
if(x < min)
min = x;
if(x > max)
max = x;
cnt++;
}
cout << min << " " << max << " " << setiosflags(ios::fixed) << setprecision(3) << (double)sum/cnt << endl;
return 0;
}
// Example2-6 数据统计2
/**
* 题目名称:数据统计2
* 题目描述:输入一些整数,求出最小值,最大值,平均值(保持3位小数)输入保证这些书都不超过1000的整数
* 输入包含多组数据,每组数据第一行是整数个数n,第二行是n个整数,n=0为输入结束标记,程序应当忽略这组数据,相邻两组数据之间应该输出一个空行
* 样例输入:
8
2 8 3 5 1 7 3 6
4
-4 6 10 0
0
* 样例输出:
Case 1: 1 8 4.375
Case 2: -4 10 3.000:
* 学习笔记:
* ①这里需要注意这题和上面数据统计1题目其实一样,只是它输入格式有点不一样而且,这种格式是我们考试特别喜欢考的
* ②这里还要注意由于n = 0作为结束标记,所以我们判定的时候要加上&& n,这样可以增加程序的鲁棒性
* ③在多数据题目中,一定要注意计算完一组数据之后,要将某些变量重置,否则计算错误
**/
//.c
#include <stdio.h>
#define INF 1000000000
int main()
{
int n = 0,s,sum = 0,kase = 0,i;
int max,min;
while(scanf("%d", &n) == 1 && n)
{
max = -INF;
min = INF;
for(i = 0; i < n; i++)
{
scanf("%d", &s);
if(s < min)
min = s;
if(s > max)
max = s;
sum += s;
}
if(kase)
printf("\n");
printf("Case %d: %d %d %.3f\n",++kase, min, max, (double)sum/n);
}
return 0;
}
//.cpp
#include <iostream>
#include <iomanip>
using namespace std;
#define INF 1000000000
int main()
{
int n = 0,s,sum = 0,kase = 0,i;
int max,min;
while(cin >> n && n)
{
max = -INF;
min = INF;
for(i = 0; i < n; i++)
{
cin >> s;
if(s < min)
min = s;
if(s > max)
max = s;
sum += s;
}
if(kase)
cout << endl;
cout << "Case " << ++kase << ": " << min << " " << max << " " << (double)sum/n << endl;
}
return 0;
}
// Exercise2-1 水仙花数(doffodil)
/**
* 题目名称:水仙花数(doffodil)
* 题目描述:输出 100~999 中的所有水仙花数。若3位数满足 ABC = A*A*A + B*B*B + C*C*C,则称水仙花数。
* 题目分析:方案①先定位,假设三个变量都大于等于0小于等于9,这里注意A不能等于0;
* 再验证是不是满足ABC=A*A*A + B*B*B + C*C*C
* 方案②先直接假设一个变量从100开始加1循环到999,之后去寻找满足条件的值
* 运行结果:153 370 371 407
**/
/*
//.cpp
#include <iostream>
using namespace std;
int main()
{
for(int i = 100; i <= 999; i++)
{
int a = i/100;
int b = i/10%10;
int c = i%10;
if(i == a*a*a + b*b*b + c*c*c)
cout << i << endl;
}
return 0;
}
*/
//.c
#include <stdio.h>
int main()
{
for(int i = 100; i <= 999; i++)
{
int a = i/100;
int b = i/10%10;
int c = i%10;
if(i == a*a*a + b*b*b + c*c*c)
printf("%d\n", i);
}
return 0;
}
// Exercise2-2 韩信点兵(hanxin)_中国剩余定理
/**
* 题目名称:韩信点兵(hanxin)
* 题目描述:
* 相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、
* 五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。
* 输入3个非负整数a, b, c, 表示每种队形排尾的人数(a < 3, b < 5, c < 7),
* 输出总人数的最小值(或报告无解)。已知人数不小于10, 不超过100。
* 样例输入: 2 1 6
* 样例输入: 2 1 3
* 样例输出: Case 1: 41
* Case 2: No answer
**/
/**
* 题目分析:
*方案一:先假设一个变量i,这个变量范围不小于10,不超过100在这里面去for循环查找
*满足i%3 == a && i%5 == b && i%7 == c的数值即为所求,这里要注意不满足的时候也需要输出No answer
*这就和我们以前的不一样,不能直接返回return 0了,这个时候就可以借助下判定变量flag.
*这里有一个地方特别容易出错,就是flag = 0初始化,必须要放入循环里来
*方案二:不是那么好想的,利用中国剩余定理。
* x % 3 = a;
* x % 5 = b;
* x % 7 = c;
* 用中国剩余定理可得公式 x =(70*a + 21*b + 15*c) % 105
* 70是5与7的倍数,而用3除余1;
* 21是3与7的倍数,而用5除余1;
* 15是3与5的倍数,而用7除余1;
* 105是它们三者的最小公倍数n;
* 使用这定理有两前提,其一:除数都为素数,其二:所求数需小于最小公倍数n。
**/
//.cpp
#include <iostream>
using namespace std;
int main()
{
int a,b,c,x,kase = 0;
while(cin >> a >> b >> c)
{
int flag = 0;
for(x = 10; x <= 100; x++)
{
if(x%3 == a && x%5 == b && x%7 == c)
{
cout << "Case " << ++kase << ": " << x << endl;
flag = 1;
}
}
if(flag == 0)
cout << "Case " << ++kase << ": No answer" << endl;
}
return 0;
}
//.c
#include <stdio.h>
int main()
{
const int MOD = 105;
int a,b,c,kase = 0;
//while(scanf("%d", &a) && scanf("%d", &b) && scanf("%d", &c))
while(scanf("%d %d %d", &a, &b, &c) != EOF)
{
int res = 0;
int m = a * 70 + b * 21 + c *15;
res = m % MOD;
if(res >= 10 && res <= 100)
printf("Case %d: %d\n",++kase, res);
else
printf("Case %d: No answer\n",++kase);
}
return 0;
}
// Exercise2-3 倒三角形(triangle)_等差数列
/**
* 题目名称:倒三角形(triangle)
* 题目描述:输入一个正整数 n<=20, 输出一个n层的倒三角形。
* 样例输入:5
* 样例输出:
* # # # # # # # # #
* # # # # # # #
* # # # # #
* # # #
* #
**/
/**
* 题目分析:观察倒三角形与输入的正整数n的规律。(等差数列)
* 从整体来看,这是一个n*n的矩形。
* 从有图案的地方来看,将三角形正着来看的时候,
* 第1行9个#, 第2行7个#,第3行5个#,第4行3个#,第5行1个#,
* a = 2p-1 p = 5, p--
* 现在再把三角形倒着来看,研究一下左边空白的地方,
* 左边空白地方很容易填充,相当于一个初值为0,步长为1的数列。
**/
//.c
#include<stdio.h>
int main()
{
int n;//编程小技巧 一定要在scanf将所用的变量全部定义完,而且需要初始化的也先初始化
scanf("%d", &n);
int i,j,p,cnt = 0,sum = 0;
for(p = n; p >= 1; p--)
{
for(i = 0; i < cnt; i++)
printf(" ");
for(j = 0; j < (2*p-1); j++)
printf("#");
cnt ++;
printf("\n");
}
return 0;
}
//.cpp
#include<iostream>
using namespace std;
int main()
{
int n;//编程小技巧 一定要在scanf将所用的变量全部定义完,而且需要初始化的也先初始化
cin >> n;
int i,j,p,cnt = 0,sum = 0;
for(p = n; p >= 1; p--)
{
for(i = 0; i < cnt; i++)
cout << " " ;
for(j = 0; j < (2*p-1); j++)
cout << "#";
cnt ++;
cout << endl;
}
return 0;
}
// Exercise2-4 子序列的和(subsequence).cpp
/**
* 题目名称:子序列的和(subsequence)
* 题目描述:输入两个正整数n < m < pow(10, 6), 输出 1/n*n + 1/(n+1)*(n+1) + ... + 1/m*m,保留5位小数。
* 样例输入:2 4
65536 655360
0 0
* 样例输出:0.42361
Case 1: 0.42361
Case 2: 0.00001
* 学习笔记:
* 本题有一些陷阱:①输入两个值要求大小关系时,我们第一步要先把输入的数据排序下,这里可以采用三变量替换法
* ②int型转为double型时候要注意要将除数变成double型 例如1写成1.0
* ③一定要留意i*i这种类型的东西,一定要防止它溢出
**/
//.c
#include <stdio.h>
int main()
{
int n,m,kase = 0;
long long i,t = 0;
double sum;
while(scanf("%d %d", &n, &m) != EOF && (m != 0 || n != 0))
{
sum = 0;
for(i = n; i <= m; i++)
{
t = i * i;
sum += 1.0/t;
}
++kase;
printf("Case %d: %.5f\n", kase, sum);
}
return 0;
}
//.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int n,m,kase = 0;
long long i,t = 0;
double sum;
while( (cin >> n >> m) && (m != 0 || n != 0))
{
sum = 0;
for(i = n; i <= m; i++)
{
t = i * i;
sum += 1.0/t;
}
++kase;
cout << "Case " << kase << ": " << setiosflags(ios::fixed) << setprecision(5) << sum << endl;
}
return 0;
}
// Exercise2-5 分数化小数(decimal).cpp
/**
* 题目名称:分数化小数(decimal)
* 题目描述:输入正整数a, b, c,输出 a/b 的小数形式,精确到小数点后c位。 a,b <=pow(10, 6), c <= 100. 含有多组数据 且结束标记为a=b=c=0
* 样例输入:1 6 4
0 0 0
* 样例输出:Case 1: 0.1667.
* 学习笔记:
* c语言中当需要精确到小数点后的数位是变量时,可以利用printf的特殊用法:
* printf("%.*f\n", n, c);当然C++还是可以用setprecision(n)
**/
//.c
#include <stdio.h>
int main()
{
int a, b, c, kase = 0;
double t = 0.0;
while(scanf("%d %d %d", &a, &b, &c) != EOF && (a != 0 || b != 0 || c != 0))
{
t = (double)a / b;
printf("Case %d: %.*f\n", ++kase, c, t);
}
return 0;
}
//.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int a, b, c, kase = 0;
double t = 0.0;
while((cin >> a >> b >> c) && (a != 0 || b != 0 || c != 0))
{
t = (double)a / b;
cout << "Case " << ++kase << ": " << setiosflags(ios::fixed) << setprecision(c) << t << endl;
}
return 0;
}
// Exercise2-6 排列(permutation)_根据条件列数字再判断.cpp
/**
* 题目名称:排列(permutation)
* 题目描述:用1, 2, 3, ..., 9 组成3个位数 abc, def 和 ghi, 每个数字恰好使用一次。
* 要求: abc: def: ghi = 1: 2: 3.输出所有解。
* 结果:
* 192 : 384 : 576
* 219 : 438 : 657
* 273 : 546 : 819
* 327 : 654 : 981
**/
/**
* 题目分析:这个题目与aabb的完全平方的那题有点相似,分析这里意思就是这里可以采用枚举法
* 这里abc最小值为123,最大值为329,之后bcd = abc * 2; ghi = abc * 3之后分别分离出来abcdefghi,
* 最后一步就是如何保证恰好出现一次:数学abcdefghi,之和为45 乘积为36288
**/
//.c
#include <stdio.h>
int main()
{
int a,b,c,d,e,f,g,h,i;
int abc,def,ghi;
int sum = 0, plus = 0;
for(abc = 123; abc <= 329; abc++)
{
def = abc * 2;
ghi = abc * 3;
a = abc / 100;
b = abc /10 % 10;
c = abc % 10;
d = def / 100;
e = def /10 % 10;
f = def % 10;
g = ghi / 100;
h = ghi /10 % 10;
i = ghi % 10;
sum = a + b + c + d + e + f + g + h + i;
plus = a * b * c * d * e * f * g * h * i;
if(sum == 45 && plus == 362880)
printf("%d%d%d %d%d%d %d%d%d\n", a,b,c,d,e,f,g,h,i);
}
return 0;
}
//.cpp
#include <iostream>
using namespace std;
int main()
{
int a,b,c,d,e,f,g,h,i;
int abc,def,ghi;
int sum = 0, plus = 0;
for(abc = 123; abc <= 329; abc++)
{
def = abc * 2;
ghi = abc * 3;
a = abc / 100;
b = abc /10 % 10;
c = abc % 10;
d = def / 100;
e = def /10 % 10;
f = def % 10;
g = ghi / 100;
h = ghi /10 % 10;
i = ghi % 10;
sum = a + b + c + d + e + f + g + h + i;
plus = a * b * c * d * e * f * g * h * i;
if(sum == 45 && plus == 362880)
cout << a << b << c << " " << d << e << f << " " << g << h << i << endl;
}
return 0;
}
——To_捭阖_youth 2014.6.27