繁体   English   中英

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

[英]Finding the closest number that factors given a list of primes

假设我有一个数字,我可以找到构成该数字的所有主要因素。 例如,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 请随时提出建议。

我建议以下解决方案。 我假设素数从低到高排序。 我还使用了方便的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;

演示

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

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

  2. 您的初始解决方案有O(n(np + log minP (n))) 您检查每个数字是否有效然后逐个从n2n为每个支票支付np + log minP (n)

  3. @anatolyg的递归解决方案在多次 “访问”一些有效数字方面存在很大的缺陷,这是非常低效的。 可以通过引入表示该号码已被“访问”的标志来修复。 例如,从6 = 2*34 = 2*2将访问12 = 2*2*3 小缺陷是许多上下文切换并支持每个呼叫的状态。 解决方案有一个全局变量,它使全局命名空间变得混乱,这可以通过添加一个函数参数来解决。

  4. @dasblinkenlight的解决方案缺乏效率,因为已经“使用”候选者被用于生成已经存在于集合中的数字的新候选者。 虽然我已经用套装借用了这个想法。

基于@גלעדברקן的回答,我创建了一个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;

演示

您可以尝试生成所有可能的产品,直到您枚举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 

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

演示。

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

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

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而不是全局result

注意:为方便起见,我使用了vector<int>而不是CVector<UInt32>

以对数为例,我们可以将其视为子集和问题的变体。 这是一个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]));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM