查找Magic Numbers C++

Cha*_*haz 8 c++ magic-numbers

魔术数字

正整数是"魔法",当且仅当它可以通过将其重复除以2(如果它是偶数或乘以3然后如果它是奇数则加1)来减少到1.因此,例如,3是神奇的,因为3首先减少到10(3*3 + 1),然后减少到5(10/2),然后减少到16(5*3 + 1),然后减少到8(16/2) ,然后到4(8/2),然后到2(4/2),最后到1(2/2).幻数假设表明所有正整数都是魔术,或者形式上:∀x∈Z,MAGIC(x)其中MAGIC(x)是谓词"x是魔法".

我们应该开发一个C++程序来查找1到50亿的"Magic Numbers".如果正确完成,则需要80秒或更短时间.我的大约需要2个小时,我们给出的示例程序需要14天.这是我的代码,我该怎么做才能加快速度?我错过了明显的优化问题吗?

#include <iostream>
using namespace std;

bool checkMagic(unsigned long number);

int main()
{
  for(unsigned long i = 1; i <= 5000000000; i++)
  {
    if(i % 1000000 == 0)
    {
      //only print out every 1000000 numbers to keep track, but slow it down 
      //by printing out every single number
      cout<<i<<endl;
    }

    if(!checkMagic(i))
    {
      //not magic
      cout<<i<<" not a magic number"<<endl;
      break;
    }
  }
}

bool checkMagic(unsigned long number)
{
  if(number % 2 == 0)
  {
    number = number / 2;
  }
  else
  {
    number = (number * 3) + 1;
  }

  if(number !=  1)
  {
    checkMagic(number);
  }

  return 1;
}
Run Code Online (Sandbox Code Playgroud)

Ami*_*ory 4

这道题基本上要求验证Collat​​z 猜想达到 5B。

我认为这里的关键是,对于我们检查的每个数字n,查看乐观场景和悲观场景,并在恢复到悲观场景之前检查乐观场景。

在乐观的情况下,当我们根据n / 2 修改n时;3n + 1规则,数字序列将:

  • 在有限的步骤中变得小于n(在这种情况下,我们可以检查我们对这个较小数字的了解)。

  • 不会导致步骤溢出。

(正如 TonyK 正确指出的那样,我们不能依赖它(甚至不能依赖第一个))。

因此,对于乐观的情况,我们可以使用以下函数:

#include <unordered_set>
#include <set>
#include <iostream>
#include <memory>
#include <list>
#include <gmp.h>

using namespace std;

using found_set = unordered_set<size_t>;

bool fast_verify(size_t i, size_t max_tries, const found_set &found) {
    size_t tries = 0;
    size_t n = i;
    while(n != 1) {
        if(++tries == max_tries ) 
            return false;

        if(n < i)
            return found.empty() || found.find(i) == found.end();
        if(n % 2 == 0)
            n /= 2;
        else if(__builtin_mul_overflow (n, 3, &n) || __builtin_add_overflow(n, 1, &n))
            return false;
    }   

    return true;
}
Run Code Online (Sandbox Code Playgroud)

请注意以下事项:

  1. 该函数仅尝试验证其收到的数字的推测。如果返回true,则已验证。如果返回false,则仅意味着它尚未被验证(即,并不意味着它已被反驳)。

  2. 它需要一个参数max_tries,并且仅验证最多这个步骤数。如果超出了该数字,它不会尝试辨别这是否是无限循环的一部分 - 它只是返回验证失败。

  3. 它需要一个unordered_set失败的已知数字(当然,如果科拉茨猜想为真,这个集合将永远是空的)。

  4. 它通过 检测溢出__builtin_*_overflow。(不幸的是,这是特定于 gcc 的。不同的平台可能需要一组不同的此类函数。)

现在来说说缓慢但确定的函数。该函数使用GNU MP多精度算术库。它通过维护已经遇到的数字序列来检查无限循环。true如果该数的猜想已被证明,则该函数返回,false如果该数的猜想已被反驳(请注意与之前的快速验证的区别)。

bool slow_check(size_t i) {
    mpz_t n_; 
    mpz_init(n_);

    mpz_t rem_;
    mpz_init(rem_);

    mpz_t i_; 
    mpz_init(i_);

    mpz_set_ui(i_, i); 
    mpz_set_ui(n_, i); 

    list<mpz_t> seen;

    while(mpz_cmp_ui(n_, 1) != 0) {
        if(mpz_cmp_ui(n_, i) < 0)
            return true;
        mpz_mod_ui(rem_, n_, 2); 
        if(mpz_cmp_ui(rem_, 0) == 0) {
            mpz_div_ui(n_, n_, 2); 
        }   
        else {
            mpz_mul_ui(n_, n_, 3); 
            mpz_add_ui(n_, n_, 1); 
       }   
       seen.emplace_back(n_);
       for(const auto &e0: seen)
           for(const auto &e1: seen)
               if(&e0 != &e1 && mpz_cmp(e0, e1) == 0)
                   return false;
   }   

   return true;
}
Run Code Online (Sandbox Code Playgroud)

