C ++ Collat​​z猜想优化

时间:2016-06-30 04:51:52

标签: c++ optimization collatz

在ProjectEuler问题#14中,需要找到最长的Collat​​z链,最多可达100万。我找到了一个不太合适的方法,但是,感觉我只是愚蠢,因为我无法找到一种方法来提高代码的效率(代码应该只打印解决方案)在测试了1到100万之后,但是在10分钟之后还没有打印出来。我是以错误的方式解决这个问题,还是有办法优化我现有的代码?

#include <iostream>
using namespace std;

int main()
{
    int i;
    int x;
    int n;
    int superN;
    int superI;

    superN = 0;
    superI = 0;

    for (i = 1; i <= 1000000; i++) {
        x = i;
        n = 1;

        do {
            if (x % 2 == 0) {
                x = x / 2;
            }

            if (x % 2 == 1 && x != 1) {
                x = 3 * x + 1;
            }

            n++;

            if (n > superN) {
                superN = n;
                superI = i;
            }
        } while (x != 1);
    }

    cout << "The number " << superI << " ran for " << superN << " terms.";
    system("pause");
    return 0;
}

4 个答案:

答案 0 :(得分:1)

你有一些小问题:

  1. 我非常确定您正在溢出int数据类型。使用uint64_t可能会降低这种可能性。
  2. 您应该只在while循环之外更新superIsuperN。这不重要,但会伤害表现。
  3. 在每次迭代中,您只应修改x一次。您当前可能会修改它两次,这可能会导致您陷入无限循环。您对n的计算也将关闭。
  4. 使用memoization通过缓存旧结果来提高性能。
  5. 应用这个,你可以想出一些像这样的代码:

    #include <cstdint>
    #include <iostream>
    #include <map>
    using namespace std;
    
    int main()
    {
        uint64_t i;
        uint64_t x;
        uint64_t n;
        uint64_t superN;
        uint64_t superI;
    
        std::map<uint64_t, uint64_t> memory;
    
        superN = 0;
        superI = 0;
    
        for (i = 1; i <= 1000000; i++) {
            x = i;
            n = 1;
    
            do {
                if (memory.find(x) != memory.end()) {
                    n += memory[x];
                    break;
                }
    
                if (x % 2 == 0) {
                    x = x / 2;
                } else {
                    x = 3 * x + 1;
                }
    
                n++;
            } while (x != 1);
    
            if (n > superN) {
                superN = n;
                superI = i;
            }
    
            memory[i] = n;
        }
    
        cout << "The number " << superI << " ran for " << superN << " terms.\n";
        system("pause");
        return 0;
    }
    

    输出需要4秒钟:

    The number 837799 ran for 556 terms.
    

答案 1 :(得分:0)

我建议不要使用memoization,因为它运行速度较慢;在我的情况下(高达10,000,000),下面的代码更快,没有记忆。 主要变化是:

  1. 仅测试当前数字是否为偶数一次(不需要else-if)。
  2. 使用按位运算代替模运算(稍快)
  3. 除此之外,我不知道为什么你的代码太长了(我的代码是200毫秒以下)你可能编译成调试吗?

    bool isEven(uint64_t value)
    {
        return (!(value & 1));
    }
    
    uint64_t solveCollatz(uint64_t start)
    {
        uint64_t counter = 0;
        while (start != 1)
        {
            if(isEven(start))
            { 
                start /= 2;
            }
            else
            {
                start = (3 * start) + 1;
            }
            counter++;
        }
    
        return counter;
    }
    

答案 2 :(得分:0)

如果您可以使用编译器内在函数,尤其是计数和删除尾随零,您将认识到您不需要在主循环中进行分支,您将始终交替使用奇数和偶数。先前提出的记忆技术很少会围绕您正在进行的数学运算进行短路,因为我们正在处理冰雹数字 - 此外,大多数数字只有一个入口点,所以如果你看到它们一次,那么你永远不会再见到他们了。

在GCC中,它看起来像这样:

#include <cstdint>
#include <iostream>
#include <unordered_map>
#include <map>
using namespace std;

using n_iPair = std::pair<uint32_t, uint64_t>;

auto custComp = [](n_iPair a, n_iPair b){
  return a.first < b.first;
};

int main()
{
    uint64_t x;
    uint64_t n;
    n_iPair Super = {0,0};

    for (auto i = 1; i <= 1000000; i++){
        x = i;
        n = 0;

        if (x % 2 == 0) {
          n += __builtin_ctz(x); // account for all evens
          x >>= __builtin_ctz(x); // always returns an odd
        }

         do{ //when we enter we're always working on an odd number

          x = 3 * x + 1; // always increases an odd to an even
          n += __builtin_ctz(x)+1; // account for both odd and even transfer
          x >>= __builtin_ctz(x); // always returns odd

        }while (x != 1);

        Super = max(Super, {n,i}, custComp);

    }

    cout << "The number " << Super.second << " ran for " << Super.first << " terms.\n";
    return 0;
}

答案 3 :(得分:0)

如果性能至关重要,但内存不是关键,则可以使用缓存来提高速度。

#include <iostream>
#include <chrono>
#include <vector>
#include <sstream>

std::pair<uint32_t, uint32_t> longestCollatz(std::vector<uint64_t> &cache)
{
    uint64_t length = 0;
    uint64_t number = 0;

    for (uint64_t current = 2; current < cache.size(); current++)
    {
        uint64_t collatz = current;
        uint64_t steps = 0;
        while (collatz != 1 && collatz >= current)
        {
            if (collatz % 2)
            {
                // if a number is odd, then ((collatz * 3) + 1) would result in
                // even number, but even number can have even or odd result,  so
                // we can combine two steps for even number, and increment twice.
                collatz = ((collatz * 3) + 1) / 2;
                steps += 2;
            }
            else
            {
                collatz = collatz / 2;
                steps++;
            }
        }

        cache[current] = steps + cache[collatz];

        if (cache[current] > length)
        {
            length = cache[current];
            number = current;
        }
    }
    return std::make_pair(number, length);
}

int main()
{
    auto start = std::chrono::high_resolution_clock::now();;

    uint64_t input = 1000000;
    std::vector<uint64_t> cache(input + 1);
    auto longest = longestCollatz(cache);

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Longest Collatz (index : value) --> " << longest.first << " : " << longest.second;
    std::cout << "\nExecution time: " << duration << " milliseconds\n";

    return EXIT_SUCCESS;
}