【问题标题】:Stuck in an infinite loop? (Maybe)陷入无限循环? (也许)
【发布时间】:2014-10-24 21:39:24
【问题描述】:

我正在尝试在 c++ 中完成 Project Euler Problem 14,但我真的被卡住了。现在,当我运行问题时,它卡在 So Far:计数最高的数字:113370,计数为 155 到目前为止:计数最高的数字,但是当我尝试将 i 值更改为超过 113371 时,它可以工作。怎么回事??

问题是:

下面的迭代序列是为正数的集合定义的 整数:n → n/2(n 为偶数)n → 3n + 1(n 为奇数)

使用上面的规则并从 13 开始,我们生成以下内容 顺序:

13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 可以看出,这 序列(从 13 开始,在 1 结束)包含 10 个术语。 虽然它还没有被证明(Collat​​z Problem),但它是 以为所有的起始数字都以 1 结束。哪个起始数字, 一百万以下,生产最长的链条?

#include<stdio.h>
int main() {
    int limit = 1000000;
    int highNum, number, i;
    int highCount = 0;
    int count = 0;
    for( number = 13; number <= 1000000; number++ )
    {
        i = number;
        while( i != 1 ) {
            if (( i % 2 ) != 0 ) {
                i = ( i * 3 ) + 1;
                count++;
            }
            else {
                count++;
                i /= 2;
            }
        }
        count++;
        printf( "So Far: the number with the highest count: %d with the count of %d\n",
                     number, count );
        if( highCount < count ) {
            highCount = count;
            highNum = number;
        }
        count = 0;
        //break;
    }
    printf( "The number with the highest count: %d with the count of %d\n",
            highNum, highCount );
}

【问题讨论】:

  • 如果程序对于相对较小的数字可以正常工作,但对于较大的数字“陷入无限循环”,则很可能不是无限循环,您的算法很慢。
  • 您的问题是您一遍又一遍地重新计算相同的部分结果。
  • 检查你使用的数据类型的限制,使用 unsigned long int
  • @PictureMeAndYou:您的限制是 1000000,但是当您计算每个序列的长度时,一些 中间 值将超出 32 位 int 的范围。
  • 实际上,按照同样的思路,在显而易见的策略中,您可以立即丢弃 1-500000 之间的每个数字作为最长链,因为总是会有一个链一的值加倍更长。您还可以丢弃(N-1)%3==0 所在的所有号码。这将可能性的数量减少了 66%。

标签: c++ collatz


【解决方案1】:

你得到整数溢出。像这样更新您的代码并自己查看:

if (( i % 2 ) != 0 ) {
    int prevI = i;
    i = ( i * 3 ) + 1;
    if (i < prevI) {
        printf("oops, i < prevI: %d\n", i);
        return 0;
    }
    count++;
}

您应该将i 的类型更改为long longunsigned long long 以防止溢出。

(是的,缓存中间结果)

