正文

以空间换时间算法集锦(1)2007-04-20 14:36:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/goal00001111/25028.html

分享到:

     以前看过一篇文章“优化C代码常用的几招”,作者提到的第一招就是“以空间换时间”,还举了一个例子,由于比较经典,引用一下:

计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招--以空间换时间。比如说字符串的赋值:

方法A:通常的办法

#define LEN 32

char string1 [LEN];

memset (string1,0,LEN);

strcpy (string1,"This is a example!!");

方法B:

const char string2[LEN] ="This is a example!";

char * cp;

cp = string2; 

使用的时候可以直接用指针来操作。

从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有A好。在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。

 

笔者在编程练习过程中也遇到了不少可以用空间换时间的算法,把它们收集起来,以便初学者学习查阅。

1.桶式排序算法

最经典的应用是“桶式排序算法”。数组的排序算法很多,其中快速排序是在实践中最快的已知排序算法,它的平均运行时间是O(NlogN),堆排序算法在最坏的情况下,其时间复杂度也能达到O(nlogn)。相对于快速排序来说,这是它最大的优点,但是它需要一个记录大小供交换用的辅助存储空间-----其实这也是用空间换时间的表现。但是,当数组的元素是一些较小的整型数据(小于1000000)时,用“桶式排序算法”可以使时间复杂度降到O(N),可以很快地对年龄,成绩等整型数据进行排序。此外还可以使用桶式排序的方法求素数表。

“桶式排序算法”的代码也很简单,只要建立一个长度为max的字符数组就可以了,代码如下:

/*

函数功能:使用筒式排序法对数组进行排序,适用于元素值为较小整数

输入变量: int a[], 数组a

           int len,数组a的长度 

输出变量:无

返回值: 无

*/

void Sort(int a[], int len)

{

    int max = GetMax(a, len);

    char *p = new char[sizeof(char) * max + 1];

 

    for (int i=0; i<=max; ++i)

        p[i] = 0;

 

    for (int i=0; i<len; ++i)

        ++p[a[i]];

       

    for (int i=0,j=0; i<=max; ++i)

    {

       while (p[i] > 0)

        {

            a[j++] = i;

            --p[i];

        }

    }

   

    delete []p;

}

/*

函数功能:返回数组的最大值

输入变量: int a[], 数组a

           int len,数组a的长度 

输出变量:无

返回值: 数组的最大值

*/

int GetMax(int a[], int len)

{

    int max = a[0];

   

    for (int i=1; i<len; i++)

    {

        if (max < a[i])

            max = a[i];

    }

   

    return max;

}

 

2.求第n个子集的所有元素

一个好汉三个帮,三个好汉九个帮,九个好汉二十七个帮,...停,今天不是让你算n个好汉几个帮(有兴趣的网友可以自己算),我们来看这个集合{ 1, 3, 9, 27, 81, ... }

这个集合是一个递增的无限集,每个元素都是3的幂。

我们把这个集合的所有子集按照元素和从小到大的顺序排好: {}(空集), { 1 }, { 3 }, { 1, 3 }, { 9 }, { 1, 9 }, { 3, 9 }, { 1, 3, 9 }, ...

现在给定一个正整数n,求第n个子集的所有元素,并按从大到小的顺序排列好

例:

n的值    结果
1        { }
4        { 3, 1 }
6        { 9, 1 }
500      { 6561, 2187, 729, 243, 81, 3, 1 }    

细节要求:
1. n的范围: n >= 1 ,unsigned int
2. 接口:    void HeroNeedThree(INT64* subset, unsigned int n);
   其中INT64是指64位整数,可以用 typedef long long INT64; 也可以自定义
   subset就是存放结果的数组,其中subset[0]存放结果子集的元素个数,
   然后子集元素从大到小存入subset[1],subset[2],......

 

算法分析:除去空集,观察前n个子集

{1},----------------------------------------2^0 个子集

{3},{1,3},--------------------------------2^1 个子集

{9},{1,9},{3,9},{1,3,9} --------------2^2 个子集

{27},{1,27},{3,27},{1,3,27},{9,27},{1,9,27},{3,9,27},{1,3,9,27}----------------------------------------2^3 个子集

从上表可以看出对于有k个元素的集合,对应的有2^k(k<=32)个子集,而且每一行子集序列的第一个子集元素的大小恰好是3^0,3^1,3^2,3^3,…3^(k-1)。

