NOIP学习小站
西安交通大学附属中学航天学校

循环结构例题(一)

一、累加求和

1.计算\(1+2+3+...+100\)(也就是\(\sum\limits_{i=1}^{100}i\))

前面章节已经具体分析过并给出了参考代码:

#include<iostream>
using namespace std;
int main()
{
	int i,s = 0;
	for(i=1;i<=100;i++){
		s += i;    //第i次循环:s累加上i
	}
	cout<<s; 
    return 0;
} 

注意上面程序循环体中的注释,我们以后再描述循环体中语句作用的时候,就会像 第i次循环:s累加上i 这样抽象地概括语句的作用,而不再是从细节上去说:第1次循环,s累加上1;第2次循环,s累加上2;...这样可以帮助我们从整体结构去构思程序的组成部分,然后再进一步细化程序各部分。

2.计算\(1\)-\(\frac{1}{2}\)+\(\frac{1}{3}\)-\(\frac{1}{4}\)+...+\(\frac{1}{99}\)-\(\frac{1}{100}\)

思路一: \( 原式 = (1+ \frac{1}{3} + \frac{1}{5} +...+ \frac{1}{99} )-( \frac{1}{2}+ \frac{1}{4}+...+ \frac{1}{100} ) \) ,可以使用累加器来计算两个累加和,然后做减法即可。

#include<iostream>
using namespace std;
int main()
{
	double s1 = 0,s2 = 0;
	int i;
	for(i=1;i<=99;i+=2){
		s1 += 1.0/i;
	}
	for(i=2;i<=100;i+=2){
		s2 += 1.0/i;
	}
	cout<<s1-s2<<endl;    
	return 0;
}
#include<iostream>
using namespace std;
int main()
{
	double s1 = 0,s2 = 0;
	int i;
	for(i=1;i<=99;i+=2){
		s1 += 1.0/i;
		s2 += 1.0/(i+1); 
	}
	cout<<s1-s2<<endl;    
	return 0;
}

思路二:仍然使用累加器实现,第i次累加使用的是1.0/i,不过奇数项是累加1.0/i,偶数项是累加-1.0/i

#include<iostream>
using namespace std;
int main()
{
	int i;
	double s = 0;
	for(i=1;i<=100;i++){
		if(i%2==0)
			s += -1.0/i;
		else
			s += 1.0/i;
	}
	cout<<s; 
    return 0;
} 
#include<iostream>
using namespace std;
int main()
{
	double s = 0;
	//控制变量i的定义和赋初值都在for循环中
	for(int i=1;i<=100;i++){
		if(i%2==0)
			s += -1.0/i; //s -= 1.0/i;
		else
			s += 1.0/i;
	}
	cout<<s; 
    return 0;
} 

上面的程序在循环体中出现了if语句,程序三大结构的使用是非常灵活的,可以相互嵌套。第二段程序将控制变量i的定义和赋初值都在for循环中,这时只能在循环中使用i,在for循环后的代码不能使用i(否则会出现编译错误)。

思路二:使用一个变量sign来记录第i项累加内容的符号,循环开始前sign = 1;在循环中使用sign *= -1;来实现sign在1和-1之间不断切换;累加的值就是 1.0*sign/i;

#include<iostream>
using namespace std;
int main()
{
	int i,sign = 1;
	double s = 0;
	for(i=1;i<=100;i++){
		s += 1.0*sign/i;
		sign *= -1;
	}
	cout<<s; 
    return 0;
}

这里使用赋初值int sign = 1;和多次执行sign *= -1;实现sign在1和-1之间不断切换。如果赋初值int f = 0;和多次执行f = 1-f;可以实现f在0和1之间不断切换。

3.计算\(\frac{1}{0.001}\)+\(\frac{1}{0.002}\)+\(\frac{1}{0.003}\)+...+\(\frac{1}{1.0}\)

初学者可能不假思索,直接使用double类型控制变量(后面会验证这里使用double类型控制变量是不可靠的):

