领会一些比较巧妙的算法

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

参考: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位取反

 

 

 

posted @ 2012-02-11 11:00  Mr.Rico  阅读(973)  评论(0编辑  收藏  举报