我们把n转换成二进制数,当n=5时,它的二进制数为0000…0101(共32位),即2^0+2^2,对应的子集为{3^03^2},即{1,9};

n=13时,它的二进制数为0000…1101(共32位),即2^0+2^2+2^3,对应的子集为{3^03^23^3},即{1,9,27};

由此可得,要求第n个子集(注意还要加上空集,实际上是第n+1个),则只需将其转换成32位的二进制,则它就是所求的子集的二进制序列。把二进制中值为1的位对应的元素作为3的指数,求出3的幂就是对应的元素。

根据此算法我们很容易的编出程序:

主函数:

#include <stdio.h>

#include <time.h>

#include <stdlib.h>

 

#define MAX_SIZE     ( 50 )

 

typedef long long INT64;

 

void HeroNeedThree(INT64* subset, unsigned int n);

 

int main(int argc, char *argv[])

{

        INT64 subset[MAX_SIZE] = {0};

        int i;

       

        for (int k=1; k<500; k++)

        {

        HeroNeedThree(subset, k);

 

        printf("%d: { ", k);

        for (i = 0; subset[i] > 0; ++i)

        {

                printf("%I64d, ", subset[i]);

        }

        printf("}\n");

        for (int i=0; i<MAX_SIZE; i++)

            subset[i] = 0;

       }

 

        system("pause");

        return 0;

}

功能函数:

INT64 Power(unsigned int x, unsigned int n)//求x的n次幂

{

    if (n == 0)

        return 1;

    INT64 s = Power(x, n/2);

    s *= s;

    if (n % 2 == 0)

        return s;

    else

        return s * x;

}

 

void HeroNeedThree(INT64* subset, unsigned int n)

{

    int t = 0;

    unsigned int m, s;

   

    n--;

    while (n > 0) //求n的二进制数的每一位

    {

        for (s=n,m=0; s>1; s>>=1)         

m++;

        subset[++t] = Power(3, m);

        n -= 1 << m;

    }

    subset[0] = t;

}

 

在上面的程序中,我们每次都要去求3的m次幂,虽然我们采用分治法求幂,速度较快,但是当n值教大时,还是需要很多时间的,因为long long类型是32位的,我们可以预先把3的0-31次幂全部求出来,存放在数组中,这样就不用每次都进行求幂运算了,可以节省不少时间。同样的,我们也可以把2的0-31次幂全部求出来,存放在数组中,这样就不必每次都去判断二进制数中各个1所在的位置,而直接用下标表示了。

改进后的代码如下:

void HeroNeedThree(INT64* subset, unsigned int n)

{

    // base3[k] = 3^k

    static INT64 base3[32] =

    {

0x1LL,0x3LL,0x9LL,0x1bLL,0x51LL,0xf3LL,0x2d9LL,0x88bLL,0x19a1LL,0x4ce3LL,0xe6a9LL,0x2b3fbLL,0x81bf1LL,0x1853d3LL,0x48fb79LL,0xdaf26bLL,0x290d741LL,0x7b285c3LL,0x17179149LL,0x4546b3dbLL,0xcfd41b91LL,0x26f7c52b3LL,0x74e74f819LL,0x15eb5ee84bLL,0x41c21cb8e1LL,0xc546562aa3LL,0x24fd3027fe9LL,0x6ef79077fbbLL,0x14ce6b167f31LL,0x3e6b41437d93LL,0xbb41c3ca78b9LL,0x231c54b5f6a2bLL

    };

    // base2[k] = 2^k

static unsigned int base2[32] =

{

0x1LL,0x2LL,0x4LL,0x8LL,0x10LL,0x20LL,0x40LL,0x80LL,0x100LL,0x200LL,0x400LL,0x800LL,0x1000LL,0x2000LL,0x4000LL,0x8000LL,0x10000LL,0x20000LL,0x40000LL,0x80000LL,0x100000LL,0x200000LL,0x400000LL,0x800000LL,0x1000000LL,0x2000000LL,0x4000000LL,0x8000000LL,0x10000000LL,0x20000000LL,0x40000000LL,0x80000000LL

    };

    INT64 num = 0;

    n--;

    for (int i=31; i>=0; --i)

    {

        if (n & base2[i]) // 计算n的第i位上是否是1

        {

            subset[++num] = base3[i];

        }

    }

    subset[0] = num;

}

阅读(4599) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册