魔术数字

当且仅当可以将正整数减小为1时,如果将其偶数除以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)。魔魔数(Magic Number)字假设指出,所有正整数都是魔幻,或者形式上是:∀x∈Z,MAGIC(x)其中MAGIC(x)是谓词“x is magic”。

我们应该开发一个C++程序来查找1到50亿个“魔术数字”。如果正确完成,则应该花费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;
}

最佳答案

这个问题基本上要求验证Collatz Conjecture到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;
    }
    

    请注意以下几点:
  • 该函数仅尝试验证其收到的数字的猜想。如果返回true,则它已被验证。如果返回false,则仅表示尚未经过验证(即,并不表示已被拒绝)。
  • 它需要一个参数max_tries,并且最多只能验证此步骤数。如果超过了该数目,则它不会尝试辨别这是否是无限循环的一部分,它只会返回验证失败的信息。
  • 它接受失败的已知数字的unordered_set(当然,如果Collat​​z猜想为true,则此集合将始终为空)。
  • 它通过 __builtin_*_overflow 检测溢出。 (不幸的是,这是特定于gcc的。不同的平台可能需要一组不同的此类功能。)

  • 现在使用缓慢但确定功能。此函数使用GNU MP multi-precision arithmetic library。它通过维护已经遇到的数字序列来检查无限循环。如果已经证明了该数字的猜想,则此函数返回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;
    }
    

    最后,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;
    }
    

    运行完整的代码(如下)将给出:
    $ 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
    

    也就是说,没有发现无法证实的数字,并且运行时间约为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;
    }
    

    关于c++ - 查找魔术数字C++,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39418601/

    10-17 01:36