尋找醜數及關於程序優化效率的一點說明

1、問題描述

若是一個整數值含有因數2,3,5(包括1和該整數自己)的整數稱爲醜數(Ugly Number)。換句話說醜數ugly_number是能夠表示成形以下面表達式的形式,表達式中的i,j,k均是大於等於0的整數。 git

舉個例子,18 = 2 * 3 * 3,因此18是一個醜數。而14 = 2 * 7,因此14不是一個醜數。 程序員

如今有個需求就是要找到第2013個醜數。 github

2、問題分析

解決問題的思路顯然是要根據醜數的性質。主要有兩種思考的切入點。 面試

第一種方法,按照整數正好三順序一個一個找,並判斷該整數是否是醜數,若是是醜數的個數加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) 學習

第二種方法將給出具體的程序實現,(注意數據類型的選取,防止溢出)。 測試

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;
}

4、一點提升效率的說明

下面咱們來計算一下程序中的for循環運行的時間,須要用到time.h,這個的用法還在總結中,總結好會在會C/C++學習中給出來。 ui

下面先給出計算時間函數: google

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循環中最好不要頻繁的調用一個實現簡單功能的函數。可是不要覺得這個違反了時間複雜度和空間複雜度是一對矛盾體,由於第一種測試中會調用一個額外的函數,因此比較浪費空間,並且每次都調用也比較費時。

總的來講,要想提高時間效率就會須要拿空間來換,要想節約空間就要拿時間來換。

5、參考資料

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

相關文章
相關標籤/搜索