寻找丑数及关于程序优化效率的一点说明

风格不统一 提交于 2019-12-07 00:06:33

一、问题描述

如果一个整数值含有因数2,3,5(包括1和该整数本身)的整数称为丑数(Ugly Number)。换句话说丑数ugly_number是可以表示成形如下面表达式的形式,表达式中的i,j,k均是大于等于0的整数。

举个例子,18 = 2 * 3 * 3,所以18是一个丑数。而14 = 2 * 7,所以14不是一个丑数。

现在有个需求就是要找到第2013个丑数。

二、问题分析

解决问题的思路显然是要根据丑数的性质。主要有两种思考的切入点。

第一种方法,按照整数正好三顺序一个一个找,并判断该整数是不是丑数,如果是丑数的个数加1,如果不是继续查找下一个,直到找到问题要求的第N个丑数即可,这种方法的效率不是很好,但是很好理解,所以在这里只给出伪代码。伪代码如下:

while(i < N)
   if(true == judge_ugly_number(number))
      i++;
      ugly_numbers[i] = number;
      number++;
   else
      number++;
 判断一个数是否是丑数的伪代码如下:
while(0 == number % 2)
    number /= 2;
while(0 == number % 3)
    number /= 3;
while(0 == number % 5)
    number /= 5;

if(1 == number)
    reutrn true;
else
    return false;

第二种方法。第一种方法的思路是遍历整数并判断该整数是不是丑数。而第二种方法的思路则是根据丑数的性质直接计算丑数。

为实现方便我们可以另ugly_numbers[0] = 1。那么来计算第一个丑数,根据丑数的性质,它只含有因数2,3,5所以我们的丑数等于ugly_numbers[0]乘以这些因数。如果已经求得第i个丑数,那么怎么来求第i+1个丑数呢。我们需要找到大于第i个丑数中最小的那个,那么我们需要在i前面的几个丑数中分别乘以2,3,5找到那个大于第i个丑数的最小的那个。所以我们还需要保留第i个前面的那几个丑数的下标。从第一个丑数开始,如果该丑数是ugly_numbers * 2,则对2的index1进行加1,如果该丑数是ugly_numbers * 3,则对2的index2进行加1如果该丑数是ugly_numbers * 5,则对5的index3进行加1。要好好分析这句话,根据计算顺序,显然一个丑数是2,第二个是3,第三个是4,第四个是5,第五个是6,而6可以用丑数2乘以因数3得到,也可以用丑数3乘以因数2得到。所以求出丑数六后需要对index1和index2都要进行加1操作。这个叙述的可能不太清楚,请读者根据后面的程序实现在做进一步理解吧。(如果还不是很清楚的话可以参考后面的参考资料2和3)

第二种方法将给出具体的程序实现,(注意数据类型的选取,防止溢出)。

三、程序实现

#include <stdio.h>

#define MAX_NUMBER 2048

long min(long a, long b, long c)
{
	long temp = a < b ? a : b;
	return temp < c ? temp : c;
}

