首页 > 代码库 > 3n+1问题

3n+1问题

猜想: 对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半。
    经过若干次这样的变换,一定会使n变为1。例如3->10->5->16->8->2->1。
    输入n,输出变换的次数。n≤10^9。
     样例输入:3
     样例输出:7

不假思索的写出下面的代码:

#include<stdio.h>int main(void){	int n;	int count=0;		scanf("%d", &n);		while(n > 1)	{			if(n%2 != 0)			n = 3*n + 1;		else			n /= 2;		count++;	}		printf("%d\n", count);    return 0;}

然而,程序正确吗?很不幸,如果输入987654321,答案为1,这显然是错误的。通过调试或者在循环体中用printf语句打印出n的值,看到n的值为负数,导致一次循环后程序退出。从这里可以获悉n的值溢出了,因为整型最大值为2^31-1 = 2147483647,大约为21亿,而由题意n的最大值为10亿(10^9),所以在n的值颇大且为奇数时乘以3是危险的,会导致溢出。

解决方案如下:

因为奇数*奇数=奇数,所以经过n=3*n+1的计算后,n的值必然是偶数,并且下次循环必然做运算n/=2,所以这里可以合并这两步,也就是n为奇数的情况下做运算n=floor(1.5*n+0.5),由于double值的误差问题,我们可以用n=floor(1.5*n+1)(floor函数接收double类型的参数,返回不大于给定参数的最大整形数,返回值类型为double),将取整后的double值赋给整形从而丢弃小数点。

修改后的程序如下:

#include<stdio.h>#include<math.h>int main(void){	int n;	int count=0;		scanf("%d", &n);	while(n > 1)	{		if(n%2 != 0)        {            n = floor(1.5*n + 1);            count += 2;        }		else		{		    n = n / 2;		    count++;		}	}	printf("%d\n", count);    return 0;}

再次用987654321测试,得到结果180次。再者,如果对n的限制为整形int的范围,该程序就不能完成要求了,那么我们可以使用long long int。

 

参考资料:《算法竞赛入门经典》——刘汝佳

 

最后一些疑惑(望看到的高手能解答一二,吾将不胜感激):

1、这里第二个程序是我依照作者的提示写的,自认应该正确吧:-),但是还是有一些疑惑,比如我们考虑了第一次n的输入值,但是循环

  中的第二次,第三次...呢?我们如何说明以后循环的值不会发生溢出呢?

  比如开始时n的值为7,那么一次循环后其值为11,而11>7。也就是说二次循环的值大于7。

2、再者,我们如何知道经过若干次的变换后一定会得到1呢,也就是说我们如何知道函数收敛呢?嗯...这貌似是一个数学问题啊。

3n+1问题