算法 之 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM