Алгоритм генерации подмножеств множества, удовлетворяющих определенным условиям.

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

Например, имея список всех положительных целых чисел, меньших 100, определите подмножества, сумма которых меньше 130: (100,29) (0,1,40), (0) и т. д.

Как я могу это сделать (желательно на Python)?

Спасибо! :)


person ooboo    schedule 01.06.2009    source источник


Ответы (6)


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

Если вы хотите быть универсальным в отношении ограничения, я думаю, что это лучшая стратегия.

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

GetAllSubsets(List objects) {
    List generated = {};
    GetAllSubsets(generated, [], objects);
    return generated;
}

GetAllSubsets(List subsetGenerated, List objectFixed, List objectsToFix) {
    GetAllSubsets(subsetGenerated, objectFixed, objectsToFix.sublist(1, objectsToFix.length());
    if (satisfy(toCheck = objectsFixed.add(objectsToFix.get(0)))) {
        subsetGenerated.add(toCheck);
        GetAllSubsets(subsetGenerated, toCheck, objectsToFix.sublist(1, objectsToFix.length());
    }
}

На самом деле подмножества, добавленные первым вызовом GetAllSubsets, не имеют первого элемента objectsToFix, в то время как подмножества, добавленные вторым вызовом (если условие сокращения не нарушено), имеют этот элемент, поэтому пересечение двух наборы сгенерированных подмножеств пусты.

person akappa    schedule 01.06.2009

Вы можете создавать свои наборы рекурсивно, начиная с пустого набора и пытаясь добавить больше элементов, отказываясь от рекурсивной строки выполнения, если одно из подмножеств (и, следовательно, все его надмножества) не удовлетворяет условию. Вот некоторый псевдокод, предполагающий набор S, чьи подмножества, удовлетворяющие условию, вы хотели бы перечислить. Для удобства предположим, что элементы S можно пронумеровать как x(0), x(1), x(2),...

EnumerateQualifyingSets(Set T)
{
    foreach (x in S with an index larger than the index of any element in T)
    {
            U = T union {x}

            if (U satisfies condition)
            {
                print U
                EnumerateQualifyingSets(U)
            }
    }
}

Первый вызов будет с T в качестве пустого множества. Затем будут напечатаны все подмножества S, соответствующие условию. Эта стратегия в решающей степени опирается на тот факт, что подмножество S, не удовлетворяющее условию, не может содержаться в подмножестве, которое ему соответствует.

person PeterAllenWebb    schedule 01.06.2009
comment
Этот псевдокод многократно генерирует одни и те же подмножества: эта дополнительная сложность может препятствовать оптимизации сокращения. Вы должны добавить запоминание, чтобы решить проблему. - person akappa; 02.06.2009

Конечно, есть способы сделать это, но если вы не можете каким-то образом ограничить условие, это займет O (2 ^ n) шагов. Если вы рассмотрите, например, условие от 1 до 100, при котором будут выбраны все подмножества (например, ‹ i для i в 1-n), тогда вы в конечном итоге перечислите все подмножества.

Вы бы смотрели на

for i in the powerset of {1-n}
    if cond(i)
       note that set

Вы можете получить набор мощности набора, просто сгенерировав все двоичные числа от 0 до sn-1 и выбрав элемент i для подмножества, когда бит i равен 1.

person Charlie Martin    schedule 01.06.2009
comment
Нет смысла перебирать все наборы; если сумма (x, y) больше 100, нет необходимости проверять любое оставшееся подмножество с x, y! (Например: (40,79,50) имеет сумму больше 100, поэтому нет необходимости проверять (40,79,50,1), (40,79,50,66,1) и т.д... - person ooboo; 01.06.2009
comment
Но это только один пример возможных условий. Что, если условие состоит в том, что сумма меньше 5050 (сумма от 1 до 100)? Тогда все подмножества будут удовлетворять условию. - person Charlie Martin; 01.06.2009
comment
Но тот факт, что в худшем случае вы получите временную сложность O(2^n), не должен останавливать нас от поиска хорошей эвристики для решения проблемы. - person akappa; 02.06.2009
comment
Постановка задачи не допускает каких-либо общих оптимизаций: предикат выбора открыт. Ваше решение будет работать для примера. - person Charlie Martin; 02.06.2009

Я сделал что-то подобное для алгоритма генерации расписания занятий. В нашем классе расписания было 2 элемента — список курсов, добавленных в расписание, и список курсов, доступных для добавления.

Псевдокод:

queue.add(new schedule(null, available_courses))
while( queue is not empty )
    sched = queue.next()
    foreach class in sched.available_courses
        temp_sched = sched.copy()
        temp_sched.add(class)
        if(temp_sched.is_valid())
            results.add(temp_sched)
            queue.add(temp_sched)

Идея состоит в том, чтобы начать с пустого расписания и списка доступных занятий и искать по дереву действительные расписания (действительное значение соответствует требованиям, заданным пользователем, не имеет временных конфликтов и т. д.). Если расписание недействительно, оно выбрасывается — мы не можем сделать недействительное расписание действительным, добавляя классы (т. е. обрезая дерево).

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

person baumgart    schedule 01.06.2009

Вот конкретный пример ответа акаппы с использованием рекурсивной функции для создания подмножеств:

def restofsubsets(goodsubset, remainingels, condition):
    answers = []
    for j in range(len(remainingels)):
        nextsubset = goodsubset + remainingels[j:j+1]
        if condition(nextsubset):
            answers.append(nextsubset)
            answers += restofsubsets(nextsubset, remainingels[j+1:], condition)
    return answers

 #runs slowly
 easieranswer = restofsubsets([], range(101), lambda l:sum(l)<40)

 #runs much faster due to eliminating big numbers first
 fasteranswer = restofsubsets([], range(100,-1,-1), lambda l:sum(l)<40)

 #runs extremely slow even with big-numbers-first strategy
 finalanswer = restofsubsets([], range(100,-1,-1), lambda l:sum(l)<130)
person krubo    schedule 01.06.2009

Я думаю, что в худшем случае вам все равно придется генерировать все подмножества и вычислять сумму каждого набора, чтобы определить, является ли он подходящим или нет. Асимптотически это стоимость процедуры генерации подмножеств.

Ниже приведен метод, который я реализовал в javascript для той же идеи.

//this is to generate an array to test
var numbers = (function(start, end){
    var result = [],
        i =  start; 
    for(; i <= end; i++){
        result.push(i);
    }
    return result; 
})(1, 12);

//this is the qualifying function to determine if the generated array is qualified
var fn = (function(maxSum){
    return function(set){
        var sum = 0;
        for(var i = 0 ; i< set.length; i++){
            sum += set[i];
            if( sum > maxSum ){
                return false;
            }
        }
        return true;
    }
})(30);

//main function
(function(input, qualifyingFn){
    var result, mask, total = Math.pow(2, input.length);
    for(mask = 0; mask < total; mask++){

        result = [];
        sum = 0;

        i = input.length - 1; 
        do{
            if( (mask & (1 << i)) !== 0){
                result.push(input[i]);
                sum += input[i];
                if( sum > 30 ){
                    break;
                }
            }
        }while(i--);
        if( qualifyingFn(result) ){
            console.log(JSON.stringify(result));
        }
    }

})(numbers, fn);
person Dewey    schedule 14.08.2013