• 算法 之 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 ", 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
    ", 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
    ", 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); // 输出也使用%I64d
            
            if(n > max)
                max = n;
        }
    
        printf("%d
    %I64d
    ", 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

  • 相关阅读:
    如何用vue组件做个机器人?有趣味的代码
    vue--监听属性完成大小写字母间的转换
    C语言基础:进制转换,变量,常量,表达式,基本数据类型,输出函数,输入函数,运算符. 分类: iOS学习 c语言基础 2015-06-10 21:39 25人阅读 评论(0) 收藏
    2019年最新50道java基础部分面试题
    28道java基础面试题-下
    最新28道java基础面试题-上
    后台管理UI模板
    Bellman-Ford 算法及其优化
    【枚举Day1】20170529-2枚举算法专题练习 题解
    【枚举Day1】20170529-2枚举算法专题练习 题目
  • 原文地址:https://www.cnblogs.com/lzhh/p/acm_n1.html
Copyright © 2020-2023  润新知