c++ Collatz猜想优化
C++ Collatz Conjecture Optimization
在ProjectEuler问题#14中,需要找到最长的Collatz链,最多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;
}
你有一些小问题:
- 我很确定您正在溢出
int
数据类型。使用uint64_t
使这种情况更不可能发生。 - 你应该只在while循环之外更新
superI
和superN
。这应该无关紧要,但它会影响性能。 - 在每次迭代中,您应该只修改
x
一次。当前可能会修改它两次,这可能会导致您陷入无限循环。你的n
的计算也会关闭。 - 使用记忆来缓存旧的结果来提高性能。
#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.
我建议不要使用记忆,因为对我来说它运行速度较慢;在我的例子中(最多10,000,000),下面的代码在没有记忆的情况下更快。主要变化如下:
- 只测试当前数字是否为偶数(不需要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 <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;
}
如果性能很关键,但内存不是,您可以使用缓存来提高速度。
#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 << " millisecondsn";
return EXIT_SUCCESS;
}
相关文章:
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 如何防止 CUDA-GDB 中的<优化输出>值
- 为什么我的程序在 O0 和 O2 的优化级别返回不同的结果
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- c++ Collatz猜想优化