最小集合[PHP]

时间:2010-06-28 08:10:15

标签: php algorithm set

Minimum Set Cover是一个问题,您必须找到涵盖每个元素所需的最小数量的集合。
例如,假设我们有一组X=array(1,2,3,4,5,6)和另一组S,其中

S[1]=array(1, 4) 
S[2] =array(2, 5) 
S[3] =array(3, 6)
S[4] =array(1, 2, 3) 
S[5] =array(4, 5, 6)

问题是找到涵盖S每个元素的最小X.集合。因此,在我们的案例中,最小集合覆盖率显然是S [4]和S [5],因为它们涵盖所有元素 有没有人知道如何在PHP中实现此代码。请注意,这是NP-complete,因此没有快速算法来解决它。任何PHP解决方案都将受到欢迎。顺便说一句,它不是一个功课,我需要在我的网络应用程序中使用此算法,以生成建议列表。
在此先感谢。

更新1
集合覆盖问题有很多应用。一些有趣的是:

  1. 构建最佳逻辑电路
  2. 空勤人员调度
  3. 装配线平衡
  4. 信息检索
  5. 美术馆问题
  6. 基因组测序
  7. Red-Blue SetCover问题
  8. 更新2
    例如,here您可以看到我提到的问题的工作版本。在这里,甚至它在视觉上显示了集合。但是我需要纯PHP代码,如果有人有它,请善待我们在PHP中提供工作示例。谢谢你

    更新3
    最后,我已经解决了PHP中的问题。我的解决方案基于一本名为Introduction to Algorithms的非常着名的书,集合覆盖问题提出的算法。这是我的解决方案的样子:

    $MainSet=array(1, 2, 3, 4, 5, 6, 7);
    $SubSet=array(array(1,4), array(2, 5), array(3, 6), array(1, 2, 3), array(4, 5, 6));
    
    $UncoveredElements=$MainSet;
    $CoveringSets=array();
    while (count($UncoveredElements)>0){
        $S=SubSetS($UncoveredElements, $SubSet);
        if (is_array($S)){
            $UncoveredElements=array_diff($UncoveredElements, $S);
            $CoveringSets[]=$S;
        }else
            break; //finish work
    }
    echo "Sets that cover MainSet are:";
    var_dump($CoveringSets);
    
    //Subset S is chosen that covers as many uncovered elements as possible
    function SubSetS($UncoveredElements, $SubSetArr){
        $max=0; $s=array();
        foreach($SubSetArr as $SubSet){
            $intersectArr=array_intersect($UncoveredElements, $SubSet);
            $weight=count($intersectArr);
            if ($weight>$max){
                $max=$weight;
                $s=$SubSet;
            }
        }
        if ($max>0)
            return $s;
        else
            return 0;
    }
    

    有关我的解决方案的任何意见和想法?对我来说,它解决了我的问题,这就是我想要的。但如果您建议任何优化,更正代码,请填写免费。
    顺便说一句,非常感谢问题参与者的宝贵回应。

    最终更新
    针对集合覆盖问题的这种贪婪方法并不总能保证最佳解决方案。请考虑以下示例:
    给定:主要元素= {1,2,3,4,5,6} 现在,考虑以下4个子集:子集1 = {1,2},子集2 = {3,4},子集3 = {5,6},子集4 = {1,3,5}。
    上述子集集合的Greedy算法给出了最小集合覆盖: 最小集合封面包含子集= {1,2,3,4} 因此,虽然覆盖主要集合的所有元素的子集的最小集合是前三个子集,但我们得到包含所有4个子集的解决方案。

6 个答案:

答案 0 :(得分:6)

Bakhtiyor,你说这个问题需要你有点矛盾。你先说你想要一个最小的封面,然后自己指出问题是NP难的。您发布的算法是贪婪的集合覆盖算法。该算法找到一个相当小的集合覆盖,但不一定是最小集合覆盖。两个人发布了一个算法,搜索得更彻底并且确实找到了最小集合覆盖率,并且您在一条评论中说您不需要最佳解决方案。

您是否需要最佳解决方案?因为找到最小集合覆盖是一个非常不同的算法问题,因为找到一个相当小的集合封面。必须非常仔细地编写前者的算法,以便大量输入不需要花费很长时间。 (通过我的大输入,我的意思是说,40个子集。)后者的算法很简单,你用自己的代码做得很好。我要改变的一件事是,在你的循环语句“foreach($ SubSetArr as $ SubSet)”之前,我会随机化子集列表。这为算法提供了一些对许多输入最佳的机会。 (但是,有一些例子,最小集合覆盖不包括最大的子集,因此对于某些输入,这个特定的贪婪算法将无法找到最小值,无论顺序如何。)

如果你真的想要最小的封面,而不仅仅是一个非常好的封面,那么你应该说明你希望代码完全解决的最大输入。这是一个至关重要的细节,会影响算法对您的任务的需求。


回应其他人所说的话:首先,如果您想要最佳解决方案,当然没有必要搜索所有子集集合。其次,你不应该寻找问题的“算法”。这个问题有很多算法,有些算法比其他算法快,有些比其他算法更复杂。

