找到给出素数列表的因子的最接近的数字

时间:2016-03-24 19:50:29

标签: c++ algorithm math

假设我有一个数字,我可以找到构成该数字的所有主要因素。例如,6000是2 ^ 4 * 3 * 5 ^ 3.

如果我的数字不能很好地分解(给定一个可接受的素数列表),我怎样才能找到下一个最接近的数字?例如,给定数字5917,与素数列表2,3,5,7相关的最接近数字是多少?在这个例子中,这是6000。

我有一些蛮力找到答案的东西,但必须有一个更优雅的解决方案。

const UInt32 num = 5917;
const CVector<UInt32> primes = { 2, 3, 5, 7 };
const size_t size = primes.size();

UInt32 x = num;
while (x < num * 2)
{
    const UInt32 y = x;
    for(size_t i = 0; i < size && x > 1; ++i)
    {
        while(x % primes[i] == 0)
        {
            x /= primes[i];
        }
    }

    if (x == 1)
    {
        cout << "Found " << y << endl;
        break;
    }
    else
    {
        x = y + 1;
    }
}

修改

我创建了一个使用蛮力方法和3个方法作为答案的测试,并得到了一些令人惊讶的结果。所有4个版本都能产生正确的答案(感谢您的贡献),然而,蛮力方法似乎是最快的,达到一个数量级。我尝试了几个不同的系统,编译器和架构,这些都产生了大致一致的结果。

可在此处找到测试代码:http://ideone.com/HAgDsF。请随时提出建议。

4 个答案:

答案 0 :(得分:4)

我建议采用以下解决方案。我假设素数从低到高排序。我还使用了方便的vectorint类型。

vector<int> primes = { 2, 3, 5, 7 };
int num = 5917;
// initialize bestCandidate as a power of some prime greater than num
int bestCandidate = 1;
while (bestCandidate < num) bestCandidate *= primes[0];
set<int> s;
s.insert(1);
while (s.size()) {
    int current = *s.begin();
    s.erase(s.begin());
    for (auto p : primes) { // generate new candidates
        int newCandidate = current * p;
        if (newCandidate < num) {
            // new lower candidates should be stored.
            if (s.find(newCandidate) == s.end())
                s.insert(newCandidate);
        }
        else {
            if (newCandidate < bestCandidate) bestCandidate = newCandidate;
            break; // further iterations will generate only larger numbers
        }
    }
}
cout << bestCandidate;

Demo

接下来,我想对提出的解决方案进行分析。让我用 np 作为一些素数; n 作为一个数字来找到最接近的结果; minP 作为列表中的最小素数。

  1. 我的解决方案会生成低于 n 的所有可能值。从旧的值生成新值。每个值仅用一次作为生成源。如果新值超过 n ,则将其视为有效候选者。如果列表将包含低于 n 的所有素数,那么算法可以表现良好。我不知道算法的时间复杂度公式,但它是低于 n 的有效候选者的数量乘以前一个因子的对数。日志来自set数据结构操作。如果 n 足够小以分配大小 n 的数组来标记已经生成了哪些值,我们可以摆脱Log因子,一个简单的列表可以保存生成源值而不是集合。

  2. 您的初始解决方案有 O(n(np + log minP (n)))。您检查每个号码是否有效,然后逐一从 n 2n 支付 np + log minP (n)每次检查。

  3. Recursive solution by @anatolyg在&#34;访问&#34;中有一个很大的缺陷一些有效的数字很多次,这是非常低效的。它可以通过引入一个标志来确定,该标志表明该号码已经被访问过#34;。例如,12 = 2*2*36 = 2*3将访问4 = 2*2。小缺陷是许多上下文切换并支持每个呼叫的状态。解决方案有一个全局变量,它会使全局命名空间变得混乱,这可以通过添加一个函数参数来解决。

  4. Solution by @dasblinkenlight缺乏效率,因为已经&#34;使用&#34;候选人被用于产生新候选人,产生已经出现在该集合中的数字。虽然我已经用set。

  5. 借用了这个想法

    基于@גלעד ברקן's answer我创建了一个c++解决方案,由于没有log因素,因此确实似乎渐渐提高了效率。但是我拒绝使用double对数并使用整数保留解决方案。这个想法很简单。我们有一个低于num的产品列表。每个产品都是从第一个primesUsed素数中生成的。然后,我们尝试使用下一个素数生成新产品。这种方法可以保证生成唯一产品:

    vector<int> primes = { 2, 3, 5, 7, 11, 17, 23 };
    int num = 100005917;
    int bestCandidate = INT_MAX;
    list<pair<int, int> > ls;
    ls.push_back(make_pair(1, 0));
    while (ls.size()) {
        long long currentProd = ls.front().first;
        int primesUsed = ls.front().second;
        ls.pop_front();
        int currentPrime = primes[primesUsed];
        while (currentProd < num) {
            if(primesUsed < primes.size() - 1)
                ls.push_back(make_pair(currentProd, primesUsed + 1));
            currentProd *= currentPrime;
        }
        bestCandidate = min((long long)bestCandidate, currentProd);
    }
    cout << bestCandidate;
    

    Demo

