以前看过一篇文章“优化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^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;
}
评论