#include<iostream>
using namespace std;
int main()
{
    //本程序使用double类型控制变量,计算结果与预期不一致
    double s = 0;
    for(double i=0.001;i<=1;i+=0.001){
        s += 1/i;
    }
	cout<<s<<endl;
    return 0;
}

程序输出结果:7484.47。是否正确呢?我们通过程序来验证。

可知原式=\(\frac{1000}{1}\)+\(\frac{1000}{2}\)+\(\frac{1000}{3}\)+...+\(\frac{1000}{999}\)+\(\frac{1000}{1000}\),这样可以使用int类型控制变量:

#include<iostream>
using namespace std;
int main()
{
    double s = 0;
    s = 0;
    for(int i=1;i<=1000;i++){
    	s += 1000.0/i;
	}
	cout<<s<<endl;
    return 0;
} 

程序输出结果:7485.47。和第一种解法结果不一致!我们修改第一段程序:

#include<iostream>
using namespace std;
int main()
{
    double s = 0;
    for(double i=0.001;i<=1;i+=0.001){
        s += 1/i;
        cout<<i<<endl;
    }
	cout<<s<<endl;
    return 0;
}

程序输出内容较多,这里给出后5行的输出:

0.996
0.997
0.998
0.999
7484.47

经过验证发现,循环累加并没有执行 s += 1/1.0;导致结果比预期少1。

特别注意的是,循环的控制变量尽量使用整数类型,使用浮点数类型的控制变量不可靠!我们来运行下面两段程序,会发现运行结果又相同(都是97876.1),用浮点类型的控制变量确实不可靠呀:

#include<iostream>
using namespace std;
int main()
{
    double s = 0;
    for(double i=0.0001;i<=1;i+=0.0001){
        s += 1/i;
    }
	cout<<s<<endl;
    return 0;
}
#include<iostream>
using namespace std;
int main()
{
    double s = 0;
    for(int i=1;i<=10000;i+=1){
        s += 10000.0/i;
    }
	cout<<s<<endl;
    return 0;
}

4.计算\(1\)+\(\frac{1}{2}\)+\(\frac{1}{3}\)+\(\frac{1}{4}\)+...问加到第几项累加和恰好超过5?

分析:仍然使用累加器求和,不过循环的条件不再是前面那样累加到多少项了,而是累加和\(s \le 5\):

#include<iostream>
using namespace std;
int main()
{
	int i;
	double s = 0;
	for(i=1;s<=5;i++){
		s += 1.0/i;
	}
	cout<<i; 
    return 0;
} 

程序运行结果输出84,其实应该是加到83项,累加和恰好超过5(5.00207),程序最后应该输出i-1而不是i。我们通过程序来加以验证(注意这里通过编写额外的程序来验证解决问题的程序是否正确的做法):

#include<iostream>
using namespace std;
int main()
{
	int i,n;
	cin>>n;
	double s = 0;
	for(i=1;i<=n;i++){
		s += 1.0/i;
	}
	cout<<s<<endl;	
	return 0;
}

输入84,输出5.01397;

输入83,输出5.00207;

输入82,输出4.99002。

通过程序验证,加到第i-1项(83项),累加和恰好超过5。

我们再来分析为什么程序应该输出i-1而不是i。for循环的条件是s<=5,那么出了循环后肯定有s>5,正是在循环里最后的一次累加导致s恰好超过了5,然后再次判断循环条件不成立退出了循环。但是要注意for循环的执行流程,每次执行了循环体(累加)后,还会自动执行i++,这就导致了本来最后一次循环累加的内容1.0/i是1.0/83,但累加后执行i++,i变成了84。也就是退出循环后累加项比i值少1,所以输出的结果应该是i-1:

#include<iostream>
using namespace std;
int main()
{
    int i;
    double s = 0;
    for(i=1;s<=5;i++){
        s += 1.0/i;
    }
    cout<<i-1; 
    return 0;
} 
#include<iostream>
using namespace std;
int main()
{
    double s = 0;
    //循环控制变量i在循环体中定义 
    for(int i=1;s<=5;i++){
        s += 1.0/i;
    }
    //出了循环后不能使用i,会出现编译错误 
    cout<<i-1; 
    return 0;
} 