答案 1 :(得分:3)

您可以尝试生成所有可能的产品,直到您枚举target*minPrime下的所有产品,其中minPrime是您设置中的最小素数。

不是试图通过重复分解来得出答案。

从包含1的集合开始。每次迭代尝试将每个素数的当前集合中的每个数字相乘。如果找到max下的新数字,则将其添加到当前集合中。该过程重复进行,直到无法添加新数字。

在你的情况下,第一代将是

1 2 3 5 7

下一代将是

1 2 3 4 5 6 7 9 10 14 15 21 25 35 49 

之后你会看到

第3代

1 2 3 4 5 6 7 8 9 10 12 14 15 18 20 21 25 27 28 30 35 42 45 49 50 63 70 75 98 105 125 147 175 245 343

第4代

1 2 3 4 5 6 7 8 9 10 12 14 15 16 18 20 21 24 25 27 28 30 35 36 40 42 45 49 50 54 56 60 63 70 75 81 84 90 98 100 105 125 126 135 140 147 150 175 189 196 210 225 245 250 294 315 343 350 375 441 490 525 625 686 735 875 1029 1225 1715 2401 

等等。十二代之后,你的集合将不再增长,此时你可以找到目标之上的最小值。

Demo.

答案 2 :(得分:2)

我们的想法是,检查所有可接受质数的产品,并选择最佳产品。

要实现这一点,使用递归最简单,尽管可能效率最低。创建一个递归函数&#34;检查&#34;通过逐个添加所有可接受的素数来临时产品。要记住最佳结果,最简单的方法是使用全局变量。

int g_result;

void check(int num, int product, const vector<int>& primes)
{
    if (product >= num)
    {
        g_result = std::min(g_result, product);
    }
    else
    {
        for (int prime: primes)
            check(num, product * prime, primes);
    }
}

...
int main()
{
    g_result = INT_MAX;
    vector<int> primes = { 2, 3, 5, 7 };
    check(5917, 1, primes);
    std::cout << g_result;
}

使用全局变量是一个丑陋的黑客;它在这个简单的例子中已经足够好了,但对于复杂的(多线程)系统并不好。要消除全局变量,请将函数填充到类中并使其成为方法;并使用成员变量result而不是全局变量。

注意:为方便起见,我使用vector<int>代替CVector<UInt32>

答案 3 :(得分:1)

取对数,我们可以将其视为子集求和问题的变体。这是一个JavaScript示例,它枚举了仅传递目标标记的不同组合。

function f(target,primes){
  target = Math.log(target);
  primes = primes.map(function(x){ return Math.log(x); });

  var best = primes[0] * Math.ceil(target / primes[0]);
  var stack = [[0,0]];

  while (stack[0] !== undefined){
    var params = stack.pop();
    var t = params[0];
    var i = params[1];

    if (t > target){
      if (t < best){
        best = t;
      }
    } else if (i == primes.length - 1){
      var m = Math.ceil((target - t) / primes[i]);
      stack.push([t + m * primes[i],i + 1]);
    } else {
      t -= primes[i];
      while (t < target){
        t += primes[i];
        stack.push([t,i + 1]);
      }
    }
  }

  return Math.round(Math.pow(Math.E,best));
}

console.log(f(5917,[2,3,5,7]));