简体   繁体   English

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

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

Say I have a number, I can find all the prime factors that make up that number. 假设我有一个数字,我可以找到构成该数字的所有主要因素。 For instance, 6000 is 2^4 * 3 * 5^3. 例如,6000是2 ^ 4 * 3 * 5 ^ 3。

If I have a number that doesn't factorize well (given a list of acceptable primes), how can I find the next closest number? 如果我有一个不能很好地分解的数字(给出一个可接受的素数列表),我怎样才能找到下一个最接近的数字? For instance, given the number 5917, what is the closest number that factors with the list of primes 2, 3, 5, 7? 例如,给定数字5917,与素数列表2,3,5,7相关的最接近数字是多少? Which is 6000 in this example. 在这个例子中,这是6000。

I have something that will brute force find the answer, but there has to be a more elegant solution. 我有一些蛮力找到答案的东西,但必须有一个更优雅的解决方案。

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;
    }
}

EDIT 编辑

I created a test that used the brute force method and the 3 methods provided as answers and got somewhat surprising results. 我创建了一个使用蛮力方法和3个方法作为答案的测试,并得到了一些令人惊讶的结果。 All 4 versions produce correct answers (so thanks for your contributions), however, the brute force method seemed to be the fastest, by an order of magnitude. 所有4个版本都能产生正确的答案(感谢您的贡献),然而,蛮力方法似乎是最快的,达到一个数量级。 I tried on a few different systems, compilers, and architectures which all yielded mostly consistent results. 我尝试了几个不同的系统,编译器和架构,它们都产生了大致一致的结果。

The test code can be found here: http://ideone.com/HAgDsF . 测试代码可以在这里找到: http//ideone.com/HAgDsF Please feel free to make suggestions. 请随时提出建议。

I suggest the following solution. 我建议以下解决方案。 I assume that primes are ordered from lower to greater. 我假设素数从低到高排序。 I have also used convenient vector and int types. 我还使用了方便的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 演示

Next I want to make an analysis of proposed solutions. 接下来我想对提出的解决方案进行分析。 Let me use np as a number of primes; 让我用np作为一些素数; n as a number to find the closest result to; n作为一个数字来找到最接近的结果; minP as a minimum prime in the list. minP作为列表中的最小素数。

  1. My solution generates all possible values that are lower than n . 我的解决方案会生成低于n的所有可能值。 New values are generated out of the old ones. 从旧的值生成新值。 Each value is used only once to be the generation source. 每个值仅使用一次作为生成源。 If new value exceeds n it is considered as a valid candidate. 如果新值超过n,则将其视为有效候选者。 In case the list will contain all the primes lower than n still the algo can perform well. 如果列表将包含低于n的所有素数,那么算法可以表现良好。 I don't know pretty time complexity formula for the algo but it is the number of valid candidates lower than n multiplied by the log of previous factor. 我不知道算法的时间复杂度公式,但它是低于n的有效候选者的数量乘以前一个因子的对数。 Log comes from set data-structure operations. 日志来自set数据结构操作。 We can get rid of Log factor if n can be small enough to allocate array of size n to flag which values were already generated, a simple list can hold generation source values instead of set. 如果n足够小以分配大小为n的数组来标记哪些值已经生成,我们就可以摆脱Log因子,一个简单的列表可以保存生成源值而不是set。

  2. Your initial solution has O(n(np + log minP (n))) . 您的初始解决方案有O(n(np + log minP (n))) You check every number to be valid taking then one by one from n to 2n paying np + log minP (n) for each check. 您检查每个数字是否有效然后逐个从n2n为每个支票支付np + log minP (n)

  3. Recursive solution by @anatolyg has a big flaw in "visiting" some valid numbers many times which is very inefficient. @anatolyg的递归解决方案在多次 “访问”一些有效数字方面存在很大的缺陷,这是非常低效的。 It can be fixed by introducing a flags indicating that the number was already "visited". 可以通过引入表示该号码已被“访问”的标志来修复。 For example 12 = 2*2*3 will be visited from 6 = 2*3 and 4 = 2*2 . 例如,从6 = 2*34 = 2*2将访问12 = 2*2*3 Minor flaws are numerous context switching and supporting the state of each call. 小缺陷是许多上下文切换并支持每个呼叫的状态。 The solution has a global variable which clutters global namespace, this can be solved by adding a function parameter. 解决方案有一个全局变量,它使全局命名空间变得混乱,这可以通过添加一个函数参数来解决。

  4. Solution by @dasblinkenlight lacks efficiency because already "used" candidates are taken for generation of the new candidates producing numbers already present in the set. @dasblinkenlight的解决方案缺乏效率,因为已经“使用”候选者被用于生成已经存在于集合中的数字的新候选者。 Although I've borrowed the idea with set. 虽然我已经用套装借用了这个想法。