5.给定n和k,计算1~n范围内的所有正整数能被k整除的数(第一类数)的和以及不能被k整除的数(第二类数)的和。

思路一:用两个累加器变量记录两类数的和,借助循环结构枚举处理1~n范围内的每个正整数i,在循环体中使用if语句来判断i能否被k整除进而将i加到对应累加器变量上。

#include<iostream>
using namespace std;
int main()
{
    int n,k,s1,s2;
	s1 = s2 = 0;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		if(i%k==0) s1 += i;
		else s2 += i;
	}
	cout<<s1<<" "<<s2;
    return 0;
}

思路二:借助循环结构可以枚举出1~n范围内的每个能被k整除正整数i,使用累加器求和,而1~n范围内的所有正整数的和可以使用等差数列求和公式计算,后者与前者做减法可以得出第二类数的和。

#include<iostream>
using namespace std;
int main()
{
    int n,k,s,s1=0;
	cin>>n>>k;
	//枚举1~n范围内的每个能被k整除正整数i(1倍k、2倍k、3倍k、...)
	for(int i=k;i<=n;i+=k){
		s1 += i;
	}
	//利用等差数列求和公式求所有数的和(高中数学知识)
	s = (1+n)*n/2;
	cout<<s1<<" "<<s-s1;
    return 0;
} 

可知第2种算法比起第1种算法,减少了循环执行的次数,执行效率优于第1种算法。

6.计算输入的10个整数的和。

分析:使用累加器求和。不过要输入10个整数,高效的方法是将cin语句置于重复执行10次的循环体中。

#include<iostream>
using namespace std;
int main()
{
    int n,s = 0;
    for(int i=1;i<=10;i++){
    	//每次都将输入的数据存放到变量n中,前面输入到n的值会被覆盖掉 
		//这也没有影响,因为变量n累加到s上后,n的值就没有用了,可以丢弃 
    	cin>>n;
    	s += n;
	}
	cout<<s;
    return 0;
}

需要特别注意的是,如果循环中同时存在输入输出的话,在命令下运行时会出现输入与输出“混杂”在一起的情况。但是在OJ测评和竞赛测评时,会自动将输入输出分开,输入在输入流中,输出在输出流中,测评时输入输出不会“混杂”在一起。所以可以一边输入一边处理输出的问题,可以放心在循环中同时处理输入输出。

二、求最值

问题描述:求给定的n个整数的最小值

输入格式:输入内容有n+1行,第1行是一个正整数n,后面的n行每一行是一个整数

输出格式:一行一个整数,就是输入的n个整数的最小值

分析:通过打擂台的方式求极值,不过数据的输入是需要首先解决的问题。程序先输入要计算最小值的整数的个数n,然后要输入n个整数,我们可以将输入语句cin放在重复执行n次的循环体中。

#include<iostream>
using namespace std;
int main()
{
    int n,m,min;
    cin>>n;
    for(int i=1;i<=n;i++){
    	//每次输入的整数都放在变量m中,会自动覆盖掉上次输入的数(也保存在m中) 
		//因为每次处理完输入的整数后,这个数后面不会再用到,可以丢弃 
    	cin>>m;
    	
    	//第一个数 或者 m比最小值小 
    	if(i==1 || m<min) min = m;
	}
	cout<<min;
    return 0;
} 

大家要关注并掌握这样的输入数据的方式,在竞赛中常用到。

也可以将第一次输入放在循环外面作为特殊处理:

#include<iostream>
using namespace std;
int main()
{
    int n,m,min;
    cin>>n>>min;    //第1个数直接输入到min中
    for(int i=2;i<=n;i++){      //输入并处理第2~n个数
    	cin>>m;
    	if(m<min) min = m;
	}
	cout<<min;
    return 0;
} 

三、最大公约数

问题:求两个正整数m和n的最大公约数

思路一:判断从1~m(或者从1~n,其实最好是1~\(min(m,n)\))的每个数i,如果i能够同时整除m和n,那么i就是m、n的公约数,最后的一个公约数就是最大公约数。

