PHP:在数据库中查找一组数字,使它们的总和等于特定的数字

6
首先,我是PHP新手... 所以我还在过程化地编写和理解PHP。也就是说,
我有一个保存在数据库中的数字集合(金额)。
问题:使用PHP和mySQL,
1.最好的方法是什么,可以从数据库中提取此信息,使金额与其交易ID相关联?
2.最重要的是,我需要找到一组匹配的数字,在数据库中等于29的总和。
下面是我的数据库Transaction_tlb的Transaction表格,用于mydb。
    Transaction_ID |     Name         |       Date      | Amount
    ---------------|------------------|-----------------|------------ 
    11012          | Jonathan May     |   6/12/2016     |     84
    21012          | John Pedesta     |   6/12/2016     |     38
    31012          | Mary Johnson     |   1/01/2017     |     12
    41012          | John Johnson     |   8/01/2017     |     13
    51012          | Keith Jayron     |   8/01/2017     |     17
    61012          | Brenda Goldson   |   8/01/2017     |     2
    71012          | Joshua Traveen   |   8/01/2017     |     78
    81012          | Remy ma Goldstein|   8/01/2017     |     1
    91012          | Barbie Traveen   |   8/01/2017     |     1

现在,我有一个想法,但它不够高效。我将尝试每种可能的情况,这意味着如果我有n个值要检查,时间复杂度将约为2^n。这非常低效(而且我甚至不知道我的代码是否有意义(见下文))。
我在这个YouTube视频中看到了一个类似的例子:https://www.youtube.com/watch?v=XKu_SEDAykw&t,但是,我不确定如何用php编写代码。
代码如下:
<?php
  if (!mysql_connect("localhost", "mysql_user", "mysql_password") || !mysql_select_db("mydb")) {
      die("Could not connect: " . mysql_error()); } //End DB Connect

  $capacity = 29; //Knapsack Capacity or Sum

  //Select Transact ID and Value from the Database where Amount is <= Capacity
  $fetchQuery = "SELECT 'Transaction_ID', 'Amount' FROM 'Transaction_tlb' WHERE 'Amount' <= $capacity"; 

  $components = array(); //new array to hold components

  if ($queryResults = mysql_query($fetchQuery)) {

     //check if data was pulled
     if (mysql_num_row($queryResults) != NULL) {
        while ($row = mysqli_fetch_assoc($queryResults) {
           $components[$row['Transaction_ID']] = $row['Amount'];
        }
     }
  }

  /* Correct me if i am wrong, but, Components associative array Should be something like
  $components = array('11012'=> 84, '21012'=> 38, '31012'=> 12, '41012'=> 13, '51012'=> 17, 
                      '61012'=> 2, '71012'=> 78, '81012'=> 1, '91012'=> 1);
  */

  $components = asort($components) // sort array in ascending order
  $componentCount = count($component)


  function match ($componentCount, $capacity) {
              $temp = match (($componentCount - 1), $capacity);
              $temp1 = $component[$componentCount] + match (($componentCount - 1), ($capacity - $component[$componentCount]));
              $result = max($temp, $temp1);
         return $result;
         }
}?>

请问有人能指点我正确的方向吗?这段代码不起作用...即使它起作用了...该方法也根本不够高效。如果我需要处理300万条记录会怎么样呢?求帮助。


提示:在 PHP 中不要对值进行排序,使用 SQL 的 order by。顺便说一下,https://en.wikipedia.org/wiki/Subset_sum_problem 是 NP 完全问题。 - maraca
2个回答

4
你可以将问题表述为0/1背包问题。PHP的现成实现可用
使用链接页面中定义的knapSolveFast2函数,可以按照下面的示例进行操作。这里的想法是将进入背包算法的“权重”设置为值本身。
$components = array(84, 38, 12, 13, 17, 2, 78, 1, 1);

$m = array();
list($m4, $pickedItems) = knapSolveFast2($components, $components, sizeof($components)-1, 29, $m);

echo "sum: $m4\n";
echo "selected components:\n";
foreach($pickedItems as $idx){
    echo "\t$idx --> $components[$idx]\n";
}

这将产生:

sum: 29
selected components:
    2 --> 12
    4 --> 17 

备注:

  • 您可以修改SQL查询,以跳过大于所需总和(29)的amount行。
  • 上面的函数将选择一个解决方案(假设存在),它不会提供所有解决方案。
  • 应该检查返回值$m4是否确实等于指定的总和(29)-由于算法的工作方式,指定的金额仅是不保证达到的上限(例如对于37而不是29,返回值仅为34,因为没有组合输入数字,其总和将产生37)

我真的不理解这个函数的逻辑,而且似乎在每种情况下都不能正常工作,我希望能得到这个函数knapSolve($w,$v,$i,$aW) 的适当解释。谢谢。 - Tamara
@Tamara,你有哪些失败的例子吗?我不是链接函数的作者,但它应该是维基描述的算法的直接实现。此外,适用于此问题的函数是knapSolveFastknapSolveFast2knapSolve本身不提供所选元素的索引... - ewcz