int main(int argc, char *argv[])
{
	long ugly_numbers[MAX_NUMBER];
	ugly_numbers[0] = 1;
	int i = 1;
	int index1 = 0;
	int	index2 = 0;
	int	index3 = 0;
	int position;

	long min(long, long, long);

	for(; i < MAX_NUMBER; i++)
	{
		ugly_numbers[i] = min(2 * ugly_numbers[index1],
					 3 * ugly_numbers[index2],
					 5 * ugly_numbers[index3]);

		if(ugly_numbers[i] == 2 * ugly_numbers[index1])
			index1++;
		if(ugly_numbers[i] == 3 * ugly_numbers[index2])
			index2++;
		if(ugly_numbers[i] == 5 * ugly_numbers[index3])
			index3++;
	}

	printf("Please input a integer number, 
		we'll find the ugly number in that position(number < 2048)\n");
	
	while(EOF != (scanf("%d", &position)))
	{
		printf("the ugly number is %ld\n", ugly_numbers[position]);
		printf("Please input a integer number, 
			we'll find the ugly number in that position(number < 2048)\n");
	}
	
	return 0;
}

四、一点提高效率的说明

下面我们来计算一下程序中的for循环运行的时间,需要用到time.h,这个的用法还在总结中,总结好会在会C/C++学习中给出来。

下面先给出计算时间函数:

void count_time(struct timespec start, struct timespec end)
{
	struct timespec countTime;
	long duration;
	const long NANOSECOND = 1000000000l;

	if(0 > (end.tv_nsec - start.tv_nsec))
	{
		countTime.tv_sec = end.tv_sec - start.tv_sec - 1;
		countTime.tv_nsec = NANOSECOND + end.tv_nsec - start.tv_nsec;
	}
	else
	{
		countTime.tv_sec = end.tv_sec - start.tv_sec;
		countTime.tv_nsec = end.tv_nsec - start.tv_nsec;
	}
	duration = NANOSECOND * (int)countTime.tv_sec + countTime.tv_nsec;
	printf("compute the ugly numbers took %ld nsec \n",duration); 
}
我们回到程序中去测试那个for循环的耗时:
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpStart);
	for(; i < MAX_NUMBER; i++)
	{
		ugly_numbers[i] = min(2 * ugly_numbers[index1],
					 3 * ugly_numbers[index2],
					 5 * ugly_numbers[index3]);

		if(ugly_numbers[i] == 2 * ugly_numbers[index1])
			index1++;
		if(ugly_numbers[i] == 3 * ugly_numbers[index2])
			index2++;
		if(ugly_numbers[i] == 5 * ugly_numbers[index3])
			index3++;
	}
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpEnd);
	count_time(tpStart, tpEnd);

这里测试5次,可以得出这5次的耗时(单位是纳秒):56859,57432,55523,56880,57316,可以计算得出平均耗时为56802。

上述的测试程序中每次都在调用min函数,我们现在在这个for循环内部实现这个功能并测试其耗时:

clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpStart);
	for(; i < MAX_NUMBER; i++)
	{
		temp = (2 * ugly_numbers[index1]) < (3 * ugly_numbers[index2]) ? (2 * ugly_numbers[index1]) : (3 * ugly_numbers[index2]);
		temp = temp < (5 * ugly_numbers[index3]) ? temp : (5 * ugly_numbers[index3]);

		if(ugly_numbers[i] == 2 * ugly_numbers[index1])
			index1++;
		if(ugly_numbers[i] == 3 * ugly_numbers[index2])
			index2++;
		if(ugly_numbers[i] == 5 * ugly_numbers[index3])
			index3++;
	}
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpEnd);
	count_time(tpStart, tpEnd);

依然测试5次, 可以得出这5次的耗时(单位是纳秒):25767,26283,25473,25607,27326,可以计算得出平均耗时为26091。

结果很明显,第二个测试比第一个测试要省多一半的时间。节省的时间就是每次for循环都要调用min函数的时间。而且要是考虑空间开销的话,测试的第二种方法会比较省空间,所以在for循环中最好不要频繁的调用一个实现简单功能的函数。但是不要以为这个违反了时间复杂度和空间复杂度是一对矛盾体,因为第一种测试中会调用一个额外的函数,所以比较浪费空间,而且每次都调用也比较费时。

总的来说,要想提升时间效率就会需要拿空间来换,要想节约空间就要拿时间来换。

五、参考资料

1. More Programming Perls Confessions of a Coder, Jon Bentley

2. google面试题目 寻找丑数--使用double防止数据溢出:
http://blog.csdn.net/shihui512/article/details/8833568

3. 程序员面试题精选100题(37)-寻找丑数[算法]:
http://zhedahht.blog.163.com/blog/static/2541117420094245366965/


说明:

如有错误还请各位指正,欢迎大家一起讨论给出指导。

上述程序完整代码的下载链接:
https://github.com/zeliliu/BlogPrograms/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%26%E7%AE%97%E6%B3%95/ugly%20numbers

最后更新时间:2013-05-05

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!