#include<iostream>
using namespace std;
int main()
{
    int m,n,ans;
    cin>>m>>n;
    for(int i=1;i<=min(m,n);i++){
    	//i是m和n的公约数,更新保存公约数的变量ans为i
    	if(m%i==0 && n%i==0) ans = i;
	}
    //循环是从小到大查找公约数的,所以循环结束后最大公约数就保存在ans中
	cout<<ans;
    return 0;
} 

思路二:在\(min(m,n)\)~1范围内从大到小查找m和n的公约数,找到的第一个公约数就是最大公约数,找到后可以使用break;语句强制跳出循环。

#include<iostream>
using namespace std;
int main()
{
    int m,n,i;
    cin>>m>>n;
    for(i=min(m,n);i>=1;i--){ 
    	//找到第一个公约数就是最大公约数,使用break;语句强制退出循环
    	//break;的作用是强制从该点退出到循环外
   	 //如果是for循环甚至不会执行第③部分控制变量变化语句直接强制退出循环
    	if(m%i==0 && n%i==0) break;
	}
	cout<<i;
    return 0;
} 

思路三:相减法求最大公约数,算法描述如下:

  1. 输入\(m\)和\(n\)
  2. 如果\(m=n\),那么\(m\)(或\(n\))就是最大公约数,输出\(m\)(或\(n\)),算法结束
  3. 如果\(m>n\),执行\(m ← m - n\)
  4. 如果\(m<n\),执行\(n ← n - m\)
  5. 返回第2步

其实就是当\(m!=n\)时,执行如下操作:如果\(m>n\),执行\(m = m - n\);否则执行\(n = n - m\)。

#include<iostream>
using namespace std;
int main()
{
    int m,n;
    cin>>m>>n;
    while(m!=n){
    	if(m>n) m -= n;
    	else n -= m;
	}
	cout<<m;
    return 0;
} 

思路四:辗转相除法(欧几里得算法)求最大公约数,算法描述如下:

  1. 输入\(m\)和\(n\)
  2. \(r←m\%n\),若\( r==0\),输出\(n\),算法结束。
  3. 互换:执行赋值\(m←n,n←r\),并返回第2步。
#include<iostream>
using namespace std;
int main()
{
    int m,n,r;
    cin>>m>>n;
    r = m%n;
    while(r!=0){
    	m = n;
    	n = r;
    	r = m%n;
	}
	cout<<n;
    return 0;
} 

当然也可以用do...while循环或者for循环实现:

#include<iostream>
using namespace std;
int main()
{
    int m,n,r;
    cin>>m>>n;    
    do{
    	r = m%n;
    	m = n;
    	n = r;
	}while(r!=0);
	cout<<m;   //注意这里是输出m 
    return 0;
} 
#include<iostream>
using namespace std;
int main()
{
    int m,n,r;
    cin>>m>>n;    
    for(r=m%n;r!=0;r=m%n){
    	m = n;
    	n = r;
	}
	cout<<n;
    return 0;
} 

关于“欧几里得算法”详细内容,可以查阅 百度百科相关词条。如果要计算最小公倍数,可以利用数学公理:两数之乘积=两数最大公倍数×两数最小公倍数。

四、寻找公倍数

问题:给定m和n,找出1~100000能被m和n整除的数

分析:通过循环一一枚举1~100000范围内的每个整数i,如果i能够同时被m和n整除,那么i符合条件输出i。这里是通过循环穷举每一个可能的数字,在循环体中用if语句来“筛选”出符合条件的数字。大家注意体会并掌握这样的程序结构。

#include<iostream>
using namespace std;
int main()
{
	int m,n;
	cin>>m>>n;
	//通过循环枚举1~100000范围内的每个整数
	for(int i=1;i<=100000;i++){
		//循环体中使用if语句筛选出符合条件的情况
		if(i%m==0 && i%n==0){
			cout<<i<<" ";
		}
	}
    return 0;
}

