在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;
}
答案 0 :(得分:1)
你有一些小问题:
int
数据类型。使用uint64_t
可能会降低这种可能性。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.
答案 1 :(得分:0)
我建议不要使用memoization,因为它运行速度较慢;在我的情况下(高达10,000,000),下面的代码更快,没有记忆。 主要变化是:
除此之外,我不知道为什么你的代码太长了(我的代码是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;
}