Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Алгоритмы > Оптимизация, получение суммы используя


Автор: Stolzen 10.8.2012, 11:19
Всем доброго времени суток!

Задача следующая: 
Есть множество P каждый элемент которого имеет некоторую цену pi; и есть заданная цена W. 
Необходимо выбрать элементы из P так, чтобы количество элементов было минимальным, а сумма цен элементов была больше или равна W. Если вариантов несколько, выбрать тот, который ближе к W.

Например, 
элементы [100, 20, 30, 50], цена 100, нужно выбрать [100]
элементы [100, 30, 20, 50, 10], цена 80, нужно выбрать [50, 30]
элементы [100, 30, 20, 50, 10], цена 85, нужно выбрать [50, 30, 10]

Теорию оптимизации совсем не помню, поэтому буду признателен любой наводке.

Добавлено через 2 минуты и 18 секунд
Да, кстати, числа могут быть довольно большими (в пределах long)

Автор: Stolzen 10.8.2012, 12:39
Вот что придумалось

поиск (P, W, результат)
  •  Если P пустой и W > 0, возвращаем пустой список
  •  Если P пустой и W < 0, возвращаем результат
  •  P не пустой. Есть два варианта - поместить первый элемент из P в результат или не поместить
  •  помещаем p0 в результат: with <- поиск(tail(P), W - head(P), head(P) ++ результат)
  •  не помещаем p0 в результат: without <- поиск(tail(P), W, результат)
  •  если сумма в with больше W, а сумма в without меньше, возвращем with
  •  если сумма в with меньше W, а сумма в without больше, возвращем without 
  •  если сумма в with меньше W, и сумма в without меньше, возвращем пустой список
  •  если сумма в with ближе к W, возвращаем with, иначе without
  •  если сумма одинаковая, а количество элементов в with меньше, возвращаем with, иначе without
Перед этим еще можно сразу же выкинуть все элементы из P, которые больше W и заодно проверить, есть ли в P такой pi что pi == W
Чем-то напомнинает рюкзак. Что скажете?

Автор: Akina 10.8.2012, 13:22
Цитата(Stolzen @  10.8.2012,  12:19 Найти цитируемый пост)
Необходимо выбрать элементы из P так, чтобы количество элементов было минимальным, а сумма цен элементов была больше или равна W. Если вариантов несколько, выбрать тот, который ближе к W.

Вывод из формулировки - количество приоритетнее близости суммы. Следовательно, во 2 и 3 вариантах следует выбрать то же решение, что и в первом варианте - именно это решение даёт минимальное количество элементов.

Цитата(Stolzen @  10.8.2012,  13:39 Найти цитируемый пост)
Что скажете?

Прежде чем формулировать задачу остальным, следует её до конца осмыслить для себя.

Автор: Stolzen 10.8.2012, 15:03
Да, не совсем верно сформулировал, близость к сумме конечно приоритетнее, чем количество элементов. 

Автор: korian 12.8.2012, 14:33
Думаю так должно работать быстрее:
Код

public class Sum {
    //основная точка входа
    public static Result find(final List<Integer> elements, final int minSum) {
        if (elements.isEmpty()) //если элементов нету
            return null; //нету решений
        Collections.sort(elements); //сортируем элементы по возрастанию
        return findInSortedArray(elements, minSum); //основная функциональность
    }

    //требование - элементы отсортированы и количество элементов больше нуля. minSum - это W в вашем описании
    public static Result findInSortedArray(final List<Integer> elements, final int minSum) {
        int index = Collections.binarySearch(elements, minSum); //поиск элемента
        if (index >= 0) //если нашли
            return new Result(elements.get(index)); //возвращаем его как результат

        int upperBoundIndex = -index - 1; //элемент не найден, здесь индекс элемента, который больше чем minSum (может указывать за пределы множества, если большего элемента нету)
        if (upperBoundIndex == 0) //элементов меньше чем minSum нету
            return new Result(elements.get(upperBoundIndex)); //возвращаем первый, больший чем minSum, элемент

        List<Integer> leftElements = elements.subList(0, upperBoundIndex); //создается подмножество элементов, которое содержит только элементы, которые меньше minSum
        Result leftResult = findBestSum(leftElements, minSum); //поиск решения для подмножества
        Result rightResult = null;
        if (upperBoundIndex != elements.size()) //существует элемент, который больше minSum
            rightResult = new Result(elements.get(upperBoundIndex)); //создаем решение на основе большего элемента

        return Result.getBetter(rightResult, leftResult); //выбираем лучший вариант
    }