最后,main保留一个unordered_set被证实的数字。对于每个数字,它乐观地尝试验证猜想,然后,如果失败(溢出或超过迭代次数),则使用慢速方法:

int main()
{
    const auto max_num = 5000000000;
    found_set found;

    for(size_t i = 1; i <= max_num; i++) {
        if(i % 1000000000 == 0)
            cout << "iteration " << i << endl;

        auto const f = fast_verify(i, max_num, found);
        if(!f && !slow_check(i))
            found.insert(i);
    }

    for(auto e: found)
        cout << e << endl;
}
Run Code Online (Sandbox Code Playgroud)

运行完整代码(如下)给出:

$ g++ -O3 --std=c++11 magic2.cpp -lgmp && time ./a.out
iteration 1000000000
iteration 2000000000
iteration 3000000000
iteration 4000000000
iteration 5000000000

real    1m3.933s
user    1m3.924s
sys 0m0.000s

$ uname -a
Linux atavory 4.4.0-38-generic #57-Ubuntu SMP Tue Sep 6 15:42:33 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
$ sudo lshw | grep -i cpu
      *-cpu
           description: CPU
           product: Intel(R) Core(TM) i7-4720HQ CPU @ 2.60GHz
           bus info: cpu@0
           version: Intel(R) Core(TM) i7-4720HQ CPU @ 2.60GHz
           capabilities: x86-64 fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts cpufreq
Run Code Online (Sandbox Code Playgroud)

即,没有发现被证实的数字,并且运行时间约为 64 秒。


完整代码:

#include <unordered_set>
#include <set>
#include <iostream>
#include <memory>
#include <list>
#include <gmp.h>

using namespace std;

using found_set = unordered_set<size_t>;

bool fast_verify(size_t i, size_t max_tries, const found_set &found) {
    size_t tries = 0;
    size_t n = i;
    while(n != 1) {
        if(++tries == max_tries )
            return false;

        if(n < i)
            return found.empty() || found.find(i) == found.end();
        if(n % 2 == 0)
            n /= 2;
        else if(__builtin_mul_overflow (n, 3, &n) || __builtin_add_overflow(n, 1, &n))
            return false;
    }   

    return true;
}

bool slow_check(size_t i) {
    mpz_t n_; 
    mpz_init(n_);

    mpz_t rem_;
    mpz_init(rem_);

    mpz_t i_; 
    mpz_init(i_);

    mpz_set_ui(i_, i); 
    mpz_set_ui(n_, i); 

    list<mpz_t> seen;

    while(mpz_cmp_ui(n_, 1) != 0) {
        if(mpz_cmp_ui(n_, i) < 0)
            return true;
        mpz_mod_ui(rem_, n_, 2); 
        if(mpz_cmp_ui(rem_, 0) == 0) {
            mpz_div_ui(n_, n_, 2); 
        }   
        else {
            mpz_mul_ui(n_, n_, 3); 
            mpz_add_ui(n_, n_, 1); 
       }   
       seen.emplace_back(n_);
       for(const auto &e0: seen)
           for(const auto &e1: seen)
               if(&e0 != &e1 && mpz_cmp(e0, e1) == 0)
                   return false;
   }   

   return true;
}


int main()
{
    const auto max_num = 5000000000;
    found_set found;

    for(size_t i = 1; i <= max_num; i++) {
        if(i % 1000000000 == 0)
            cout << "iteration " << i << endl;

        auto const f = fast_verify(i, max_num, found);
        if(!f && !slow_check(i))
            found.insert(i);
    }

    for(auto e: found)
        cout << e << endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 有两个“num”有点令人困惑。 (2认同)
  • 事实上,这是行不通的。考虑一下如果有一个非幻数低于“max_num”会发生什么:您将如何检测到这一点?你不会,因为 (i) `num` 会溢出,可能会溢出到一个幻数;(ii) 程序将进入无限循环。我想你可以说你在情况 (ii) 中检测到了它,但在情况 (i) 中肯定没有检测到。 (2认同)
  • 由于 while 循环没有副作用,编译器可能会完全忽略该循环。 (2认同)