【讨论】:

    【解决方案2】:

    记住所有中间结果(直到某个适当高的数字)。
    另外,使用足够大的类型:

    #include <stdio.h>
    
    static int collatz[4000000];
    unsigned long long collatzmax;
    
    int comp(unsigned long long i) {
      if(i>=sizeof collatz/sizeof*collatz) {
          if(i>collatzmax)
            collatzmax = i;
          return 1 + comp(i&1 ? 3*i+1 : i/2);
      }
      if(!collatz[i])
          collatz[i] = 1 + comp(i&1 ? 3*i+1 : i/2);
      return collatz[i];
    }
    
    int main() {
      collatz[1] = 1;
      int highNumber= 1, highCount = 1, c;
      for(int i = 2; i < 1000000; i++)
        if((c = comp(i)) > highCount) {
          highCount = c;
          highNumber = i;
        }
      printf( "The number with the highest count: %d with the count of %d\n",
            highNumber, highCount );
      printf( "Highest intermediary number: %llu\n", collatzmax);
    }
    

    关于大肠杆菌:http://coliru.stacked-crooked.com/a/773bd8c5f4e7d5a9

    运行时间更短的变体:http://coliru.stacked-crooked.com/a/2132cb74e4605d5f

    The number with the highest count: 837799 with the count of 525
    Highest intermediary number: 56991483520
    

    BTW:遇到的最高中介需要 36 位来表示为无符号数。

    【讨论】:

    • @Jarod42:正确的幻数有点太高了。添加代码来计算它。
    • +1 我认为,您只需要对奇数进行迭代。
    • @PetrBudnik:我不相信这是一个有效的优化(尽管我确定了几个有效的类似推理)
    • @PetrBudnik:您只需要迭代每三个数字 500000-1000000 中的两个:其中 N%3==1 为假。最后,我敢打赌,大部分内容都会被记住。
    【解决方案3】:

    使用您的算法,您可以计算多个时间相同的序列。 您可以缓存以前数字的结果并重复使用它们。

    类似:

    void compute(std::map<std::uint64_t, int>& counts, std::uint64_t i)
    {
        std::vector<std::uint64_t> series;
        while (counts[i] == 0) {
            series.push_back(i);
            if ((i % 2) != 0) {
                i = (i * 3) + 1;
            } else {
                i /= 2;
            }
        }
        int count = counts[i];
        for (auto it = series.rbegin(); it != series.rend(); ++it)
        {
            counts[*it] = ++count;
        }
    }
    
    int main()
    {
        const std::uint64_t limit = 1000000;
    
        std::map<std::uint64_t, int> counts;
        counts[1] = 1;
        for (std::size_t i = 2; i != limit; ++i) {
            compute(counts, i);
        }
        auto it = std::max_element(counts.begin(), counts.end(),
            [](const std::pair<std::uint64_t, int>& lhs, const std::pair<std::uint64_t, int>& rhs)
            {
                return lhs.second < rhs.second;
            });
        std::cout << it->first << ":" << it->second << std::endl;
        std::cout << limit-1 << ":" << counts[limit-1] << std::endl;
    }
    

    Demo(10 秒)

    【讨论】:

    • 奇怪的是它停在同一个地方
    • @PictureMeAndYou:使用提供的代码,我可以在 10 秒内得到结果。
    • @Jarod42:可以是done in 0.034 seconds
    • @Blastfurnace:为什么是std::unique_ptr&lt;int[]&gt; 而不是std::vector&lt;int&gt;
    • @MooingDuck:回想起来,我想我想避免初始化容器的值,而且它的输入比reserve/push_back少。
    【解决方案4】:

    不要一遍又一遍地重新计算相同的中间结果!

    给定

    typedef std::uint64_t num;  // largest reliable built-in unsigned integer type
    
    num collatz(num x)
    {
        return (x & 1) ? (3*x + 1) : (x/2);
    }
    

    那么collatz(x)的值只取决于x,而不取决于你调用它的时间。 (换句话说,collatzpure function。)因此,您可以将 memoize 的值 collatz(x) 用于不同的 x 值。为此,您可以使用std::map&lt;num, num&gt;std::unordered_map&lt;num, num&gt;

    作为参考,here 是完整的解决方案。

    这里是Coliru,计时(2.6 秒)。

    【讨论】:

    • 是的,我知道有更快的方法可以做到这一点,但我想尝试我的方法,因为它对我来说是原创的,并且理解为什么它不起作用。
    • @PictureMeAndYou:你的方法行不通精确地,因为你的算法太慢了。
    • AFAIK,我以为编译和运行只需要一段时间我不知道它会完全停止?
    • @PictureMeAndYou:如果您编写的程序需要一千年才能完成运行,出于所有实际意图和目的,您的程序将永远不会停止。
    • @EduardoLeón:一旦我将所有内容更改为 long long,他的代码在我的机器上只需要大约 3.7 秒。
    猜你喜欢
    • 2020-05-13
    • 1970-01-01
    • 2013-03-17
    • 2021-02-15
    • 2022-01-23
    • 2021-01-23
    相关资源
    最近更新 更多