    //требование - элементы отсортированы, количество элементов больше нуля и среди элементов, нету элементов больше minSum
    public static Result findBestSum(List<Integer> elements, final int minSum) {
        Result result = null;

        while (true) {
            int lastIndex = elements.size() - 1;
            int lastValue = elements.get(lastIndex);
            elements = elements.subList(0, lastIndex); //создается подмножество элементов, которое не содержит последний элемент
            if (elements.isEmpty()) //если подмножество пусто
                break; //выходим

            Result subResult = findInSortedArray(elements, minSum - lastValue); //поиск решения для подмножества без последнего элемента, и W = minSum - <значение последнего элемента>
            if (subResult == null) //решенией нет
                break; //выходим (их уже и не будет, т.к. массив отсортирован)

            Result newResult = new Result(subResult, lastValue); //Формируем решение на основе решения для подмножества. Result = {<Result для подмножества>, lastValue}. lastValue - удаленные элемент из подмножества

            result = Result.getBetter(result, newResult); //выбираем лучшее решение между текущим и вновь посчитанным.
        }

        return result;
    }
}

Код

public class Result {
    private final List<Integer> elements;
    private final int sum;
    
    public Result(int oneElement) {
        this.elements = Collections.singletonList(oneElement);
        this.sum = oneElement;
    }

    public Result(Result subResult, int newValue) {
        this.elements = new ArrayList<>(subResult.elements);
        this.elements.add(newValue);
        this.sum = subResult.getSum() + newValue;
    }

    public int get(int index) {
        return elements.get(index);
    }
    public int size() {
        return elements.size();
    }
    public int getSum() {
        return sum;
    }

    public static Result getBetter(Result left, Result right) {
        if (right == null)
            return left;
        if (left == null)
            return right;
        if (left.getSum() < right.getSum())
            return left;
        if (left.getSum() > right.getSum())
            return right;
        if (left.size() > right.size())
            return right;
        return left;
    }
}

Автор: Stolzen 13.8.2012, 10:28
korian, спасибо. А можно на словах описать, что тут происходит? 
У меня http://paste.ubuntu.com/1144429/ реализация для алгоритма выше получилась.

Автор: Akina 13.8.2012, 12:30
Цитата(Stolzen @  10.8.2012,  16:03 Найти цитируемый пост)
близость к сумме конечно приоритетнее, чем количество элементов. 

Всё равно не до конца понятно. Как они соотносятся? если нужна сумма 98, она не набирается, а набирается 99 из 11 элементов или 100 одним элементом - какой вариант выбрать?
Где математическое определение критерия оптимальности?

Автор: Stolzen 13.8.2012, 13:14
Цитата(Akina @  13.8.2012,  13:30 Найти цитируемый пост)
Где математическое определение критерия оптимальности? 

У меня нет такого, извините.

Цитата(Akina @  13.8.2012,  13:30 Найти цитируемый пост)
если нужна сумма 98, она не набирается, а набирается 99 из 11 элементов или 100 одним элементом - какой вариант выбрать?

Склоняюсь к 99.

Меня, по правде, больше интересовали подходы к решению подобных задач. Спасибо.

Автор: korian 13.8.2012, 17:11
Цитата(Stolzen @  13.8.2012,  09:28 Найти цитируемый пост)
 А можно на словах описать, что тут происходит? 

Я добавил комментарии.

Цитата(Stolzen @  13.8.2012,  09:28 Найти цитируемый пост)
У меня вот такая реализация для алгоритма выше получилась. 

Мне тяжело читать функциональные языки %)
Можете проверить ваш алгоритм на таких данных (я их использовал для своих тестов и это вроде как основные моменты, которые надо проверить):
Код

P = {50, 20, 30}
W = 50
Result = {50}

P = {10, 20}
W = 5
Result = {10}

P = {5, 10, 30}
W = 20
Result = {30}