Based on the @גלעד ברקן's answer I've created a c++ solution which is indeed seems to be asymptotically more efficient because there is no log factor. 基于@גלעדברקן的回答,我创建了一个c++解决方案,由于没有log因子,它确实似乎渐渐更有效。 However I refused to work with double logarithms and left solution with integers. 但是我拒绝使用double对数并使用整数保留解。 The idea is simple. 这个想法很简单。 We have a list of products lower than num . 我们有一个低于num的产品清单。 Each of the products is generated out of the first primesUsed primes. 每个产品都是从第一个primesUsed素数的素数中产生的。 We then try to generate new products using next prime. 然后,我们尝试使用下一个素数生成新产品。 Such approach guarantees to generate unique products: 这种方法可以保证产生独特的产品:

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 演示

Rather than trying to arrive at the answer by repeated factoring, you could try generating all possible products until you enumerate all products under target*minPrime , where minPrime is the smallest prime in your set. 您可以尝试生成所有可能的产品,直到您枚举target*minPrime下的所有产品,其中minPrime是您集合中的最小素数,而不是通过重复分解来获得答案。

Start with a set consisting of 1 . 从包含1的集合开始。 Each iteration tries multiplying each number in the current set by each prime. 每次迭代尝试将每个素数的当前集合中的每个数字相乘。 If a new number under the max is found, it is added to the current set. 如果找到max下的新数字,则将其添加到当前集合中。 The process repeats itself until no new numbers can be added. 该过程重复进行,直到无法添加新数字。

In your case, the first generation would be 在你的情况下,第一代将是

1 2 3 5 7

The next generation would be 下一代将是

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

After that you would see 之后你会看到

Generation 3 第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

Generation 4 第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 

and so on. 等等。 After twelve generations your set would no longer grow, at which point you could find the smallest value above the target. 十二代之后,你的集合将不再增长,此时你可以找到目标之上的最小值。

Demo. 演示。

The idea is, to check all possible products of acceptable primes, and choose the best. 我们的想法是检查所有可接受质数的产品,并选择最佳产品。

To implement that, it's easiest, though probably not most efficient, to use recursion. 要实现这一点,使用递归最简单,尽管可能效率最低。 Make a recursive function that "checks" a temporary product by adding all acceptable primes, one by one. 创建一个递归函数,通过逐个添加所有可接受的素数来“检查”临时产品。 To remember the best result, it's easiest to use a global variable. 要记住最佳结果,最简单的方法是使用全局变量。

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;
}

The usage of a global variable is an ugly hack; 使用全局变量是一个丑陋的黑客; it's good enough in this simple example, but not good for complicated (multi-threaded) systems. 在这个简单的例子中它已经足够好了,但对复杂(多线程)系统并不好。 To eliminate the global variable, stuff the function into a class and make it a method; 要消除全局变量,请将函数填充到类中并使其成为方法; and use a member variable result instead of a global one. 并使用成员变量result而不是全局result

Note: I used vector<int> instead of CVector<UInt32> for convenience. 注意:为方便起见,我使用了vector<int>而不是CVector<UInt32>

Taking the logarithm, we can look at this as a variant of the subset sum problem. 以对数为例,我们可以将其视为子集和问题的变体。 Here's a JavaScript example that enumerates distinct combinations that just pass the target mark. 这是一个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