• C语言循环的小艺术(转)


    原地址:http://blog.csdn.net/csdn_zc/article/details/6776929

    1. 质数判断

    对于这个,很多人可能会直接这样写:


    1. int isPrime(int n) //函数返回1表示是质数,返回0表示不是质数   
    2. {  
    3. int i;  
    4. for (i = 2; i < n; i++)  
    5. if (n % i == 0)  
    6. break;  
    7. return i >= n;  
    8. }  


    又或者,有的人知道平方根的优化:

    1. int isPrime(int n)  
    2. {  
    3. int i, s = (int)(sqrt((double)n) + 0.01);  
    4. for (i = 2; i <= s; i++)  
    5. if (n % i == 0)  
    6. break;  
    7. return i > s;  
    8. }  


    再或者,消除偶数:

    1. int isPrime(int n)  
    2. {  
    3. int i, s = (int)(sqrt((double)n) + 0.01);  
    4. if (n <= 3) return 1;  
    5. if (n % 2 == 0) return 0;  
    6. for (i = 3; i <= s; i += 2)  
    7. if (n % i == 0)  
    8. break;  
    9. return i > s;  
    10. }  


    当然,这样还不是很够的话,我们可以考虑这个事实:
    所有大于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 <stdio.h>   
    2. #include <math.h>   
    3.   
    4. int isPrime(int n)  
    5. {  
    6. int i, s = (int)(sqrt((double)n) + 0.01), step = 4;  
    7. if (n <= 3) return 1;  
    8. if (n % 2 == 0) return 0;  
    9. if (n % 3 == 0) return 0;  
    10. for (i = 5; i <= s; i += step)  
    11. {  
    12. if (n % i == 0)  
    13. break;  
    14. step ^= 6;  
    15. }  
    16. return i > s;  
    17. }  
    18.   
    19. int main()  
    20. {  
    21. int n;  
    22. for (n = 2; n < 100; ++n) //找出 2 - 100 的质数并输出   
    23. {  
    24. if (isPrime(n)) printf("%d,", n);  
    25. }  
    26. getchar();  
    27. return 0;  
    28. }  


    如上代码,一个 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 的点

    由此可得

    1. #include <stdio.h>   
    2. #define IABS(x) ( (x) >= 0 ? (x) : -(x) ) //定义一个计算绝对值的宏   
    3. void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1   
    4. {  
    5. int x, y;  
    6. for (y = -size; y <= size; y++)  
    7. {  
    8. for (x = -size; x <= size; x++)  
    9. {  
    10. if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size   
    11. putchar('*');  
    12. else  
    13. putchar(' ');  
    14. }  
    15. putchar('\n');  
    16. }  
    17. }  
    18.   
    19. int main()  
    20. {  
    21. print(5); //输出一个半径为5的菱形   
    22. getchar();  
    23. return 0;  
    24. }  


    如果我需要得到空心菱形呢?非常非常简单,因为菱形边界上的点,满足的是|x| + |y| == c
    所以,我们只要把那个if里的小于等于号,改成双等于号 == 就可以了

    再类似地,如果我不要*号,我要最外层是字母A,然后里一层是B这样呢?即:
        A
      ABA
    ABCBA
      ABA
        A

    那么,我们只要在putchar那里做一个字符计算:

    1. void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1   
    2. {  
    3. int x, y;  
    4. for (y = -size; y <= size; y++)  
    5. {  
    6. for (x = -size; x <= size; x++)  
    7. {  
    8. if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size   
    9. putchar( 'A' + (size - IABS(x) - IABS(y)) ); //留意这里的计算方法   
    10. else  
    11. putchar(' ');  
    12. }  
    13. putchar('\n');  
    14. }  
    15. }  


    类似地,如果我们要打印的是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;

    这样,我们就可以把原来复杂的循环,化简成一重简单循环

    于是有程序:

    1. #include <stdio.h>   
    2. #define SIZE 5 //定义幻方阶数,这个数只能是奇数   
    3. int main()  
    4. {  
    5. int x, y, i, sqSize, hSize;  
    6. int sqMap[SIZE][SIZE];  
    7. sqSize = SIZE * SIZE;  
    8. hSize = SIZE / 2;  
    9. //计算1至SIZE * SIZE的数的位置并记录   
    10. for ( i = 0; i < sqSize; i++)  
    11. {  
    12. x = hSize + 1 + i + i / SIZE;  
    13. y = hSize + i - i / SIZE;  
    14. sqMap[y % SIZE][x % SIZE] = i + 1;  
    15. }  
    16. //以下是输出   
    17. for (y = 0; y < SIZE; y++)  
    18. {  
    19. for (x = 0; x < SIZE; x++)  
    20. printf("%4d", sqMap[y][x]);  
    21. puts("");  
    22. }  
    23. return 0;  
    24. }  


    这个比你网上能找到的很多求奇数阶幻方的代码都短小很多(不过网上较多称之为魔方阵,不知为何)



    4. 字符串循环移位

    问题,给你一个字符串,要求循环左移n位
    比如对"abcdefg" 循环左移2位,我们要得到"cdefgab"

    附加条件,不能使用连续辅助空间(包括动态分配),只能使用若干单个变量(即O(1)空间)

    首先,我们知道,反转一个字符串操作("abcd"变"dcba"),是不需要额外数组辅助的,只要头尾数据交换就可以了

    然而,可能你不知道,仅仅使用字符串反转可以实现字符串循环移位:


    1. //反转字符串,把st与ed所指向的中间的内容反转(包含st不包含ed)   
    2. void str_rev(char* st, char *ed)  
    3. {  
    4. for (--ed; st < ed; ++st, --ed)  
    5. {  
    6. char c;  
    7. c = *st; *st = *ed; *ed = c;  
    8. }  
    9. }  


    1. //用三反转等效左移字符串(st与ed之间,包含st不包含ed的内容)   
    2. char* str_shl(char* st, char* ed, int n)  
    3. {  
    4. str_rev(st, &st[n]);  
    5. str_rev( &st[n], ed);  
    6. str_rev(st, ed);  
    7. return st;  
    8. }  

    1. #include <stdio.h>   
    2. #include <string.h>   
    3. int main()  
    4. {  
    5. char str[] = "abcdefghijklmnopqrstuvwxyz";  
    6. puts( str_shl(str, str + strlen(str), 6) );  
    7. getchar();  
    8. return 0;  
    9. }  


    这里,如果要循环左移n位,只要把原来字符串分成两段,前n字符,和后面其它字符

    两段分别反转,最后再整体反转,就实现了循环左移(如果先整体再两部分,就是循环右移)

  • 相关阅读:
    098实战 Job的调度
    maven在windows下的安装
    Map的知识点梳理(不包含collections工具类)
    001 LRU-缓存淘汰算法
    Android渲染机制和丢帧分析
    Android性能优化典范
    正确使用Android性能分析工具——TraceView
    android View 绘制完成监听
    那些年我们用过的显示性能指标
    view, surfaceView, invalidate, postInvalidate, 刷新屏幕
  • 原文地址:https://www.cnblogs.com/pulan/p/2921624.html
Copyright © 2020-2023  润新知