正文

以空间换时间算法集锦(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 int2. 接口:    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^0,3^2},即{1,9}; 当n=13时,它的二进制数为0000…1101(共32位),即2^0+2^2+2^3,对应的子集为{3^0,3^2,3^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; }

阅读(4813) | 评论(0)


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

评论

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