领会一些比较巧妙的算法

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

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

1. 素数判断

 作者给出了几种方法。

 方法一:

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

 方法二:

复制代码
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)即可。

 方法三:

复制代码
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开始判断所有的奇数。

方法四: 

复制代码
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开始判断,如果这个数不能被前面的素数整除,则这个数是素数。已经得到的素数保存在一个表格中。

复制代码
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是菱形的边长). 

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

复制代码
#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

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

复制代码
#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 幻方  
    }  
}  
复制代码

在matlab中可以通过magic函数得到魔方阵,比如magic(5)就是5阶魔方阵。看matlab中magic.m代码可以发现,它的实现也是分为三种情况的。
复制代码
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"),是不需要额外数组辅助的,只要头尾数据交换就可以了
然而,可能你不知道,仅仅使用字符串反转可以实现字符串循环移位:
复制代码
#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位取反

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值