这是一种方法,您可以从搜索所有集合的算法开始,并加速它以使事情变得更好。我不会提供代码,因为我不认为提问者想要这样一个奇特的解决方案,但我可以描述这些想法。您可以将搜索排列为分支搜索:最佳集合封面包含最大的子集A或不包含。 (从最大的集合开始就很好。)如果是这样,你可以从元素列表中删除A的元素,从其他子集的元素中删除A的元素,并解决剩余的子集覆盖问题。如果没有,您仍然可以从子集列表中删除A并解决剩余的子集覆盖问题。

到目前为止,这只是一种安排搜索所有内容的方法。但是,有几种重要的方法可以在这种递归算法中采用快捷方式。当您找到解决方案时,您可以记录到目前为止找到的最佳解决方案。这设置了您必须击败的阈值。在每个阶段,您可以总计剩余的最大阈值-1子集的大小,并查看它们是否有足够的元素来覆盖其余元素。如果没有,你可以放弃;你不会超过当前的门槛。

此外,如果在此递归算法中,任何元素仅由一个子集覆盖,则可以引入该子集以确定它是否是最大的子集。 (事实上​​,将这一步骤添加到Bakhtiyor编码的贪婪算法中是一个好主意。)

这两个加速度可以从搜索树中删除大分支,并且它们组合起来效果更好。

由于Don Knuth称为“Dancing Links”,因此这类问题也有一个聪明的数据结构。他提出了一个确切的覆盖问题,这个问题与这个有点不同,但是你可以将它调整到最小子集覆盖和上面的所有想法。跳舞链接是交叉链接列表的网格,允许您检查递归搜索中的子集,而无需复制整个输入。

答案 1 :(得分:1)

  • 找一套封面
  • 找到一个包含较少套装的新套装,直到您满意为止。

也就是说,如果您的第一次尝试找到有4套的套装,您可以在下次尝试时停在3。这已经比检查所有可能的封面有所改进。

答案 2 :(得分:1)

您需要获得S [i]的所有可能组合,然后搜索覆盖原始集合的最小数量的集合。

// original set
$x = array(1, 2, 3, 4, 5, 6);
$xcount = sizeof($x);

// subsets
$s = array();
$s[] = array(1, 4);
$s[] = array(2, 5);
$s[] = array(3, 6);
$s[] = array(1, 2, 3);
$s[] = array(4, 5, 6);

// indices pointing to subset elements
$i = range(0, sizeof($s) - 1);

// $c will hold all possible combinations of indices
$c = array(array( ));

foreach ($i as $element)
    foreach ($c as $combination)
        array_push($c, array_merge(array($element), $combination));

// given that $c is ordered (fewer to more elements)
// we need to find the first element of $c which
// covers our set
foreach ($c as $line)
{
    $m = array();
    foreach ($line as $element)
        $m = array_merge($m, $s[$element]);

    // check if we have covered our set
    if (sizeof(array_intersect($x, $m)) == $xcount)
    {
        echo 'Solution found:<br />';
        sort($line);
        foreach ($line as $element)
            echo 'S[',($element+1),'] = ',implode(', ',$s[$element]),'<br />';
        die();
    }
}

答案 3 :(得分:1)

在这里,为您的示例找到11个解决方案:

function array_set_cover($n,$t,$all=false){
    $count_n = count($n);
    $count = pow(2,$count_n);

    for($i=1;$i<=$count;$i++){
        $total=array();
        $anzahl=0;
        $k = $i;
        for($j=0;$j<$count_n;$j++){
                        if($k%2){
                $total=array_merge($total,$n[$j]);
                        }
            $anzahl+=($k%2);
            $k=$k>>1;
        }
                $total = array_unique($total);
        if(count(array_diff($t,$total)) < 1){
            $loesung[$i] = $anzahl;
            if(!$all){
                break;
            }
        }
    }

    asort($loesung);

    foreach($loesung as $val=>$anzahl){
        $bit = strrev(decbin($val));
        $total=0;
        $ret_this = array();
        for($j=0;$j<=strlen($bit);$j++){
            if($bit[$j]=='1'){
                $ret_this[] = $j;
            }   
        }
        $ret[]=$ret_this;
    }

    return $ret;
}

// Inputs
$s = array();
$s[] = array(1, 4);
$s[] = array(2, 5);
$s[] = array(3, 6);
$s[] = array(1, 2, 3);
$s[] = array(4, 5, 6);

// Output
$x = array(1, 2, 3, 4, 5, 6);

var_dump(array_set_cover($s,$x)); //returns the first possible solution (fuc*** fast)

var_dump(array_set_cover($s,$x,true)); // returns all possible solution (relatively fast when you think of all the needet calculations)

如果在没有第三个参数的情况下调用该函数,则会返回第一个找到的解决方案(作为使用了$ s的数组键的数组) - 如果将第三个参数设置为true,返回所有解决方案(以相同的格式,按使用项目的数量排序,因此第一个应该是最好的)。 retun值是这样的:

array(1) { // one solution
  [0]=>
  array(3) { // three items used
    [0]=>
    int(0) // used key 0  -> array(1, 4)
    [1]=>
    int(1) // used key 1 ...
    [2]=>
    int(2) // used key 2 ...
  }
}

答案 4 :(得分:0)

您可以看到算法here的解释,然后将其从伪代码转换为php。享受!

答案 5 :(得分:0)

如果您想拥有最佳解决方案,则必须列举所有可能的组合。 Here这样做的PHP代码(考虑网站上的评论!)。

然后,您必须检查您的哪个组合是具有最少使用元素的组合。我想你可以自己做。