1
这实际上是一个背包问题,但我会尝试给出一个完整的解决方案,该方案并不是最优的,但说明了解决您的问题的完整策略,同时使内容更加通俗易懂。首先,您可以只使用一次迭代来处理数字数组,无需递归和预排序。动态编程就是你所需要的,跟踪所有之前可能的部分和“路径”。这个想法与您描述的递归方法有些相似,但我们可以迭代地完成它,而无需预先排序。
假设输入数组为[84, 38, 12, 13, 17, 2, 78, 1, 1],目标为29,我们按以下方式循环处理数字:
* 84 - too big, move on
* 38 - too big, move on
* 12 - gives us a subtarget of 29-12 = 17
            subtargets:
              17 (paths: 12)
* 13 - gives us a subtarget of 29-13=16
            subtargets:
              16 (paths: 13)
              17 (paths: 12)
* 17 - is a subtarget, fulfilling the '12' path;
   and gives us a subtarget of 29-17=12
            subtargets:
              12 (paths: 17)
              16 (paths: 13)
              17 (paths: 12)
            solutions:
              12+17
etc.

这里的诀窍是,在循环数字时,我们保留一个查找表来记录“subTargets”,这些数字可以使用先前看到的一个或多个组合(“路径”)来给出解决方案。如果新数字是子目标,则将其添加到解决方案列表中;否则,我们将其附加到现有路径中,其中num<subTarget,然后继续执行。

以下是一个快速而简单的PHP函数:

// Note: only positive non-zero integer values are supported
// Also, we may return duplicate addend sets where the only difference is the order
function findAddends($components, $target)
{
    // A structure to hold our partial result paths
    // The integer key is the sub-target and the value is an array of string representations
    // of the 'paths' to get to that sub-target. E.g. for target=29
    // subTargets = {
    //   26: { '=3':true },
    //   15: { '=12+2':true, '=13+1':true }
    // }
    // We are (mis)using associative arrays as HashSets
    $subTargets = array();

    // And our found solutions, stored as string keys to avoid duplicates (again using associative array as a HashSet)
    $solutions = array();

    // One loop to Rule Them All
    echo 'Looping over the array of values...' . PHP_EOL;
    foreach ($components as $num) {
        echo 'Processing number ' . $num . '...' . PHP_EOL;

        if ($num > $target) {
            echo $num . ' is too large, so we skip it' . PHP_EOL;
            continue;
        }

        if ($num == $target) {
            echo $num . ' is an exact match. Adding to solutions..' . PHP_EOL;
            $solutions['='.$num] = true;
            continue;
        }

        // For every subtarget that is larger than $num we get a new 'sub-subtarget' as well
        foreach ($subTargets as $subTarget => $paths) {
            if ($num > $subTarget) { continue; }

            if ($num == $subTarget) {
                echo 'Solution(s) found for ' . $num . ' with previous sub-target. Adding to solutions..' . PHP_EOL;
                foreach ($paths as $path => $bool) {
                    $solutions[$path . '+' . $num] = true;
                }
                continue;
            }

            // Our new 'sub-sub-target' is:
            $subRemainder = $subTarget-$num;
            // Add the new sub-sub-target including the 'path' of addends to get there
            if ( ! isset($subTargets[$subRemainder])) { $subTargets[$subRemainder] = array(); }

            // For each path to the original sub-target, we add the $num which creates a new path to the subRemainder
            foreach ($paths as $path => $bool) {
                $subTargets[$subRemainder][$path.'+'.$num] = true;
            }
        }

        // Subtracting the number from our original target gives us a new sub-target
        $remainder = $target - $num;

        // Add the new sub-target including the 'path' of addends to get there
        if ( ! isset($subTargets[$remainder])) { $subTargets[$remainder] = array(); }
        $subTargets[$remainder]['='.$num] = true;
    }
    return $solutions;
}

像这样运行代码:

$componentArr = array(84, 38, 12, 13, 17, 2, 78, 1, 1);
$addends = findAddends($componentArr, 29);

echo 'Result:'.PHP_EOL;
foreach ($addends as $addendSet => $bool) {
    echo $addendSet . PHP_EOL;
}

输出结果为:

Looping over the array of values...
Processing number 84...
84 is too large, so we skip it
Processing number 38...
38 is too large, so we skip it
Processing number 12...
Processing number 13...
Processing number 17...
Solution(s) found for 17 with previous sub-target. Adding to solutions..
Processing number 2...
Processing number 78...
78 is too large, so we skip it
Processing number 1...
Processing number 1...
Solution(s) found for 1 with previous sub-target. Adding to solutions..

Result:
=12+17
=12+13+2+1+1

1
我认为这个想法非常具有说明性!我只想指出,内存使用量可能会相当快地增长 - 例如,如果输入的数字是 1, 2, ..., N,并且正在寻找 N*(N+1)/2 的总和(一种最坏情况),那么当 N=23 时,我已经达到了约4G的峰值使用率。 - ewcz
绝对的,最坏情况下内存使用是可怕的(即使在平均情况下也很糟糕)。 - Jens Roland
这段代码的记忆化会让它变得更好吗? - Tamara
@Tamara:记忆化并不能解决内存问题,它通过存储单个函数调用的结果来消除代码执行(理想情况下是整个子树)。这段代码只有一个函数调用(而不是递归或遍历树的函数),因此它已经做出了类似的权衡。实际上,这种权衡所使用的内存正是导致内存问题的原因 ;) - Jens Roland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接