考虑到能够被m和n整除的数就是m和n的公倍数,也就是m和n的最小公倍数的1倍、2倍、3倍……这些数,那么先求出最小公倍数,再输出最小公倍数的1倍、2倍、3倍……这些数,比较前面的算法,这样的算法减少了循环的次数,提高了执行效率:

#include<iostream>
using namespace std;
int main()
{
	int m,n,r,p,q;
	cin>>m>>n;
	
	p = m*n;	//p是m、n的积
	
	//辗转相除法求最大公约数 
	for(r=m%n;r!=0;r=m%n){
        m = n;
        n = r;
    }
    
    //最大公约数是n,求最小公倍数 
    q = p/n;
    
    //输出最小公倍数的1倍、2倍、3倍...这些数 
	for(int i=q;i<=100000;i+=q){
		cout<<i<<" ";
	}
    return 0;
}

输出的数据可能很多,为了美观,每10个数字一行,每个数字占6位。可以使用计数器tot来记录找到的数的数量,每10个数也就是tot%10==0,此时输出换行:

#include<cstdio>
int main()
{
	int m,n,r,p,q;
	scanf("%d%d",&m,&n);
	
	p = m*n;	//p是m、n的积
	
	//辗转相除法求最大公约数 
	for(r=m%n;r!=0;r=m%n){
        m = n;
        n = r;
    }
    
    //最大公约数是n,求最小公倍数 
    q = p/n;
    
    int tot=0;
    //输出最小公倍数的1倍、2倍、3倍...这些数 
	for(int i=q;i<=100000;i+=q){
		printf("%6d ",i);
		tot++;
		if(tot%10==0) printf("\n");
		//上面一句也可以写成:
		//if(tot==10) tot=0,printf("\n");
	}
    return 0;
}

五、逆序数

问题:计算非负整数n的逆序数。例如123的逆序数是321,1200的逆序数是21。

分析:前面顺序结构我们计算过五位数的逆序数,使用的是/和%运算,使用整除10的某次方将指定位变到结果的个位数,再%10求出某位上的数字。现在输入的正整数n的位数不定,那应该如何处理呢?我们以一个数36495为例:

n%10取出了n个位上的数,n=n/10相当于把个位直接“砍掉”,通过这两个操作的重复执行,能够将整数n每位上的数字取出来。在拆分取数的同时,右边m是在“组装”逆序数,m初值为0,每次执行m=m*10+n%10;作用是将m扩大10倍然后将取出来的n个位上的数放到m个位数,通过这样的操作就能将逆序数“组装”到m中:

#include<iostream>
using namespace std;
int main()
{
	int n,m=0;
	cin>>n;
	while(n!=0){
		m = m*10+n%10;
		n /= 10;
	}
	cout<<m;
    return 0;
}

其实也可以不“组装”逆序后的整数,直接输出每次“拆”出来的个位部分,不过得想办法确保“高位”多余的0不输出。经过分析可知,只有“当前拆出来的个位数是0”的时候才考虑可能不用输出;但是并不是所有拆出来的0都不输出,如果之前拆出来过非0的数,那么当前拆出来的0也要输出。综上分析可知,只有“当前拆出来的数是0并且之前没有拆出来非0的数”的情况下不输出,其它情况都要输出拆出来的数:

#include<iostream>
using namespace std;
int main()
{
	//注意标记变量flag的使用 
	int n,flag = 0;
	cin>>n;
	//对于0,特判 
	if(n==0){
		cout<<0<<endl;
		return 0;    //提前结束程序 
	}
	while(n>0){
		//标记拆分过程中出现了非0的数 
		if(n%10 != 0) flag = 1;
		//注意这里条件的描述:只有“当前拆出来的数是0并且之前没有拆出来非0的数”的情况下不输出,其它情况都要输出拆出来的数
		//仔细琢磨,其实只要前面拆出来了非0的数,接下来拆出来的数都要输出,所以条件可以直接用 flag==1
		if(!(n%10==0 && flag==0))
			cout<<n%10;
		n /= 10;
	}    
	return 0;
}

