C语言循环的小艺术(1) by Misakamm
- 格式:pdf
- 大小:142.56 KB
- 文档页数:10
1质数判断1
C语言循环的小艺术(1)
作者:Misakamm
博客:http://misakamm.com
前言
写代码,有两类追求,一种是追求实用(Coder),一种是追求代码艺术(Artist)我是
那种追实用追腻了,偶然追一下艺术(就是偶然和艺术有一腿)的那种Coder。
本文作者Misakamm见网上故意删除作者信息然后发布的行为,觉得很不愉快,特此重
新精细排版此文,并精简和美化代码,发布连载文。本文采用Latex排版。
很多人,已经习惯了for(i=0;i
高,但在特殊场合,特殊的循环写法,不但能提升循环的效率,还能使代码更精巧
1质数判断
对于这个,很多人可能会直接这样写
1intisPrime(intn)//函数返回1表示是质数,返回0表示不是质数
2{
3inti;
4for(i=2;i
5if(n%i==0)
6break;
7returni>=n;
8}
又或者,有的人知道平方根的优化:
1intisPrime(intn)
2{
3inti,s=(int)sqrt(n+0.01);
4for(i=2;i<=s;i++)
5if(n%i==0)
6break;
7returni>s;
8}
再或者,消除偶数:
1intisPrime(intn)
2{1质数判断2
3inti,s=(int)sqrt(n+0.01);
4if(n<=3)
5return1;
6if(n%2==0)
7return0;
8for(i=3;i<=s;i+=2)
9if(n%i==0)
10break;
11returni>s;
12}
当然,这样还不是很够的话,我们可以考虑这个事实:所有大于4的质数,被6除
的余数只能是1或者5。比如接下来的5,7,11,13,17,19都满足。所以,我们可以
特殊化先判断2和3。但后面的问题就出现了,因为并非简单的递增,从5开始是
+2,+4,+2,+4,....这样递增的。这样的话,循环应该怎么写呢?
首先,我们定义一个步长变量step,循环大概是这样
for(i=5;i<=s;i+=step)
那么,就是每次循环,让step从2变4,或者从4变2
于是,可以这么写:
1#include
2#include
3intisPrime(intn)
4{
5inti,s=(int)sqrt(n+0.01),step=4;
6if(n<=3)
7return1;
8if(n%2==0)
9return0;
10if(n%3==0)
11return0;
12for(i=5;i<=s;i+=step)
13{
14if(n%i==0)
15break;
16step^=6;
17}
18returni>s;
19}
20intmain(void)
21{1质数判断3
22intn;
23for(n=2;n<100;++n)//找出2-100的质数并输出
24{
25if(isPrime(n))
26printf(”%d,”,n);
27}
28getchar();
29return0;
30}
如上代码,一个stepˆ=6;完成step在2和4之间转换(这个ˆ符号是C
里的异或运算)
理由是,2化二进制是010,4是100,6是110,于是2异或4得到6:
2ˆ4=>6
6ˆ2=>4
6ˆ4=>2
于是利用异或,就可以构造这种步长在两个值之间来回变化的循环
思考题:前面说的是双值循环,那么如何构造三值或者四值循环?2菱形打印4
2菱形打印
很多人,打印菱形在控制台的思路是,把菱形上下拆分,分两段很接近的代码来打印
其实这样代码很不好看,并且不好阅读
我们知道,要打印的图案是这种:
1*
2***
3*****
4***
5*
满足上下对称,左右对称,那么,你能不能也弄一个二重循环,同样是对称的?很简单,
首先我们要抛开习惯性思维,for循环不一定要在0开始或者0结束。我们可以让循环从
-c到c,这样不就轻松产生一个对称的吗?(只要取个绝对值)我们把菱形的中心看成是坐
标0,0,那么,会输出星号的坐标,是|x|+|y|c的点
由此可得
1#include
2#include
3voidprint(intsize)//size是这个菱形的半径,直径会是size*2+1
4{
5intx,y;
6for(y=−size;y<=size;y++)
7{
8for(x=−size;x<=size;x++)
9{
10//x和y各自的绝对值的和,即|x|+|y|<=size
11if(abs(x)+abs(y)<=size)
12putchar(’*’);
13else
14putchar(’␣’);
15}
16putchar(’\n’);
17}
18}
19intmain(void)
20{
21print(5);//输出一个半径为的菱形5
22getchar();
23return0;
24}2菱形打印5
当然,有些人还会使用三目运算把代码简化成以下这个样子:
1voidprint(intsize)
2{
3intx,y;
4for(y=−size;y<=size;y++,putchar(’\n’))
5for(x=−size;x<=size;x++)
6putchar(abs(x)+abs(y)<=size?’*’:’␣’);7}
不过下文就不使用以上写法了。
如果需要得到空心菱形呢?非常非常简单,因为菱形边界上的点,满足的是|x|+|y|=c
所以,我们只要把那个if里的小于等于号,改成双等于号==就可以了
再类似地,如果我不要*号,我要最外层是字母A,然后里一层是B这样呢?即
1A
2ABA
3ABCBA
4ABA
5A
那么,我们只要在putchar那里做一个字符计算:
1voidprint(intsize)
2{
3intx,y;
4for(y=−size;y<=size;y++)
5{
6for(x=−size;x<=size;x++)
7{
8if(abs(x)+abs(y)<=size)
9putchar(’A’+(size−abs(x)−abs(y));//留意这里的计算方法10else
11putchar(’␣’);
12}
13putchar(’\n’);
14}
15}
类似地,如果我们要打印的是X形:2菱形打印6
1**
2**
3*
4**
5**
同样可以利用这个思路完成,这题就作为思考题吧3奇数阶幻方7
3奇数阶幻方
所谓幻方(最基本的那种),就是横,竖,对角线上的数的和等于一个常数的数字方阵
438
951
276
以上这个图,有什么规律?容易写成代码吗?
我们把这个图,向右复制五次,向下复制三次,展开一下:
438438438438438
95[1]951951951951
276[2]76276276276
4384[3]8[4]38438438
9519519[5]1951951
27627627[6]2[7]6276
43843843843[8]438
951951951951[9]51
276276276276276
注意括号数字的走向
怎么样,现在呢?
现在看起来显得规律性强了很多,但是,你会不会觉得循环还是不太好写?
我们如何从一个给定的n,直接得知它的坐标呢?
不难,找一下规律就可以发现对于任意的数值n+1有(以左上角为0,0坐标):
x=2+n+⌊n3⌋
y=1+n−⌊n3⌋
其实这个规律可以简单扩展到任意奇数阶幻方(以下size是奇数):
x=⌊size2⌋+1+n+⌊nsize⌋
y=⌊size2⌋+n−⌊nsize⌋
这样,我们就可以把原来复杂的循环,化简成一重简单循环
于是Misakamm有程序
1#include
2#defineSIZE5//定义幻方阶数,这个数只能是奇数
3intmain(void)
4{
5intx,y,i,sqSize,hSize;
6intsqMap[SIZE][SIZE];
7sqSize=SIZE*SIZE;3奇数阶幻方8
8hSize=SIZE/2;
9//计算1至SIZE*SIZE的数的位置并记录
10for(i=0;i
11{
12x=hSize+1+i+i/SIZE;
13y=hSize+i−i/SIZE;
14sqMap[y%SIZE][x%SIZE]=i+1;
15}
16//以下是输出
17for(y=0;y
18{
19for(x=0;x
20printf(”%4d”,sqMap[y][x]);
21puts(””);
22}
23return0;
24}
这个比你网上能找到的很多求奇数阶幻方的代码都短小很多(不过网上较多称之为魔方
阵,不知为何)