Минимальное покрытие набора [PHP]

Минимальное Покрытие Набора является вопросом, где необходимо найти, что минимальное количество наборов должно было покрыть каждый элемент.
Например, предположите, что у нас есть ряд X=array(1,2,3,4,5,6) и 5 других наборов 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, таким образом, нет никакого алгоритма FAST для решения его. Любое решение в PHP будет одобрено. И BTW, это не домашняя работа, я должен использовать этот алгоритм в своем веб-приложении для генерации списка предложения.
Заранее спасибо.

Обновление 1
Существует много приложений Набора, Покрывающего проблему. Некоторые интересные:

  1. Конструкция Оптимальных логических схем
  2. Планирование летного экипажа
  3. Балансировка сборочного конвейера
  4. Информационный поиск
  5. Проблема Художественной галереи
  6. Упорядочивание генома
  7. Красно-синяя проблема SetCover

Обновление 2
Например, здесь Вы видите рабочую версию проблемы, которую я упомянул. Здесь, даже это показывает визуально наборы. Но мне нужен чистый код PHP для этого, если у кого-то есть он быть добрым для предоставления нам рабочий пример в PHP.Спасибо

Обновление 3
Наконец, я решил проблему в PHP. Мое решение на основе алгоритма, предложенного на очень известной книге под названием Введение в Алгоритмы, разделите покрывающую набор проблему. Здесь, как мое решение похоже:

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

Какие-либо комментарии и идеи о моем решении? Для меня это решает мою проблему, именно это я хотел. Но если Вы предлагаете какую-либо оптимизацию, исправление к коду, заполнитесь свободный.
BTW, большое спасибо за участников вопроса для их ценных ответов.

Заключительное обновление
Этот Жадный подход для Набора, Покрывающего проблему, не всегда гарантирует оптимальное решение. Рассмотрите следующий пример:
Данный: элементы основного набора = {1, 2, 3, 4, 5, 6}
Теперь, рассмотрите 4 подмножества следующим образом: Подмножество 1 = {1, 2}, Подмножество 2 = {3, 4}, Подмножество 3 = {5, 6}, Подмножество 4 = {1, 3, 5}.
Жадный алгоритм для вышеупомянутого набора Подмножеств дает Минимальное Покрытие Набора как: Минимальное Покрытие Набора содержит Подмножества = {1, 2, 3, 4}.
Таким образом, хотя минимальный набор подмножеств, которые покрывают все элементы Основного набора, является первыми тремя подмножествами, мы получаем решение, содержащее все эти 4 Подмножества.

13
задан Bakhtiyor 28 June 2010 в 15:20
поделиться

6 ответов

Бахтиёр, то, что вы говорите, что вам нужно для решения этой проблемы, немного противоречиво. Сначала вы сказали, что вам нужен минимальный набор крышек, и указали себе, что проблема NP-сложная. Алгоритм, который вы опубликовали, является алгоритмом покрытия жадного набора. Этот алгоритм находит довольно маленькую обложку набора, но не обязательно минимальную обложку набора. Два человека опубликовали алгоритм, который ищет более тщательно и действительно находит минимальный набор обложек, и в одном комментарии вы сказали, что вам не нужно оптимальное решение.

Вам нужно оптимальное решение или нет? Потому что поиск минимального покрытия набора - это совсем другая проблема алгоритма, чем поиск довольно маленького покрытия набора . Алгоритм для первого случая должен быть написан очень тщательно, чтобы не потребовалось много времени для ввода большого количества данных. (Под большим вводом я подразумеваю, скажем, 40 подмножеств.) Алгоритм для последнего прост, и вы хорошо поработали со своим собственным кодом.Единственное, что я хотел бы изменить, это то, что перед вашим оператором цикла «foreach ($ SubSetArr as $ SubSet)» я бы рандомизировал список подмножеств. Это дает алгоритму некоторые шансы на то, что он будет оптимальным для многих входных данных. (Но есть примеры, когда минимальное покрытие набора не включает самое большое подмножество, поэтому для некоторых входных данных этот жадный алгоритм не будет иметь шансов найти минимум, независимо от порядка.)