巧妙解法:低位上的数会被先拆分出来,那么可以先把低位上连续的0全部拆出来,剩余的数正常拆分就行:

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;

    //对于0,特判 
    if(n==0){
        cout<<0<<endl;
        return 0;    //提前结束程序 
    }

    //先把低位上连续的0全部拆出来:拆出来的个位数是0的时候,一直“切掉”个位上的0
    while(n!=0 && n%10==0) n /= 10;

    //拆分剩余的
    while(n!=0){
        cout<<n%10;
        n /= 10;
    }
    return 0;
}

六、判断素数

问题:判断正整数n是否为素数。素数就是质数,也就是只能被1和自身整除的整数。

思路一:利用计数器统计正整数n的约数的数量,根据数量来判断。

#include<iostream>
using namespace std;
int main()
{
    int n,tot=0;
	cin>>n;
	if(n==1){       //特判1不是素数 
		cout<<"NO";
		return 0;   //main中运行到return语句整个程序结束 
	}
	//统计除1和自身外,n约数的数量 
	for(int i=2;i<n;i++){
		if(n%i==0) tot++;
	} 
	if(tot==0) cout<<"YES";
	else cout<<"NO";
    return 0;
} 

思路二:在2~n-1范围内找到任意一个n的约数,那么n就不是素数,可以使用break语句强制退出循环。

#include<iostream>
using namespace std;
int main()
{
	//变量find记录是否找到除1和自身外的约数
	//开始赋值为0,表示没有找到 
    int n,find = 0;
	cin>>n;
	if(n==1){       //特判1不是素数 
		cout<<"NO";
		return 0;   //main中运行到return语句整个程序结束 
	}
 
	for(int i=2;i<n;i++){
		if(n%i==0) {
			find = 1;  //赋值为1,表示找到 
			break;
		}
	}
	
	//判断find的值是否仍然为0(如果为0说明循环体中的if语句没有进去过) 
	if(find==0) cout<<"YES";
	else cout<<"NO";
    return 0;
} 

程序使用了一个特殊的变量find来标记是否找到了除1和自身外的其它约数,并人为约定find值为0表示未找到,值为1表示找到。那么在开始查找前,还没有找到,所以find赋初值0,在循环体中如果找到了一个约数,那么find赋值为1。出循环后只需要判断find的值是否仍然为0(0表示未找到),就能判断n是否为素数。像find这样的变量称为标记变量,用来标记一些特殊的状态(本例中用两个值0和1标记了未找到和找到两种状态)。标记变量的使用首先要人为约定特殊值表示的状态,并且在后面的操作中满足约定。

思路三:其实可以进一步缩小查找约数的范围,只需要在2~\(\sqrt n\)查找n的约数即可(根据数学知识可知,如果在2~\(\sqrt n\)找到一个i能够整除n,那么在\(\sqrt n\)~n范围内肯定还有一个整数n/i也能整除n;反之如果在2~\(\sqrt n\)找不到任何一个整数能够整除n,那么在\(\sqrt n\)~n范围内肯定也没有任何整数能整除n)。

#include<iostream>
using namespace std;
int main()
{
	//变量find记录是否找到除1和自身外的约数
	//开始赋值为0,表示没有找到 
    int n,find = 0;
	cin>>n;
	if(n==1){       //特判1不是素数 
		cout<<"NO";
		return 0;   //main中运行到return语句整个程序结束 
	}
 
	for(int i=2;i*i<=n;i++){     //使用i*i<=n巧妙地表示了i<=sqrt(n)这个循环条件
		if(n%i==0) {
			find = 1;  //赋值为1,表示找到 
			break;
		}
	}
	
	//判断find的值是否被修改成了1 
	if(find==0) cout<<"YES";
	else cout<<"NO";
    return 0;
} 

循环语句正常结束后循环的条件肯定不成立,而break强制退出循环后循环的条件仍然成立,根据这两点,不使用标记变量也能解决问题:

