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
集合覆盖问题有很多应用。一些有趣的是:
更新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个子集的解决方案。
答案 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代码(考虑网站上的评论!)。
然后,您必须检查您的哪个组合是具有最少使用元素的组合。我想你可以自己做。