处理大量数字

时间:2011-03-23 19:29:00

标签: math numbers

项目欧拉问题14:

  

以下迭代序列是   为积极的一套定义   整数:

     

n→n / 2(n为偶数)n→3n + 1(n为   奇)

     

使用上面的规则并从中开始   13,我们生成以下内容   顺序:13→40→20→10→5→16→   8→4→2→1

     

可以看出这个序列   (从13开始到1完成)   包含10个术语。虽然没有   它已被证实(Collat​​z问题)   被认为是所有的起始数字   结束于1。

     

哪个起始编号,一个   百万,产生最长的链?

我的第一直觉是创建一个计算链的函数,并使用1到100万之间的每个数运行它。显然,这需要时间。根据Project Euler的“关于”页面,比解决这个应该采取的方式更长。我在Project Euler上发现了一些涉及大量数字的问题,这些问题导致一个运行数小时的程序没有完成。显然,我做错了什么。

如何快速处理大量数字?

我在这里缺少什么?

6 个答案:

答案 0 :(得分:4)

阅读memoization。关键的见解是,如果你有一个序列从A开始,其长度为1001,然后你得到一个产生A的序列B,你就不要再重复所有这些了。<​​/ p>

答案 1 :(得分:3)

这是Mathematica中的代码,使用memoization和recursion。只有四行:)

f[x_] := f[x] = If[x == 1, 1, 1 + f[If[EvenQ[x], x/2, (3 x + 1)]]]; 
Block[{$RecursionLimit = 1000, a = 0, j},
 Do[If[a < f[i], a = f[i]; j = i], {i, Reverse@Range@10^6}];
 Print@a; Print[j];
]

输出....链长'525',数字是......哦......字体太小了! :)

顺便说一句,在这里你可以看到每个链长的频率图

enter image description here

答案 2 :(得分:2)

从1,000,000开始,生成链。跟踪链中生成的每个数字,因为您确定它们的链小于起始数字的链。达到1后,将起始编号及其链长存储起来。获取之前未生成的下一个最大数字,然后重复此过程。

这将为您提供数字和链长的列表。采取最大的链长,这是你的答案。

我会做一些代码来澄清。

 public static long nextInChain(long n) {
    if (n==1) return 1;

    if (n%2==0) {
        return n/2;
    } else {
        return (3 * n) + 1;
    }
}


public static void main(String[] args) {
    long iniTime=System.currentTimeMillis();
    HashSet<Long> numbers=new HashSet<Long>();
    HashMap<Long,Long> lenghts=new HashMap<Long, Long>();

    long currentTry=1000000l;
    int i=0;
    do {
        doTry(currentTry,numbers, lenghts);
        currentTry=findNext(currentTry,numbers);
        i++;
    } while (currentTry!=0);
    Set<Long> longs = lenghts.keySet();
    long max=0;
    long key=0;
    for (Long aLong : longs) {
        if (max < lenghts.get(aLong)) {
            key = aLong;
            max = lenghts.get(aLong);
        }
    }
    System.out.println("number = " + key);
    System.out.println("chain lenght = " + max);
    System.out.println("Elapsed = " + ((System.currentTimeMillis()-iniTime)/1000));
}


private static long findNext(long currentTry, HashSet<Long> numbers) {
    for(currentTry=currentTry-1;currentTry>=0;currentTry--) {
        if (!numbers.contains(currentTry)) return currentTry;
    }
    return 0;
}

private static void doTry(Long tryNumber,HashSet<Long> numbers, HashMap<Long, Long> lenghts) {
    long i=1;
    long n=tryNumber;
    do {
        numbers.add(n);
        n=nextInChain(n);
        i++;
    } while (n!=1);
    lenghts.put(tryNumber,i);
}

答案 3 :(得分:2)

假设您有一个函数CalcDistance(i)来计算1的“距离”。例如,CalcDistance(1) == 0CalcDistance(13) == 9。这是一个天真的递归实现这个函数(在C#中):

public static int CalcDistance(long i)
{
    if (i == 1)
        return 0;

    return (i % 2 == 0) ? CalcDistance(i / 2) + 1 : CalcDistance(3 * i + 1) + 1;
}

问题是这个函数必须一遍又一遍地计算多个数字的距离。通过给它一个记忆,你可以让它变得更聪明(并且更快)。例如,让我们创建一个静态数组,可以存储第一百万个数字的距离:

static int[] list = new int[1000000];

我们使用-1预填充列表中的每个值,以指示尚未计算该位置的值。在此之后,我们可以优化CalcDistance()函数:

public static int CalcDistance(long i)
{
    if (i == 1)
        return 0;

    if (i >= 1000000)
        return (i % 2 == 0) ? CalcDistance(i / 2) + 1 : CalcDistance(3 * i + 1) + 1;

    if (list[i] == -1)
        list[i] = (i % 2 == 0) ? CalcDistance(i / 2) + 1: CalcDistance(3 * i + 1) + 1;

    return list[i];
}

如果i >= 1000000,那么我们就无法使用我们的列表,因此我们必须始终对其进行计算。如果i < 1000000,那么我们检查该值是否在列表中。如果没有,我们先计算它并将其存储在列表中。否则我们只返回列表中的值。使用此代码,处理所有数百万个数字大约需要120毫秒。

这是一个非常简单的memoization示例。我在这个例子中使用一个简单的列表来存储中间值。您可以在适当的时候使用更高级的数据结构,如哈希表,向量或图形。

答案 4 :(得分:0)

最小化循环的深度级别,并使用IList或IDictionary等高效数据结构,可以在需要扩展时自动调整自身大小。如果使用普通数组,则需要在扩展时将它们复制到更大的数组中 - 效率不高。

答案 5 :(得分:0)

此变体不使用HashMap,但仅尝试不重复前1000000个数字。我没有使用散列图,因为找到的最大数字大约是56亿,哈希映射可能会崩溃。

我已经完成了一些premature optimization。我使用/代替>>,而不是%而不是&。而不是*我使用了一些+

void Main()
{
    var elements = new bool[1000000];       

    int longestStart = -1;
    int longestRun = -1;

    long biggest = 0;

    for (int i = elements.Length - 1; i >= 1; i--) {
        if (elements[i]) {
            continue;
        }

        elements[i] = true;

        int currentStart = i;
        int currentRun = 1;

        long current = i;

        while (current != 1) {
            if (current > biggest) {
                biggest = current;
            }

            if ((current & 1) == 0) {
                current = current >> 1;
            } else {
                current = current + current + current + 1;
            }

            currentRun++;

            if (current < elements.Length) {
                elements[current] = true;
            }
        }

        if (currentRun > longestRun) {
            longestStart = i;
            longestRun = currentRun;
        }
    }   

    Console.WriteLine("Longest Start: {0}, Run {1}", longestStart, longestRun);
    Console.WriteLine("Biggest number: {0}", biggest);
}