算法 之 3n+1问题

卡拉兹(Callatz)猜想:

  对任何一个自然数n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把(3n+1)砍掉一半。这样一直反复砍下去,最后一定在某一步得到n=1。卡拉兹在1950年的世界数学家大会上公布了这个猜想,传说当时耶鲁大学师生齐动员,拼命想证明这个貌似很傻很天真的命题,结果闹得学生们无心学业,一心只证(3n+1),以至于有人说这是一个阴谋,卡拉兹是在蓄意延缓美国数学界教学与科研的进展……

猜想内容: 对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半。经过若干次这样的变换,一定会使n变为1。例如3->10->5->16->8->2->1。
      输入n,输出变换的次数。n≤10^9。
      样例输入:3
      样例输出:7    (有的博客输出为5是考虑的变化次数 这里是把每一次出现的都算作一次)

#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)/2;
        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;
}

 使用 long long版本的亦可以算出正确结果

#include<stdio.h>
int main()
{
    int n2;
    int count=0;
    
    scanf("%d", &n2);
    long long n = n2;

    while(n > 1)
    {  
        if(n%2 != 0)
            n = (n * 3 + 1)/2;
        else
            n = n / 2;
        count++;
    }
    printf("%d\n", count);
    return 0;
}

 

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

 

经验:

1、c99支持了for循环()内定义变量

2、while(scanf("%d%d",&begin,&end)!=EOF);

 

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

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

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

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

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

关于这个问题的答案我是在数学科普神犇顾森的博客中找到的,其博客地址在这里Matrix67(顺便推荐,很好的博客呢),原文见这里千万别学数学:最折磨人的数学未解之谜(一),下面把与该问题相关的文字摘录在这里:

数学之美不但体现在漂亮的结论和精妙的证明上,那些尚未解决的数学问题也有让人神魂颠倒的魅力。和 Goldbach 猜想、 Riemann 假设不同,有些悬而未解的问题趣味性很强,“数学性”非常弱,乍看上去并没有触及深刻的数学理论
,似乎是一道可以被瞬间秒杀的数学趣题,让数学爱好者们“不找到一个巧解就不爽”;但令人称奇的是,它们的困难程度却不亚于那些著名的数学猜想,这或许比各个领域中艰深的数学难题更折磨人吧。 作为一本数学趣题集, Mathematical Puzzles 一书中竟把仍未解决的数学趣题单独列为一章,可见这些问题有多么令人着迷。我从这一章里挑选了一些问题,在这里和大家分享一下。这本书是
04 年出版的,
书里提到的一些“最新进展”其实已经不是最新的了;不过我也没有仔细考察每个问题当前的进展,因此本文的信息并不保证是 100% 准确的,在此向读者们表示歉意。 3x + 1 问题
  从任意一个正整数开始,重复对其进行下面的操作:如果这个数是偶数,把它除以
2 ;如果这个数是奇数,则把它扩大到原来的 3 倍后再加 1 。序列是否最终总会变成 4, 2, 1, 4, 2, 1, … 的循环?这个问题可以说是一个
“坑”——乍看之下,问题非常简单,突破口很多,于是数学家们纷纷往里面跳;殊不知进去容易出去难,不少数学家到死都没把这个问题搞出来。已经中招的数学家不计其数,这可以从 3x
+ 1 问题的各种别名看出来: 3x + 1 问题又叫
Collatz 猜想、 Syracuse 问题、 Kakutani 问题、 Hasse 算法、 Ulam 问题等等。后来,由于命名争议太大,干脆让谁都不沾光,直接叫做 3x + 1 问题算了。 3x + 1 问题不是一般的困难。这里举一个例子来说明数列收敛有多么没规律。从 26 开始算起, 10 步就掉入了“421 陷阱”:26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, … 但是,从 27 开始算起,数字会一路飙升到几千多,你很可能会一度认为它脱离了“421 陷阱”;但是,经过上百步运算后,它还是跌了回来: 27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186,
593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822,
911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40,
20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, …

额外,再说一点:如上所述,既然数列的收敛如此没有规律,那么上面我们的程序可能是错误的(包括优化过后的那个)。举一个例子,我们用 long long int 做一个测试:

#include<math.h>
int main(void)
{
    long long int n;
    int count=0; // 统计计算次数
    
    scanf("%I64d", &n); // long long int在windows下一定要用%I64d读入数据,否则会出问题(比如数据截断什么的)
    long long max = 0;  // 记录计算过程中出现的最大值
    
    while(n > 1)
    {
        if(n%2 != 0)
        {
            //n = floor(1.5*n + 1);
              n = (n * 3 + 1)/2;
        }
        else
        {
            n = n / 2;
        }
        count++;
        printf("%I64d\n", n); // 输出也使用%I64d
        
        if(n > max)
            max = n;
    }

    printf("%d\n%I64d\n", count, max);

    return 0;
}

我们输入704511,最后那个printf函数的输出结果:242,56991483520。可见其中间值有5百亿这么大,而用 n = floor(1.5*n + 1) 语句代替 n = n * 3 + 1;输出的中间值为28495741760,也有2百亿那么大,int类型存不下这么大的数据,所以之前的两个程序在测试强度不够的情况下表面上看是正确,实则是错误的。
最后,写的这里,应该可以告一段落了。

转自 :http://www.cnblogs.com/xpjiang/p/4129340.html

posted @ 2018-01-20 13:29  叶碎夜  阅读(4763)  评论(0编辑  收藏  举报