寫代碼,有兩類追求,一種是追求實用(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