P = {40, 50, 80}
W = 65
Result = {80}

P = {10}
W = 80
Result = null

P = {10, 20}
W = 40
Result = null

P = {10, 15, 20}
W = 30
Result = {10, 20}

P = {10, 20, 30, 41}
W = 50
Result = {20, 30}

P = {1, 2, 23, 30, 50}
W = 53
Result = {23, 30}


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

Автор: Akina 13.8.2012, 20:59
Цитата(Stolzen @  13.8.2012,  14:14 Найти цитируемый пост)
Меня, по правде, больше интересовали подходы к решению подобных задач. 

Ну здесь Вы собсно имеете в чистом виде задачу об одномерном раскрое. И решаете её соответственно. Либо какой-то точный переборный метод, скажем метод ветвей и границ, либо (если устраивает решение, близкое к оптимуму, а необязательно оптимум) генетика.

Цитата(Stolzen @  13.8.2012,  14:14 Найти цитируемый пост)
У меня нет такого, извините.

А вот это очень плохо. Вы обязаны иметь... ммм... ну, скажем, функцию, в которую Вы передаёте два решения, а она возвращает ответ, какое из них лучше. Иначе решать задачу не получится. Ощущения класса "склоняюсь к" не программируются.

Автор: Stolzen 14.8.2012, 11:28
Цитата(Akina @  13.8.2012,  21:59 Найти цитируемый пост)
Ну здесь Вы собсно имеете в чистом виде задачу об одномерном раскрое. И решаете её соответственно. Либо какой-то точный переборный метод, скажем метод ветвей и границ, либо (если устраивает решение, близкое к оптимуму, а необязательно оптимум) генетика.

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

Цитата(Akina @  13.8.2012,  21:59 Найти цитируемый пост)
А вот это очень плохо. Вы обязаны иметь... ммм... ну, скажем, функцию, в которую Вы передаёте два решения, а она возвращает ответ, какое из них лучше. Иначе решать задачу не получится. Ощущения класса "склоняюсь к" не программируются. 

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

Цитата(korian @  13.8.2012,  18:11 Найти цитируемый пост)
Можете проверить ваш алгоритм на таких данных (я их использовал для своих тестов и это вроде как основные моменты, которые надо проверить):

Спасибо, нашел пару опечаток и неточностей в коде. (http://paste.ubuntu.com/1146383/.)

Цитата(korian @  13.8.2012,  18:11 Найти цитируемый пост)
Я добавил комментарии.

Да, теперь немного понятнее. Получается, примерно такая же идея, как в http://forum.vingrad.ru/index.php?showtopic=355199&view=findpost&p=2510655, только алгоритм реализован эффективнее? У этого алгоритма есть какое-нибудь название?


Автор: Akina 14.8.2012, 12:05
Цитата(Stolzen @  14.8.2012,  12:28 Найти цитируемый пост)
Почитаю про одномерный раскрой 

Заодно почитай и задачу о рюкзаке (вариация - об одномерном рюкзаке). Она отличается от раскроя тем, что в ней есть относительные веса/стоимости, о чём обычно забывают, благополучно путая меж собой эти задачи.

Автор: korian 14.8.2012, 17:03
Цитата(Stolzen @  14.8.2012,  10:28 Найти цитируемый пост)
Да, теперь немного понятнее. Получается, примерно такая же идея, как в посте 2, только алгоритм реализован эффективнее?

В общем да. Эффективнее тем, что я на каждом шаге уменьшаю размер множества, если это возможно. Т.е. рассматриваю подмножество элементов, в котором только элементы, которые меньше W, плюс один наименьший элемент, который больше W. Ну и учитывая, что множество отсортированное, это стоит мне всего log(n) времени.
Плюс если я правильно понимаю, моему алгоритму надо меньше времени для определения, что сумма множества меньше W.

Цитата(Stolzen @  14.8.2012,  10:28 Найти цитируемый пост)
У этого алгоритма есть какое-нибудь название?

Без понятия, особо этим не интересовался. Написан за субботу от нечего делать.

Автор: Stolzen 14.8.2012, 17:14
korianAkina, спасибо вам за отклик, на пока тему можно считать закрытой. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)