#include<iostream>
using namespace std;
int main()
{
    int n;
	cin>>n;
	if(n==1){       //特判1不是素数 
		cout<<"NO";
		return 0;   //main中运行到return语句整个程序结束 
	}
	
	int i;
	for(i=2;i*i<=n;i++){ //因为后面还要使用i值,这里不能用for(int i=2;i*i<=n;i++)
		if(n%i==0) break;
	}
	
	//如果前面循环正常结束,循环条件不成立,肯定有i*i>n
	//如果循环体中的break被执行,循环被强制结束,出了循环后循环条件依然成立
	//这里判断前面的循环条件不成立,那么break未被执行,也就是没有找到1和自身外的其他约数 
	if(i*i>n) cout<<"YES";
	else cout<<"NO";
    return 0;
} 

还可以借助return 0;语句来处理:

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    if(n==1){       //特判1不是素数 
        cout<<"NO";
        return 0;   //main中运行到return语句整个程序结束 
    }
    
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
        	cout<<"NO";
        	return 0;	//main中运行到return语句整个程序结束 
        }
    }
    
    //如果能执行到这里,那么意味着前面循环中没有找到任何约数i
	//也就意味着n是素数 
    cout<<"YES";
    return 0;
} 

七、分解质因数

问题:输入一个正合数n(合数就是除了1和本身外还有其它约数的整数),输出其分解质因数的结果。例如2520,分解质因数的结果是:\(2 \times 2 \times 2 \times 3 \times 3 \times 5 \times 7\)

分析:模拟数学中的短除法分解质因数。i初始值为2,判断n能否被i整除:如果能那么i就是n分解质因数的一个因子,n /= i,(这里不执行i++,保证n能够被i彻底分解),然后继续判断n能否被i整除;如果不能被整除,那么i++,然后n能否被i整除。直到n==1为止:

nin%i==0?输出执行n/=i?
n值
执行i++?
i值
25202Y21260N
12602Y2630N
6302Y2315N
3152NNN3
3153Y3105N
1053Y335N
353NNN4
354NNN5
355Y57N
75NNN6
76NNN7
77Y71N

从表格描述的过程分析可知,像4这样的约数不会出现在分解质因数结果中,因为肯定在前面会被分解成 \(2 \times 2\)。

#include<iostream>
using namespace std;
int main()
{
    int n,i=2;
    cin>>n;
    while(n!=1){
    	if(n%i==0){
			n /= i;
			cout<<i<<" ";
		}
    	else i++;
	}
    return 0;
} 

运行程序,输入2520,输出结果如下:

2 2 2 3 3 5 7

如果要输出乘号,如何修改程序呢?如果直接用cout<<i<<"×";可以猜想到肯定会多输出一个乘号。因为不是找到一个i就要输出i和乘号,找到的最后一个i不能输出乘号,所以这里要使用if语句分情况讨论。那么怎么描述是不是最后一个质因子呢?试试判断n与1的关系?

学习和循环语句的嵌套后,我们还会用其他方法来分解质因数。

八、break和continue

循环中break语句的作用是强制退出循环,要注意for循环中执行break后程序直接跳转到for循环后面的语句,不会执行for循环的第③部分修改程序控制变量的语句。

循环正常退出后,循环的条件肯定不再成立了;如果循环是被break强制退出的,出了循环后循环的条件依然成立。

除了break语句能够影响循环的执行流程,还有continue语句也能改变循环的执行流程,与break不同的是,continue只是不再执行本次循环体中continue后面的语句,直接进入下一次循环(如果是for循环会执行第③部分修改程序控制变量的语句)。一般情况下,break和continue都位于循环体的if语句中。

通过一个简单的例子来说明continue语句的使用,计算1~100范围内所有偶数的和,用continue语句来实现:

#include<iostream>
using namespace std;
int main()
{
    int s = 0;
	for(int i=1;i<=100;i++){
		//发现i是奇数,continue不作处理直接进入下次循环 
		if(i%2==1) continue;
		s += i;
	} 
	cout<<s;
    return 0;
} 

需要注意的是,这里只是演示需要使用了continue来解决问题,针对本问题更简洁的程序如下:

#include<iostream>
using namespace std;
int main()
{
    int s = 0;
	for(int i=2;i<=100;i+=2){
		s += i;
	} 
	cout<<s;
    return 0;
}