Если вам действительно нужен минимум установите обложку, а не просто довольно хорошую обложку, тогда вы должны указать самый большой вход, который вы хотите, чтобы код полностью решал. Это важная деталь, которая влияет на то, насколько сложным должен быть алгоритм для вашей задачи.


В ответ на то, что говорили некоторые другие люди: во-первых, конечно, нет необходимости искать во всех наборах подмножеств, если вы хотите оптимальное решение. Во-вторых, вам не следует искать «алгоритм» решения проблемы. В задаче много алгоритмов, одни быстрее других, некоторые сложнее других.

Вот один из способов, которым вы можете начать с алгоритма, который ищет по всем коллекциям, и ускорить его, чтобы сделать что-то намного лучше. Я не буду приводить код, так как не думаю, что задающему вопрос нужно такое модное решение, но я могу описать идеи. Вы можете организовать поиск как разветвленный поиск: Либо лучший набор обложек содержит наибольшее подмножество A, либо его нет. (Хорошо начинать с самого большого набора.) Если это так, вы можете удалить элементы A из списка элементов, удалить элементы A из элементов других подмножеств и решить проблему покрытия оставшегося подмножества. В противном случае вы все равно можете удалить A из списка подмножеств и решить оставшуюся проблему покрытия подмножества.

Пока что это просто способ устроить обыск по всему. Но есть несколько важных способов сократить путь в этом рекурсивном алгоритме. По мере нахождения решений вы можете записывать лучшее, что вы нашли на данный момент. Это устанавливает порог, который вы должны преодолеть. На каждом этапе вы можете суммировать размеры самых больших оставшихся подмножеств с порогом 1 и посмотреть, достаточно ли в них элементов для покрытия оставшихся элементов. Если нет, можете сдаться; вы не побьете текущий порог.

Вдобавок, если в этом рекурсивном алгоритме какой-либо элемент покрывается только одним подмножеством, вы можете наверняка добавить это подмножество, независимо от того, является ли оно самым большим. (Фактически, это хорошая идея добавить этот шаг даже в жадный алгоритм, который закодировал Бахтиер.)

Каждое из этих двух ускорений может отсечь огромные ветви из дерева поиска, и они работают даже лучше в сочетании.

Существует также умная структура данных для этого типа проблем, благодаря Дону Кнута, названная «Танцующие ссылки». Он предложил это для конкретной задачи покрытия, которая немного отличается от этой, но вы можете адаптировать ее к минимальному подмножеству покрытия и всем вышеперечисленным идеям.Танцующие ссылки - это сеть пересекающихся связанных списков, которая позволяет вам проверять подмножества в рекурсивном поиске и из него без необходимости копировать весь ввод.

6
ответ дан 2 December 2019 в 01:31
поделиться
  • Найдите одно покрытие множества
  • Найдите новое покрытие множества с меньшим количеством множеств, пока вы не будете удовлетворены.

То есть, если в первой попытке вы нашли обложку с 4 наборами, то в следующих попытках вы можете остановиться на 3. Это уже улучшение по сравнению с проверкой всех возможных покрытий.

1
ответ дан 2 December 2019 в 01:31
поделиться

Вам нужно получить все возможные комбинации 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();
    }
}
1
ответ дан 2 December 2019 в 01:31
поделиться

вот оно, поиск 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 , ВСЕ решения будут возвращены (в том же формате, в порядке количества использованных элементов, поэтому первое должно быть лучшим ). значение перенастройки выглядит следующим образом:

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 ...
  }
}
1
ответ дан 2 December 2019 в 01:31
поделиться

Вы можете посмотреть объяснение алгоритма здесь, а затем перевести его из псевдокода в php. Наслаждайтесь!

0
ответ дан 2 December 2019 в 01:31
поделиться

Если вы хотите получить оптимальное решение, вам придется перечислить все возможные комбинации. Вот PHP-код для этого (учитывайте комментарии на сайте!).

Затем нужно проверить, какая из ваших комбинаций имеет наименьшее количество используемых элементов. Думаю, вы можете сделать это самостоятельно.

0
ответ дан 2 December 2019 в 01:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: