• 领会一些比较巧妙的算法


    昨天看到了一些比较巧妙的算法,觉得挺不错的。

    参考:http://blog.csdn.net/csdn_zc/article/details/6776929

    1. 素数判断

     作者给出了几种方法。

     方法一:

    View Code
    int isPrime(int n) //函数返回1表示是质数,返回0表示不是质数  
    {  
    int i;  
    for (i = 2; i < n; i++)  
    if (n % i == 0)  
    return 0;  
    return 1;  
    }  
    这个是最原始的做法:从2开始,检测数n是否能被其之前的任意一个数整除,如果能,说明其不是素数。

     方法二:

    View Code
    int isPrime(int n)  
    {  
    int i;  
    for (i = 2; i*i<n; i++)  
    if (n % i == 0)  
    return 0;  
    return 1;  
    }  

    对上面算法的优化是缩小搜索范围,将范围有[2,n)缩小到[2, sqrt(n)). 因为对于一个小于n的整数x,如何n不能整除x,则n必然不能整除n/x,反之,相同,所以只需到sqrt(n)即可。

     方法三:

    View Code
    int isPrime(int n)  
    {  
    int i;  
    if (n <= 3return 1;  
    if (n % 2 == 0return 0;  
    for (i = 3; i*i<n; i += 2)  
    if (n % i == 0)  
    return 0;
    return 1;  
    }  
    这个方法是删除掉偶数。因为我们知道,一个数如果不能被2整除,那么也就不能被4、6、等所有的偶数整除。所以我们可以把循环规则改变成先判断2,如果不能被2整除就从3开始判断所有的奇数。

    方法四:

    View Code
    int isPrime(int n)  
    {  
    int i, step = 4;  
    if (n <= 3return 1;  
    if (n % 2 == 0return 0;  
    if (n % 3 == 0return 0;  
    for (i = 5; i*i <n; i += step)  
    {  
    if (n % i == 0)  
    return 0;  
    step ^= 6;  
    }  
    return 1;  
     这个方法比较巧妙:我们可以考虑这个事实:所有大于4的质数,被6除的余数只能是1或者5比如接下来的5,7,11,13,17,19都满足。所以,我们可以特殊化先判断2和3。但后面的问题就出现了,因为并非简单的递增,从5开始 是+2,+4,+2,+4,....这样递增的,这样的话,循环应该怎么写呢?从上面的程序中可以看到,每次循环,让step从2变4,或者从4变2,而 所采用的方法就是:一个 step ^= 6; 完成step在2和4之间转换(这个 ^ 符号是C里的异或运算)

     另外:如果要判断一个范围内,比如100之内的素数,我们可以这样做:从2开始判断,如果这个数不能被前面的素数整除,则这个数是素数。已经得到的素数保存在一个表格中。

    View Code
    void print_prime(int n)
    {
        int table[100];
        int i,k,j;
        table[0]=2;
        k=1;
        for(i=3;i<n;i++)
        {
            for(j=0;j<k;j++)
                if(i%table[j]==0)
                    break;
            if(j>=k)
                table[k++]=i;
        }
        for(i=0;i<k;i++)
        {
            printf("%3d",table[i]);
            if((i+1)%10==0)
                printf("\n");
        }
        printf("\n");

    }

    2. 菱形打印

    作者使用了坐标的思想,

    我们知道,要打印的图案是这种:
       *
      ***
     *****
      ***
       *
     如果将菱形中间的点作为坐标原点的话,可以看到“*”所在的点的左边满足|x|+|y|<=c(c是菱形的边长). 

     所以可以得到下面的代码:

    View Code
    #include <stdio.h>
    #define ABS(x) (x<0?-(x):(x))
    int main(void)
    {
        int i,j;
        int c=2;
        for(i=-c;i<=c;i++)
        {
            for(j=-c;j<=c;j++)
            {
                if(ABS(i)+ABS(j)<=c)
                    printf("*");
                else
                    printf(" ");
            }
            printf("\n");

        }
        return 0;
    }

     其他的类型的形状可以使用同样的思路解决。

    3. 魔方矩阵

     参考:http://blog.csdn.net/cmutoo/article/details/6492895

     此处的总结比较全面,大家可以看看

    View Code
    #include<stdio.h>  
    #include<math.h>  
    #define MAX 30  
      
    int a[MAX][MAX];    // 幻方矩阵  
    int n,s;    // n:阶数,s:幻方数  
    int x,y;  
    int i,j,k;  
    int total,m;  
    int ox,oy;  
      
    void main()  
    {  
        void odd(int m, int index);  
        void singleEven();  
        void FourXFour();  
        void doubleEven();  
          
        do  
        {  
            printf("Please input n(3<=n[<=17]):\t");    // 屏幕可显示的最大阶数为17  
            scanf("%d",&n);  
            if(n<3continue;   // 幻方最小阶数  
      
            s=n*(pow(n,2)+1)/2// 幻方数  
            printf("s=%d\n",s);  
      
            if(n%2==1){  
                // 奇阶幻方  
                ox=oy=0;  
                odd(n,0);   // 从1开始填写n阶幻方  
            }  
            else if(n%4==0)  
            {  
                // 双偶阶幻方  
                doubleEven();  
                  
            }  
            else if(n%2==0)  
            {  
                // 单偶阶幻方  
                singleEven();  
                  
            }  
            // 输出制作好的n阶幻方  
            for(i=0;i<n;i++)  
            {  
                s=0;  
                for(j=0;j<n;j++)  
                    s+=a[i][j],printf("%4d",a[i][j]);  
                printf("\t=%d\n",s);  
            }  
      
            fflush(stdin);  // 清除多余或无效的输入  
        }while(1);  
    }  
      
    /* 奇数阶幻方 
    最经典的填法是罗伯特法(楼梯法),填写方法是这样: 
    把1(或最小的数)放在第一行正中;按以下规律排列剩下的n×n-1个数:  
    (1)每一个数放在前一个数的右上一格; 
    (2)如果这个数所要放的格已经超出了顶行那么就把它放在底行,仍然要放在右一列; 
    (3)如果这个数所要放的格已经超出了最右列那么就把它放在最左列,仍然要放在上一行; 
    (4)如果这个数所要放的格已经超出了顶行且超出了最右列,那么就把它放在前一个数的下一行同一列的格内; 
    (5)如果这个数所要放的格已经有数填入,处理方法同(4)。 
    这种写法总是先向“右上”的方向,象是在爬楼梯。 
     
    三阶幻方: 
     
       8   1   6 
       3   5   7 
       4   9   2    
    */  
      
    // 解奇阶幻方的通用模块  
    // m 为阶数  
    // index 为起始标识  
    void odd(int m, int index)  
    {  
        x=m/2;  
        y=0;  
        for(i=index+1;i<=index+pow(m,2);i++)  
        {  
            a[oy+y][ox+x]=i;  
            if(i%m==0) y++;  
            else x++,y--;  
            // else x++,y+=2; Hourse法  
            x=(x%m+m)%m;  
            y=(y%m+m)%m;  
        }  
    }  
      
    /* 单偶阶幻方 
    n为偶数,且不能被4整除 (n=6,10,14,18,22……;n=4k+2,k=1,2,3,4,5……) 
    以n=10为例。这时,k=2 
    (1) 把方阵分为A,B,C,D四个象限,这样每一个象限肯定是奇数阶。 
    用楼梯法,依次在A象限,D象限,B象限,C象限按奇数阶幻方的填法填数。 
     
    6阶幻方第一步: 
     
       8   1   6 | 26  19  24 
       3   5   7 | 21  23  25 
       4   9   2 | 22  27  20 
    ------------------------- 
      35  28  33 | 17  10  15 
      30  32  34 | 12  14  16 
      31  36  29 | 13  18  11 
     
    (2) 在A象限的中间行、中间格开始,按自左向右的方向,标出k格。 
    A象限的其它行则标出最左边的k格。 
    将这些格,和C象限相对位置上的数,互换位置。 
     
    6阶幻方第二步: 
     
      35*  1   6 | 26  19  24 
       3  32*  7 | 21  23  25 
      31*  9   2 | 22  27  20 
    ------------------------- 
       8* 28  33 | 17  10  15 
      30   5* 34 | 12  14  16 
       4* 36  29 | 13  18  11 
     
    (3) 在B象限任一行的中间格,自右向左,标出k-1列。 
    (注:6阶幻方由于k-1=0,所以不用再作B、D象限的数据交换) 
    将B象限标出的这些数,和D象限相对位置上的数进行交换,就形成幻方。 
     
    6阶幻方: 
     
      35   1   6 | 26  19* 24 
       3  32   7 | 21  23* 25 
      31   9   2 | 22  27* 20 
    ------------------------- 
       8  28  33 | 17  10* 15 
      30   5  34 | 12  14* 16 
       4  36  29 | 13  18* 11   
    */  
      
    void singleEven()  
    {  
        int temp;  
        // 步骤一  
        
    // A象限  
        ox=oy=0;  
        odd(n/2,pow(n/2,2)*0);  
        // D象限  
        ox=oy=n/2;  
        odd(n/2,pow(n/2,2)*1);  
        // B象限  
        ox=n/2,oy=0;  
        odd(n/2,pow(n/2,2)*2);  
        // C象限  
        ox=0,oy=n/2;  
        odd(n/2,pow(n/2,2)*3);  
        // 对已经按ADBC象限以奇阶方式填充的幻方做处理  
        m=(n-2)/4;  
        for(i=0;i<n/2;i++)  
        {     
            // 步骤二  
            for(j=0;j<m;j++)  
            {  
                k=(i==n/4)?n/4+j:j;  
                temp=a[i][k];  
                a[i][k]=a[i+n/2][k];  
                a[i+n/2][k]=temp;  
      
            }  
            // 步骤三  
            for(j=0;j<m-1;j++)  
            {  
                k=n/2+n/4+j;  
                temp=a[i][k];  
                a[i][k]=a[i+n/2][k];  
                a[i+n/2][k]=temp;  
            }  
        }  
    }  
      
    /* 双偶阶幻方 
    n为偶数,且能被4整除 (n=4,8,12,16,20……;n=4k,k=1,2,3,4,5……) 
    互补:如果两个数字的和,等于幻方最大数和最小数的和,即 n*n+1,称为互补。
    */  
      
    /* 四阶幻方 
    将数字从左到右、从上到下按顺序填写: 
     
       1   2   3   4 
       5   6   7   8 
       9  10  11  12 
      13  14  15  16 
     
    将对角线上的数字,换成与它互补的数字。 
    这里,n×n+1 = 4×4+1 = 17; 
    把1换成17-1 = 16; 
    把6换成17-6 = 11; 
    把11换成17-11 = 6; 
    把16换成17-16 = 1; 
    …… 
    换完后就是一个四阶幻方。 
     
      16*  2   3  13* 
       5  11* 10*  8 
       9   7*  6* 12 
       4* 14  15   1* 
    */  
      
    void FourXFour()  
    {  
        // 对已填写数字的4阶幻方进行对角线互补替换  
        for(i=0;i<4;i++)  
        {  
            a[oy+i][ox+i]=total-a[oy+i][ox+i];  
            a[oy+i][ox+(4-i-1)]=total-a[oy+i][ox+(4-i-1)];  
        }  
    }  
      
    /* 对于n=4k阶幻方,我们先把数字按顺序填写。 
     
       1   2   3   4   5   6   7   8 
       9  10  11  12  13  14  15  16 
      17  18  19  20  21  22  23  24 
      25  26  27  28  29  30  31  32 
      33  34  35  36  37  38  39  40 
      41  42  43  44  45  46  47  48 
      49  50  51  52  53  54  55  56 
      57  58  59  60  61  62  63  64 
     
    写好后,按4*4把它划分成k*k个方阵。 
    因为n是4的倍数,一定能用4*4的小方阵分割。 
     
       1   2   3   4 |  5   6   7   8 
       9  10  11  12 | 13  14  15  16 
      17  18  19  20 | 21  22  23  24 
      25  26  27  28 | 29  30  31  32 
    --------------------------------- 
      33  34  35  36 | 37  38  39  40 
      41  42  43  44 | 45  46  47  48 
      49  50  51  52 | 53  54  55  56 
      57  58  59  60 | 61  62  63  64 
     
    然后把每个小方阵的对角线上的数字换成互补的数字,就构成幻方。 
     
      64*  2   3  61*| 60*  6   7  57* 
       9  55* 54* 12 | 13  51* 50* 16 
      17  47* 46* 20 | 21  43* 42* 24 
      40* 26  27  37*| 36* 30  31  33* 
    --------------------------------- 
      32* 34  35  29*| 28* 38  39  25* 
      41  23* 22* 44 | 45  19* 18* 48 
      49  15* 14* 52 | 53  11* 10* 56  
       8* 58  59   5*|  4* 62  63   1* 
    */  
      
    void doubleEven()  
    {  
        // 填写数字  
        x=y=0;  
        for(i=1;i<=pow(n,2);i++)  
        {  
            a[y][x]=i;  
            if(i%n==0) x=0,y++;  
            else x++;  
        }  
      
        total=1+pow(n,2);   // 最大数和最小数的和  
      
        
    // 以 4x4 大小分割幻方  
        m=n/4;  
        x=y=0;  
        ox=oy=0;  
        for(k=1;k<=pow(m,2);k++)  
        {  
            // 对每个 4x4 幻方做对角互补替换  
            FourXFour();  
            if(k%m==0) ox=0,oy+=4;  
            else ox=k%m*4;  // 转移到下一个 4x4 幻方  
        }  
    }  

    #include<stdio.h>  
    在matlab中可以通过magic函数得到魔方阵,比如magic(5)就是5阶魔方阵。看matlab中magic.m代码可以发现,它的实现也是分为三种情况的。
    View Code
    function M = magic(n)
    %MAGIC  Magic square.
    %   MAGIC(N) is an N-by-N matrix constructed from the integers
    %   1 through N^2 with equal row, column, and diagonal sums.
    %   Produces valid magic squares for all N > 0 except N = 2.

    %   Copyright 1984-2002 The MathWorks, Inc. 
    %   $Revision: 5.15 $  $Date: 2002/04/15 03:44:23 $

    % Historically, MATLAB's magic was a built-in function.
    % This M-file uses a new algorithm to generate the same matrices.

    n = floor(real(double(n(1))));

    % Odd order.
    if mod(n,2) == 1
       [J,I] = meshgrid(1:n);
       A = mod(I+J-(n+3)/2,n);
       B = mod(I+2*J-2,n);
       M = n*A + B + 1;

    % Doubly even order.
    elseif mod(n,4) == 0
       [J,I] = meshgrid(1:n);
       K = fix(mod(I,4)/2) == fix(mod(J,4)/2);
       M = reshape(1:n*n,n,n)';
       M(K) = n*n+1 - M(K);

    % Singly even order.
    else
       p = n/2;
       M = magic(p);
       M = [M M+2*p^2; M+3*p^2 M+p^2];
       if n == 2, return, end
       i = (1:p)';
       k = (n-2)/4;
       j = [1:k (n-k+2):n];
       M([i; i+p],j) = M([i+p; i],j);
       i = k+1;
       j = [1 i];
       M([i; i+p],j) = M([i+p; i],j);
    end

     4.字符串循环移位

     问题,给你一个字符串,要求循环左移n位

    比如对"abcdefg" 循环左移2位,我们要得到"cdefgab"
    附加条件,不能使用连续辅助空间(包括动态分配),只能使用若干单个变量(即O(1)空间)
    首先,我们知道,反转一个字符串操作("abcd"变"dcba"),是不需要额外数组辅助的,只要头尾数据交换就可以了
    然而,可能你不知道,仅仅使用字符串反转可以实现字符串循环移位:
    View Code
    #include <stdio.h>  
    #include <string.h>  
    //反转字符串,把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;  
    }  
    int main()  
    {  
    char str[] = "abcdefghijklmnopqrstuvwxyz";  
    puts( str_shl(str, str + strlen(str), 6) );  
    return 0;  
    }  

     参考:http://blog.csdn.net/csdn_zc/article/details/6776853

     其中也介绍了数字循环移位的问题:

     C语言中没有提供循环移位的操作符,但可以通过简洁的方式实现循环移位

    设一个操作数x有s位则循环左移n位的操作为:
    (x << n) | (x >> (s - n));
    同理右移n位位:
    (x >> n) | (x << (s - n));
    实际编程中可以用宏定义实现循环移位:
    #define ROTATE_LEFT(x, s, n) ((x) << (n)) | ((x) >> ((s) - (n)))
    #define ROTATE_RIGHT(x, s, n) ((x) >> (n)) | ((x) << ((s) - (n)))
    几个对位操作的样例如下:
    #define SETBIT(REG,N) REG|=(1<<N) //对REG的N位置1
    #define CLRBIT(REG,N) REG&=~(1<<N) //对REG的N位清零

    #define INVBIT(REG,N) REG^=(1<<N) //对REG的N位取反

     
  • 相关阅读:
    使用jsonEditor打造一个复杂json编辑器
    【原创】一次“诡异”的容器Unix Socket通信问题分析
    【原创】Ingress-Nginx-Controller的Metrics监控源码改造简析
    IDEA+DevTools实现热部署功能
    ElementUI按需引入各种组件
    vue-cli4.0更新后怎样将eslint关闭
    Mysql修改字段名、修改字段类型
    博客搬家CSDN
    如何优雅的处理Restful
    java系列之注解
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2346369.html
Copyright © 2020-2023  润新知