本专栏主要基于北大郭炜老师的程序设计与算法系列课程进行整理,包括课程笔记和OJ作业。该系列课程有三部分: (一) C语言程序设计;(二) 算法基础;(三) C++面向对象程序设计
1. 循环结构之break语句、continue语句
break语句
- 可以出现在循环体中(for、while、do…while循环均可),
其作用是跳出循环。
int n = 0;
while(true) {
if( n > 100)
break;
++n;
}
cout << n ; //101
-
在多重循环的情况下,break语句只能跳出直接包含它的那一重循环.
-
如果两个不同的正整数,他们的和是他们的积的因子,就称这两个数为兄弟数,小的称为弟数,大的称为兄数。先后输入正整数n和m(n < m) , 请在n至m这m-n+1个数中,找出一对兄弟数。如果找不到,就输出“No Solution.”。如果能找到,就找出和最小的那一对;如果有多对兄弟数和相同且都是最小,就找出弟数最小的那一对。
思路:在n-m中枚举每一对不同的数,看看是不是兄弟数。用两个变量记录当前已经找到的最佳兄弟数,如果发现更佳的,就重新记录(可以使用break加速枚举)。
#include <iostream>
using namespace std;
int main() {
int n,m;
cin >> n >> m;
int a = m + 1,b = m + 1; //a,b记录已经找到的最佳兄弟数,a是弟数,b是兄数,找最小初始化为最大
for(int i=n;i<m;++i) {//取弟数,共m-n种取法
if( i > (a + b)/2 + 1) //如果弟数已经>当前已找到最佳兄弟数和的一半 那么加上兄数肯定超过了 此时没必要再找兄数
break; // 跳出外重循环
for( int j = i + 1; j <= m; ++j ) { //取兄数
if(i+j>a+b) //此时的弟数+兄数超过了最佳兄弟数和 没必要继续遍历兄数 只会越来越大
break; //跳出内重循环 到外重循环继续遍历下一个弟数,再重新遍历兄数
if(i*j%(i+j)==0){ //发现兄弟数
if(i+j<a+b){ //发现和更小的兄弟数
a = i; b = j ; //更新已找到的最佳兄弟数
}
else if( i + j == a + b && i < a)//发现和相同但弟数更小的兄弟数
a = i; b = j; //更新已找到的最佳兄弟数
}
}
}
if(a==m+1) //初始的a 没有被更新过 即没找到兄弟数
cout<<"No solution.";
else
cout<<a<<","<<b;
return 0;
continue语句
- 可以出现在循环体中(for、while、do…while循环均可), 其作用是立即结束本次循环,并回到循环开头判断是否要进行下一次循环。
for( int i = 1;i <= 10 ;++i ) {
if( i % 2 ) //奇数 非0为true
continue; //导致不执行后面的语句,回到循环开头 判断是否进行下一次循环
cout << i << ","; //偶数输出 2,4,6,8,10
}
- 在多重循环的情况下,continue只对直接包含它的那重循环起作用。
2. OJ编程题输入数据的处理
scanf表达式的值
- scanf(…)表达式的返回值为int,表示成功读入的变量个数。
int n,m;
printf("%d",scanf("%d%d",&n,&m));
- scanf(…) 返回值为EOF(即-1)则说明输入数据已经结束
int n,m;
while(scanf("%d%d",&n,&m) != EOF) { //EOF是直接能拿来用的符号常量
printf("%d",n+m);
}
Windows系统下,不停输入两个整数再敲回车,则不停输出它们的和,直到输入 Ctrl+Z 然后回车(此时scanf会返回EOF),程序结束。
或者:
int n,m;
while(scanf("%d%d",&n,&m) == 2) {
printf("%d",n+m);
}
cin表达式的值
- cin >> m >> n … 表达式的值,在成功读入所有变量时为true,否则为false。
int n,m;
while(cin >> n >> m ) {
printf("%d",n+m);
}
不停输入两个整数再敲回车,则不停输出它们的和,直到输入: Ctrl+Z 然后回车,程序结束.
处理无结束标记的OJ题目输入
- 输入若干个(不知道多少个)正整数,输出其中的最大值
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, mx = 0; //找最大初始化为最小
while( cin >> n) {
if( n > mx )
mx = n; //更新最大
}
printf("%d",mx);
return 0;
}
或者:
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, mx = 0;
while(scanf("%d",&n) != EOF) {
//或 while(scanf("%d",&n) == 1) {
if( n > mx )
mx = n;
}
printf("%d",mx);
return 0;
}
3. 重定向输入
- 调试程序时,每次运行程序都要从键盘手动输入测试数据,太麻烦
- 可以将测试数据存入文件,然后用freopen将输入由键盘重定向为文件,则运行程序时不再需要手动输入数据了
#include <iostream>
using namespace std;
int main()
{
freopen("c:\\tmp\\test.txt","r",stdin); // 两个\\转义表示一个\ 提交oj时 这行要删掉或注释
//此后所有输入都来自文件 c:\tmp\test.txt 不用手动从键盘输入
int n, mx = 0;
while( cin >> n) {
if( n > mx )
mx = n;
}
printf("%d",mx);
return 0;
}
4. 循环例题选讲
- 例1.乘方计算
#include <iostream>
using namespace std;
int main()
{
int a,n;
cin >> a >> n;
int result = a; //初始化为a
for(int i = 0;i < n-1; ++i) //n-1次 乘a
result *= a;
cout << result ;
}
- 例2. 输入若干个整数求最大值
输入若干个整数(可正可负,不超过int的表示范围),输出最大值。
#include <iostream>
using namespace std;
int main()
{
int n,mx; //求最大 初始化mx为最小(之前求的是正整数序列最大值,可以把mx初始化为0);现在求整数序列最大,这个初始化最小值不容易确定,此时可以把mx初始化为输入序列中的第一个数,然后遍历更新mx
cin>>mx;
while(cin >> n) {
if(n>mx) {
mx = n;
}
}
cout << mx << endl;
return 0;
}
- 输入至少2个整数,求最大值和第二大值
#include <iostream>
using namespace std;
int main()
{
int n,max1,max2; //max1放第一个大的数 max2放第二大的数
int num = 0; // 输入的是第几个数
while(cin >> n) {
++num;
if( num == 1) //第一个数
max1 = n; //把最大的数初始化为第一个数
else if( num == 2) { //第二个数
if( n > max1) { //第二个数>当前最大的数(第一个数)
max2 = max1; //把当前最大的数 赋给第二大的数
max1 = n; //把第二个数 赋给最大的数
}
else
{
max2 = n; //把第二个数 赋给第二大的数
}
else{ //序列中的其他数
if(n>=max1){
max2 = max1;
max1 = n;
}
else if(n > max2){
max2 = n;
}
}
}
cout<<max1<<" "<<max2<<endl;
return 0;
- 斐波那契数列
菲波那契数列是指这样的数列: 数列的第一个和第二个数都为1, 接下来每个数都等于前面2个数之和。给出一个正整数k,要求菲波那契数列中第k个数是多少。
#include <iostream>
using namespace std;
int main()
{
int a1 = 1,a2 = 1; int k; //第1,2个数是1
cin >> k;
if( k == 1 || k == 2)
cout << 1 << endl;
else {
int sum;
for(int i = 0;i < k-2; ++i) { //剩k-2个数
sum = a1+a2;
a1 = a2;
a2 = sum;
}
cout << a2 << endl;
}
return 0;
}
- 求阶乘的和
最简单的解法是用两重循环,外重循环从1~n遍历每个数i,内重计算i!。
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int sum = 0;
for(int i = 1;i <= n; ++i) {
int factorial=1; //存放i阶乘
for(int j = 1; j <= i; ++j)
factorial *= j; //此操作做1+2+3+...+n次 O(n^2)
sum += factorial;
}
cout << sum ;
return 0;
}
更快的解法只需要一重循环。
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sum = 0;
int factorial = 1;
for(int i = 1;i <= n; ++i) {
factorial *= i;
sum += factorial; //此操作做n次 O(n)
}
cout << sum ;
return 0;
}
- 输入正整数n(n>=2),求不大于n的全部质数
解法1:
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
for(int i = 2; i <= n; ++i) { //每次判断i是否是质数
int k;
for(k = 2; k < i; ++k) {
if( i % k == 0) //除了1和本身还有别的因子 就不是质数
break;
}
if(k==i) //说明没有执行过break
cout<<i<<endl;
}
return 0;
}
此解法做了没必要的尝试, k 大于 i的平方根后就不必再试.(如果当k小于i的平方根时,没有因子,那么k大于i的平方根必然也没有,如果有的话,就说明k小于i的平方根时有一个和他对应的另一个因子,矛盾)
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n; //n>=2
cout << 2 << endl; //第一个质数
for(int i = 3; i <= n; i+=2) { //每次判断i是否是质数 从3开始只需要判断奇数就好了 偶数(>3)一定不是质数
int k;
for(k = 3; k < i; k+=2) { //只需要判断奇数即可
if( i % k == 0) //除了1和本身有因子 则不是质数
break; //跳出
if( k*k > i) //k大于i的平方根时不用做尝试了 即k*k>i 也可以用cmath库中的sqrt
break; //跳出
}
if( k*k > i) //如果是第二种情况跳出 i是质数
cout << i << endl;
}
return 0;
}