我在报告和位置之间有一对多的关系。我的目标是将我的报告列表缩小到尽可能少的报告,其中包含所有代表的位置。
如果我将其简化为数字列表,它将如下所示,其中键是报告,数组是位置列表:
{
1:[1,2],
2:[1],
3:[2,3],
4:[1,3,4]
}
理想的解决方案是选择报告1 or 3
和4
。可以选择1
或3
,因为它们都包含位置2
和带有报告1
的重复位置4
。已选择报告4
,因为它是唯一一个位置4
。
效率不是主要问题。使用PHP缩小列表的最佳方法是什么?
答案 0 :(得分:3)
NP-completeness再次出现。
您尝试解决的问题称为Set Cover,而且肯定是NP-Complete。
这意味着不太可能存在“高效”(读取,多项式时间)算法。
好消息是有简单的近似算法可以给你一个不错的近似值。
请参阅this了解“明显的”贪婪算法(在每个点,选择具有最大数量的未覆盖位置的报告)如何为您提供log (R)
近似值,其中R
为报告的数量(实际上,它甚至比那更好)。
答案 1 :(得分:1)
如果效率不是你所说的问题,我可以建议你 O(2 ^ n * k)算法,其中 n 是列表的数量和 k 是它们长度的总和。只需使用位掩码进行所有可能的组合,并为每个组合计算它是否涵盖所有内容。
P.S。 这是一个实现(http://ideone.com/bAGpbL):
$arr = array(
0 => array(1,2),
1 => array(1),
2 => array(2,3),
3 => array(1,3,4),
);
// It is assumed that all indexes are sequential starting from 0
$total_cover = array();
foreach($arr as $sub_arr) {
foreach($sub_arr as $value) {
$total_cover[$value] = true;
}
}
$n = count($arr);
$best_cover = array_keys($arr);
for($i = 0; $i < (1 << $n); $i++) {
$cover = array();
$selected_list = array();
for($j = 0; $j < $n; $j++) {
if(($i >> $j) & 1) {
$selected_list[] = $j;
foreach($arr[$j] as $value) {
$cover[$value] = true;
}
}
}
$good_cover = true;
foreach($total_cover as $key => $value) {
if(!isset($cover[$key])) {
$good_cover = false;
break;
}
}
if($good_cover && count($selected_list) < count($best_cover)) {
$best_cover = $selected_list;
}
}
var_dump($best_cover);