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