C++ C++;Collatz猜想优化

C++ C++;Collatz猜想优化,c++,optimization,collatz,C++,Optimization,Collatz,在ProjectEuler问题#14中,需要找到最长的Collatz链,高达100万条。我找到了一个半途而废的方法,但是,我觉得我只是在做傻事,因为我找不到一种方法来提高代码的效率(代码应该只打印出解决方案,在它测试了100万到100万次之后,但在10分钟后没有打印出任何内容)。我是以错误的方式解决这个问题,还是有办法优化我现有的代码 #include <iostream> using namespace std; int main() { int i; int x

在ProjectEuler问题#14中,需要找到最长的Collatz链,高达100万条。我找到了一个半途而废的方法,但是,我觉得我只是在做傻事,因为我找不到一种方法来提高代码的效率(代码应该只打印出解决方案,在它测试了100万到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;
}
#包括
使用名称空间std;
int main()
{
int i;
int x;
int n;
国际超人;
国际高等教育;
superN=0;
supi=0;
对于(i=1;i超){
superN=n;
supri=i;
}
}而(x!=1);
}

您可能遇到了一些小问题:

  • 我很确定您的
    int
    数据类型溢出了。使用
    uint64\u t
    可以大大降低这种可能性
  • 您应该只在while循环之外更新
    super
    superN
    。这不重要,但会影响性能
  • 在每次迭代中,您只应修改
    x
    一次。您当前可能会修改它两次,这可能会导致您陷入无限循环。您对
    n
    的计算也将关闭
  • 使用memonization通过缓存旧结果来提高性能
  • 应用此方法,您可以得到如下代码:

    #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;
    }
    
    #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;
    }
    

    您有一些小问题:

  • 我很确定您的
    int
    数据类型溢出了。使用
    uint64\u t
    可以大大降低这种可能性
  • 您应该只在while循环之外更新
    super
    superN
    。这不重要,但会影响性能
  • 在每次迭代中,您只应修改
    x
    一次。您当前可能会修改它两次,这可能会导致您陷入无限循环。您对
    n
    的计算也将关闭
  • 使用memonization通过缓存旧结果来提高性能
  • 应用此方法,您可以得到如下代码:

    #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;
    }
    
    #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;
    }
    

    我建议不要使用回忆录,因为对我来说它运行得比较慢;在我的情况下(高达10000000),下面的代码在没有记忆的情况下速度更快。 主要变化是:

  • 仅测试当前数字是否为偶数一次(不需要else if)
  • 使用位运算而不是模运算(稍快)
  • 除此之外,我不知道为什么你的代码这么长(我的代码不到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;
    }
    

    我建议不要使用回忆录,因为对我来说它运行得比较慢;在我的情况下(高达10000000),下面的代码在没有记忆的情况下速度更快。 主要变化是:

  • 仅测试当前数字是否为偶数一次(不需要else if)
  • 使用位运算而不是模运算(稍快)
  • 除此之外,我不知道为什么你的代码这么长(我的代码不到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;
    }
    

    如果您可以使用编译器内部函数,特别是在计算和删除尾随零时,您将认识到不需要在主循环中进行分支,您将始终交替使用奇数和偶数。之前介绍的记忆技术很少会在你所做的数学运算中出现短路,因为我们处理的是冰雹数字——此外,大多数数字只有一个入口点,因此如果你看到它们一次,你将永远不会再看到它们

    在GCC中,它将如下所示:

    #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;
    }
    
    #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;
    }
    
    #包括
    #包括
    #包括
    #包括
    使用名称空间std;
    使用n_iPair=std::pair;
    auto custComp=[](n_iPair a,n_iPair b){
    返回a.first=\uu builtin\u ctz(x);//始终返回奇数
    }
    当我们进入时,我们总是在处理一个奇数
    x=3*x+1;//总是将奇数增加到偶数
    n+=\uuuu内置ctz(x)+1;//奇偶传输的帐户
    x>>=\uu内置的ctz(x);//始终返回奇数
    }而(x!=1);
    Super=max(Super,{n,i},custComp);
    }
    
    cout如果你能使用编译器内部函数,特别是在计算和删除尾随零时,你会发现你不需要在主循环中分支,你总是交替奇数和偶数。之前介绍的记忆技术很少会在你所做的数学上短路,因为我们正在处理hailstone数字-此外,大多数数字只有一个入口点,因此如果你看到它们一次,你将永远不会再看到它们

    在GCC中,它将如下所示:

    #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;
    }
    
    #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;
    }
    
    #包括
    #包括
    #包括
    #包括
    使用名称空间std;
    使用n_iPair=std::pair;
    auto custComp=[](n_iPair a,n_iPair b){
    返回a.first=\uu builtin\u ctz(x);//始终返回奇数
    }
    当我们进入时,我们总是在处理一个奇数
    x=3*x+1;//总是将奇数增加到偶数
    n+=\uuuu内置ctz(x)+1;//奇偶传输的帐户
    x>>=\uu内置的ctz(x);//始终返回奇数
    }而(x!=1);
    Super=max(Super,{n,i},custComp);
    }
    
    cout如果性能至关重要,但内存不是,则可以使用缓存来提高速度

    #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;
    }
    
    #包括
    #包括
    #包括
    #包括
    std::pair longestclatz(std::vector&cache)
    {
    uint64_t长度=0;
    uint64_t编号=0;
    对于(uint64_t current=2;current=当前)
    {
    如果(collatz%2)
    {
    //如果我有一个号码