shingyuan 发表于 2011-11-30 00:04:34

一道和角谷静夫猜想相关的问题

最近在看EulerProject上的问题:

有一道问题是这样的:

角谷静夫猜想是说:对于一个任意的自然数,总可以通过以下步骤变为1:

f(n) = \frac{n}{2}.如果 n 是偶数
f(n) = 3 \times n + 1. 如果n 是奇数

对于不同的数,需要的步骤肯定不同,问题是1000000以内的数,哪个数需要的步骤最多?

我的想法比较粗糙:就是对每个数去统计.然后选取一个最长的。

对于每个数,到底需要多少次才可以到达1应该是未知的。假设统计这么多所需要的平均次数是T,那么就做了 1000000*T次循环,然后还有(2*T+1)*1000000次判断,我的代码如下;int main()
{
    //open file to output
    ofstream outfile("G:/math/ans.txt");
    long ans = 0;
    long index;
    long a,b;
    for(long i = 2; i < 1000000; )
    {
             a = i;
             index = 0;
             while(true)
             {
                     if(a == 1)
                     {
                        break;
                     }
                     if(a % 2 == 0)
                     {
                        a = a/2;
                     }
                     else
                     {
                         a = a*3 + 1;
                     }
                     index++;
             }
             if(index > ans)
             {
               ans = index;
               b = i;
             }
            i+=2;         
    }
    if(ans < 500000)
{
       ans = ans * 2;
      b++;
}
    outfile << "这个数是:" << ans << endl;
    outfile << "需要的次数:" << b << endl;
}不过貌似我的电脑根本跑不出来这个结果。是不是我的计算方法太愚蠢了。(我考虑偶数可以不用判断了。如果判断出来最大的数是<500000的奇数,则考虑这个数*2的那个偶数,就是需要步骤最多的。)我跑了很久结果也出不来

liangbch 发表于 2011-11-30 07:36:57

我发现楼主的代码有一个bug。楼主显然没有考虑到整数溢出问题,在执行下面的语句时,可能会发生整数溢出而导致结果错误。例如,当n=159487,在计算到第60步时,a*3+1的值就会超过32bit,这时,如果变量类型为unsigned long, 在32bit编译器下编译,就会出现错误。我注意到楼主的代码中,数据类型为long,那么溢出的几率会更高

a = a*3 + 1;

liangbch 发表于 2011-11-30 07:45:57

  我没有仔细调试楼主的程序。我自己写了一个程序,在PM1.7上运行,计算
100万/1000万/1亿之内的数需要的步时, 需要375/6250/93265毫秒。

 几点说明:
1.在计算过程中,我的程序使用unsigned __int64(VC编译器) 型变量,避免溢出。
2.我的程序作了一点儿优化,事先计算出1-65536之间的数所需的步骤,在计算大于65536的数所需的步骤时,一旦发现计算过程中,a的值<=65536,那么剩余的步骤可直接查表。

liangbch 发表于 2011-11-30 07:48:39

本楼给出所有源代码#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

typedef unsigned __int64 UINT64;
typedef unsigned long DWORD;
typedef unsigned short WORD;

WORD g_steps_tab;

int steps(DWORD n)
{
        UINT64 x=n;
        int r=0;
        while ( x >1)
        {
                if ( x & 1)//x 是奇数
                {
                        if ( x>=6148914691236517205I64)
                        {
                                printf("ERROR,n=%u,integer overflow, can not continue\n",n);
                                return 0;
                        }
                        x=x*2+x+1;
                        r++;
                }
                else
                {
                        x=x /2;
                        r++;
                }
        }
        return r;
}


//在计算过程中,当n的值小于65536,直接查表
//tab 表示 i+1 转化为1需要的步数
int steps_pro(DWORD n,WORD tab[])
{
        UINT64 x=n;
        int r=0;
       
        while ( x >1)
        {
                if ( x <=65536)
                {
                        return r+tab;
                }
                else if ( x & 1)//x 是奇数
                {
                        if ( x>=6148914691236517205I64)
                        {
                                printf("ERROR,n=%u,integer overflow, can not continue\n",n);
                                return 0;
                        }
                        x=x*2+x+1;
                        r++;
                }
                else
                {
                        x=x /2;
                        r++;
                }
        }
        return r;
}

void test2(DWORD to)
{
        DWORD i,max_n;
        int r,max_r;
        max_r=0;

        DWORD t;
        t=GetTickCount();
        for (i=1;i<=65536;i++)
        {
                r=steps(i);
                g_steps_tab=r;
        }
       
        max_r=0;
        for (i=1;i<=to;i++)
        {
                r=steps_pro(i,g_steps_tab);
                if (r>max_r)
                {
                        max_r=r;
                        max_n=i;
                }
        }
        t=GetTickCount()-t;
        printf("Calc up to %u, take %d ms\n",to,t);
        printf("steps[%d]=%d\n",max_n,max_r);
}


int main(int argc, char argv[])
{
        test2(1000000);
        test2(10000000);
        test2(100000000);
        return 0;
}

shingyuan 发表于 2011-12-9 00:11:28

非常感谢~~,看了收获很大。
有个小问题:
把 x = x*3 + 1;
写成:
x=x*2+x+1;
又没有什么特别的原因?
我运行时间好像差不太多。
我是AMD 2800+ , 512MB内存
计算1000000以内花费406MS-500MS。
判断奇数的方法+查表比我的节约非常多时间。

liangbch 发表于 2011-12-9 10:50:43

是基于优化的考虑。
位运算一定比除法快。因此,
   1. 判断是否是奇数写成"(n & 1)==1" 比 " n % 2 ==1" 快
   2.“ x=x*2+x+1;” 对通常的编译器,编译成的指令为x=(x<<1)+x+1, 不使用乘法指令。而"x=x*3+1;"对先进的编译器(如intel编译器)会编译成x=(x<<1)+x+1,但对于老的编译器,可能依然使用乘法指令来实现。乘法指令要比移位指令慢得多。

shingyuan 发表于 2011-12-9 22:36:13

原来这样,好精彩:b:,非常感谢
页: [1]
查看完整版本: 一道和角谷静夫猜想相关的问题