写代码,有两类追求,一种是追求实用(Coder),一种是追求代码艺术(Artist) 我是那种追实用追腻了,偶然追一下艺术(就是偶然和艺术有一腿)的那种Coder 不少人,已经习惯了for(i=0; i<n; i++)这种单调的循环,虽然这的确的使用率最高, 但在特殊场合,特殊的循环写法,不但能提高循环的效率,还能使代码更精巧 1. 质数判断 对于这个,不少人可能会直接这样写: int isPrime(int n) //函数返回1表示是质数,返回0表示不是质数 { int i; for (i = 2; i < n; i++) if (n % i == 0) break; return i >= n; } 又或者,有的人知道平方根的优化: int isPrime(int n) { int i, s = (int)(sqrt((double)n) + 0.01); for (i = 2; i <= s; i++) if (n % i == 0) break; return i > s; }
再或者,消除偶数: int isPrime(int n) { int i, s = (int)(sqrt((double)n) + 0.01); if (n <= 3) return 1; if (n % 2 == 0) return 0; for (i = 3; i <= s; i += 2) if (n % i == 0) break; return i > s; } 固然,这样还不是很够的话,咱们能够考虑这个事实: 全部大于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 因而,能够这么写: #include <stdio.h> #include <math.h> int isPrime(int n) { int i, s = (int)(sqrt((double)n) + 0.01), step = 4; if (n <= 3) return 1; if (n % 2 == 0) return 0; if (n % 3 == 0) return 0; for (i = 5; i <= s; i += step) { if (n % i == 0) break; step ^= 6; } return i > s; } int main() { int n; for (n = 2; n < 100; ++n) //找出 2 - 100 的质数并输出 { if (isPrime(n)) printf("%d,", n); } getchar(); return 0; } 如上代码,一个 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.菱形打印 不少人,打印菱形在控制台的思路是,把菱形上下拆分,分两段很接近的代码来打印, 其实这样代码很很差看,而且很差阅读 咱们知道,要打印的图案是这种: * *** ***** *** * 知足上下对称,左右对称,那么,你能不能也弄一个二重循环,一样是对称的? 很简单,首先咱们要抛开习惯性思惟,for循环不必定要在0开始或者0结束 咱们可让循环从 -c 到 c ,这样不就轻松产生一个对称的吗?(只要取个绝对值) 咱们把菱形的中心当作是坐标0,0,那么,会输出星号的坐标,是 |x| + |y| <= c 的点 由此可得 #include <stdio.h> #define IABS(x) ( (x) >= 0 ? (x) : -(x) ) //定义一个计算绝对值的宏 void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1 { int x, y; for (y = -size; y <= size; y++) { for (x = -size; x <= size; x++) { if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size putchar('*'); else putchar(' '); } putchar('\n'); } } int main() { print(5); //输出一个半径为5的菱形 getchar(); return 0; } 若是我须要获得空心菱形呢?很是很是简单,由于菱形边界上的点,知足的是|x| + |y| == c 因此,咱们只要把那个if里的小于等于号,改为双等于号 == 就能够了 再相似地,若是我不要*号,我要最外层是字母A,而后里一层是B这样呢?即: A ABA ABCBA ABA A 那么,咱们只要在putchar那里作一个字符计算: void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1 { int x, y; for (y = -size; y <= size; y++) { for (x = -size; x <= size; x++) { if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size putchar( 'A' + (size - IABS(x) - IABS(y)) ); //留意这里的计算方法 else putchar(' '); } putchar('\n'); } } 相似地,若是咱们要打印的是X形: * * * * * * * * * 一样能够利用这个思路完成,这题就做为思考题吧 3. 奇数阶幻方 所谓幻方(最基本的那种),就是横,竖,对角线上的数的和等于一个常数的数字方阵 4 3 8 9 5 1 2 7 6 以上这个图,有什么规律?容易写成代码吗? 咱们把这个图,向右复制五次,向下复制三次,展开一下: 4 3 8 4 3 8 4 3 8 4 3 8 4 3 8 9 5 [1] 9 5 1 9 5 1 9 5 1 9 5 1 2 7 6 [2] 7 6 2 7 6 2 7 6 2 7 6 4 3 8 4 [3] 8 [4] 3 8 4 3 8 4 3 8 9 5 1 9 5 1 9 [5] 1 9 5 1 9 5 1 2 7 6 2 7 6 2 7 [6] 2 [7] 6 2 7 6 4 3 8 4 3 8 4 3 8 4 3 [8] 4 3 8 9 5 1 9 5 1 9 5 1 9 5 1 [9] 5 1 2 7 6 2 7 6 2 7 6 2 7 6 2 7 6 注意中括号数字的走向 怎么样,如今呢? 如今看起来显得规律性强了不少,可是,你会不会以为循环仍是不太好写? 咱们如何从一个给定的n,直接得知它的坐标呢? 不难,找一下规律就能够发现对于任意的数值n+1有(以左上角为0,0坐标): x = 2 + n + n / 3; y = 1 + n - n / 3; 其实这个规律能够简单扩展到任意奇数阶幻方(如下size是奇数): x = size / 2 + 1 + n + n / size; (注意这里的除法是取整除法,不带小数) y = size / 2 + n - n / size; 这样,咱们就能够把原来复杂的循环,化简成一重简单循环 因而有程序: #include <stdio.h> #define SIZE 5 //定义幻方阶数,这个数只能是奇数 int main() { int x, y, i, sqSize, hSize; int sqMap[SIZE][SIZE]; sqSize = SIZE * SIZE; hSize = SIZE / 2; //计算1至SIZE * SIZE的数的位置并记录 for ( i = 0; i < sqSize; i++) { x = hSize + 1 + i + i / SIZE; y = hSize + i - i / SIZE; sqMap[y % SIZE][x % SIZE] = i + 1; } //如下是输出 for (y = 0; y < SIZE; y++) { for (x = 0; x < SIZE; x++) printf("%4d", sqMap[y][x]); puts(""); } return 0; } 这个比你网上能找到的不少求奇数阶幻方的代码都短小不少(不过网上较多称之为魔方阵,不知为什么) 4. 字符串循环移位 问题,给你一个字符串,要求循环左移n位 好比对"abcdefg" 循环左移2位,咱们要获得"cdefgab" 附加条件,不能使用连续辅助空间(包括动态分配),只能使用若干单个变量(即O(1)空间) 首先,咱们知道,反转一个字符串操做("abcd"变"dcba"),是不须要额外数组辅助的,只要头尾数据交换就能够了 然而,可能你不知道,仅仅使用字符串反转能够实现字符串循环移位: //反转字符串,把st与ed所指向的中间的内容反转(包含st不包含ed) void str_rev(char* st, char *ed) { for (--ed; st < ed; ++st, --ed) { char c; c = *st; *st = *ed; *ed = c; } } //用三反转等效左移字符串(st与ed之间,包含st不包含ed的内容) char* str_shl(char* st, char* ed, int n) { str_rev(st, &st[n]); str_rev( &st[n], ed); str_rev(st, ed); return st; } #include <stdio.h> #include <string.h> int main() { char str[] = "abcdefghijklmnopqrstuvwxyz"; puts( str_shl(str, str + strlen(str), 6) ); getchar(); return 0; } 这里,若是要循环左移n位,只要把原来字符串分红两段,前n字符,和后面其它字符 两段分别反转,最后再总体反转,就实现了循环左移(若是先总体再两部分,就是循环右移) 而在那个字符串反转函数里,参与循环的,再也不是int,而是两个指针, 为何选择使用两个指针呢?若是你写一个str_rev(char* str, int len)的版本,相信你就明白了,这里很少废话 好了,先写这么多,其它的留下次了,xixi