Java 优化算法

5

我刚开始学习编程,目前遇到了一个优化算法的问题,我无法找到一个好的、高效的解决方案来满足我的预期。这是我的情况:

假设现在James有900块钱,在商店里有4种不同价格的物品。

A物品:450块钱

B物品:300块钱

C物品:350块钱

D物品:200块钱

*每个物品只有一件。

现在James需要最大化他手头的钱(900块钱)。换句话说,他可以购买任何物品,但剩余的钱必须尽可能少。在这种情况下,最佳结果将是:James购买B、C和D物品,他剩下的余额将是50块钱。

用口头语言很容易解释,但当涉及到编写这种情况的程序或算法时,就完全是另一回事了。

我曾试图编写逻辑:按价格从低到高排序,然后从最低价格的物品中扣除900块钱的余额,直到没有可以购买的物品,但我意识到这个逻辑无法实现最大化资金的使用。例如,当900块钱的金额变为800块钱时,最好的情况是购买450和350块钱的物品,剩余为零,但我的逻辑将购买300和200块钱的物品,因为早期的物品已被排序。

因此,我在这里提出这个问题,希望能找到处理这种情况的任何解决方案。我知道这可能是一个愚蠢的问题,但我会尽力学习和改善。

算法应具备以下功能:

  • 能够处理商店中可变数量的物品(不一定仅限于4件物品,可以多于或少于4件),以及可变的起始预算(不一定是900块钱,每次都可以更改)。
  • 每个产品只能购买一次。

*请提供参考资料供我学习你的解决方案。谢谢。


6
如果你知道这是计算机科学中最著名问题之一的变体,也许能帮助你研究算法:背包问题。 - Jordan
为什么不按从高到低的顺序排序? - jrook
2
@Jordan 背包有两个变量:在整数重量限制下最大化价值。我在这里看不到重量约束的等价物。 - jrook
3
假设买家手头有100美元,商品可分别以80、50和40美元的价格购买。如果按照从高到低排序,则会购买价值80美元的商品,并剩下20美元。但是最优解是购买价值50美元和40美元的两件商品,剩下未花费的10美元。因此,仅仅依靠从高到低排序并不能总是得到最优解。同样地,如果按照从低到高排序20、60和90美元的商品,也不是最优解。 - Jordan
2
这是子集和问题的优化形式。 - sascha
显示剩余2条评论
4个回答

2
对于那些关注这个问题的人,我已经找到了解决方案:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Map.Entry;
import java.util.LinkedHashMap;
import java.util.Iterator;

public class SumSet {
    static Map<Integer, ArrayList<Integer>> handleAllSumPossibilities(ArrayList<Integer> itemList, int balance, ArrayList<Integer> combination, Map<Integer, ArrayList<Integer>> qualifyItemsCombination) {

        System.out.println("COMBINATION FOR TEST: "+combination);

       int sum = 0; 
       Integer remain=null; 


       for (int x: combination){ sum += x;}; 

       if (sum <= balance && sum != 0){
            remain=(balance - sum);

            qualifyItemsCombination.put(remain,combination);
            System.out.println("ADD COMBINATION TO MAP: "+combination+"  CURRENT QUALIFIED COMBINATION: "+qualifyItemsCombination);
       }else{
            System.out.println("IGNORE COMBINATION: "+combination+"  NOT QUALIFY, THE COMBINATION IS EXCEEDED THE BALANCE");
       }
            System.out.println("_____________________________");


       for(int i=0;i<itemList.size();i++) {
             ArrayList<Integer> remainingItems = new ArrayList<Integer>();

             int pointingItem = itemList.get(i); 
             for (int j=i+1; j<itemList.size();j++) remainingItems.add(itemList.get(j));

             ArrayList<Integer> combinationRecord = new ArrayList<Integer>(combination);

             combinationRecord.add(pointingItem);

             Map<Integer, ArrayList<Integer>> retrievedItemsCombination = handleAllSumPossibilities( remainingItems, balance, combinationRecord, qualifyItemsCombination);
             qualifyItemsCombination = retrievedItemsCombination;

       }
            return qualifyItemsCombination;
    }



    static Map<Integer, ArrayList<Integer>> findBestCombination(ArrayList<Integer> itemList, int balance) {

        Map<Integer, ArrayList<Integer>> qualifyItemsCombination;
        qualifyItemsCombination = handleAllSumPossibilities(itemList,balance,new ArrayList<Integer>(),new HashMap<>());

        System.out.println("THE FINAL QUALIFIED COMBINATION: "+qualifyItemsCombination);

        //sort the key (remaining balance)
        List<Entry< Integer, ArrayList<Integer>>> qualifyItemsCombinationList = new ArrayList<>(qualifyItemsCombination.entrySet());
        qualifyItemsCombinationList.sort(Entry.comparingByKey());

        //place the sort result
        Map<Integer, ArrayList<Integer>> sortedResult = new LinkedHashMap<>();
        for (Entry<Integer, ArrayList<Integer>> entry : qualifyItemsCombinationList) {
            sortedResult.put(entry.getKey(), entry.getValue());
        }
        System.out.println("QUALIFIED COMBINATION AFTER SORTED: "+sortedResult);

        //iterate to get the first combination = the combination with lesser remaining.
        Map.Entry<Integer, ArrayList<Integer>> entry = sortedResult.entrySet().iterator().next();
        Integer getMapKey = entry.getKey();
        ArrayList<Integer> getMapValue=entry.getValue();

        //remove all the combination that contains the remaining(key)
        //different to the lesser remaining
        //the reason of doing this is to filter the combinations and ensure the map only left the combinations with the lesser remaining
        //since it might contains more than one combination are having the lesser remaining
        sortedResult.entrySet().removeIf(key -> key.getKey() != getMapKey);
        System.out.println("THE COMBINATION WITH LESSER BALANCE: "+sortedResult);

        return sortedResult;
    }



    public static void main(String args[]) {
        ArrayList<Integer> itemList = new ArrayList<>();
        itemList.add(450);
        itemList.add(350);
        itemList.add(300);
        itemList.add(200);

        int balance = 900;

        Map<Integer, ArrayList<Integer>> returnResult;
        returnResult = findBestCombination(itemList,balance);

        //Iterate to display all the combination with lesser balance remaining
        Iterator it = returnResult.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry)it.next();
            System.out.println("THE LESSER REMAINING: "+pair.getKey() + ", THE COMBINATION TO ACHIVE THIS: " + pair.getValue());   
            it.remove(); // avoid concurrent modification exception
        }
    }
}

*** 复制代码并在Java在线编译器上尝试:

https://www.jdoodle.com/online-java-compiler/

https://www.tutorialspoint.com/compile_java_online.php

请改进或纠正我的答案,如果您发现任何问题或更好的方法来最大化数据传递效率。谢谢。

该行代码 sortedResult.entrySet().removeIf(key -> key.getKey() != getMapKey);Integer 对象执行了引用比较,而不是比较它们的值。但这是一个奇怪的操作。在映射中只能有一个键的出现,因此这将删除除一个键之外的所有键。但是,在通过 qualifyItemsCombinationList.get(0) 排序后,最小的条目已经可用,无需将列表复制到 LinkedHashMap 中。实际上,即使排序也是过时的,Collections.min(qualifyItemsCombination, Entry.comparingByKey()) 已经给出了最小的条目... - Holger

1
你可以通过递归来解决这个任务。当你有一个方法能够为给定的预算选择最佳的物品组合时,请按照以下步骤实现:
遍历物品,对于每个物品,检查它是否在预算范围内,如果是,则将其从可用物品中删除,并从预算中减去其成本。然后,递归地询问剩余物品和剩余预算的最佳组合,并将其与当前物品组合。检查生成的组合是否比之前的组合更好,只保留最佳组合。
这可以通过使用按价格排序的物品列表进行优化,这样一旦所有剩余物品的成本都超过当前预算,就可以停止迭代。使用列表,可以通过索引表示剩余物品,无需创建新的集合。我们不需要考虑当前物品之前的物品,因为那会导致我们已经检查过的组合:
public static Set<String> select(Map<String,Integer> available, int budget) {
    List<Map.Entry<String,Integer>> temp = new ArrayList<>(available.entrySet());
    temp.sort(Map.Entry.comparingByValue());
    Choice c = selectImpl(temp, 0, budget);
    return c == null? Collections.emptySet(): c.items;
}
private static Choice selectImpl(
    List<Map.Entry<String, Integer>> availTemp, int start, int budget) {
    Choice c = null;
    for(int ix = start; ix < availTemp.size(); ix++) {
        Map.Entry<String, Integer> e = availTemp.get(ix);
        if(e.getValue() > budget) return c;
        Choice sub;
        int cost = e.getValue(), remaining = budget - cost;
        if(remaining == 0) return new Choice(e.getKey(), budget);
        sub = selectImpl(availTemp, ix + 1, remaining);
        if(c == null || c.cost < (sub == null? cost: sub.cost + cost))
            c = sub == null? new Choice(e.getKey(),e.getValue()): sub.add(e.getKey(),cost);
    }
    return c;
}
private static final class Choice {
    final Set<String> items;
    int cost;

    Choice(String key, int c) {
        items = new HashSet<>();
        items.add(key);
        cost = c;
    }
    Choice add(String key, int c) {
        items.add(key);
        cost += c;
        return this;
    }
}

这可以像这样使用

Map<String,Integer> available = Map.of(
    "Item A", 450, "item B", 300, "Item C", 350, "item D", 200);
int budget = 900;
Set<String> picked = select(available, budget);
System.out.println("With "+budget+ ", buy "+picked
    +", consuming "+picked.stream().mapToInt(available::get).sum());

这是一个有趣的解决方案,但更有趣的是它带给我多少回忆,当年在我的家乡举办计算机奥林匹克比赛时也是如此。令人惊叹。 - Eugene

0

你可以通过先计算所有可能性,然后再进行排序来解决这个问题。如果你能发现一个有趣的规律,找到所有可能性并不那么复杂。让我们举个例子:

   a
=======
  [a]

如果您只有一个输入 a,唯一可能的结果是 [a]
     a, b
==============
 [a]  [a, b]
 [b]

如果您的输入为a,b,则有3种可能的结果:[a],[b],[a,b]
这就是我们发现规则1的地方:单个元素([a])的可能性是a,b中可能性的一个子集。或者概括一下:

所有先前结果的可能性都将包含在当前结果中。

让我们看看对于a,b,c是否成立:
     a, b                              a, b, c
==================         =============================
  --------------              -------------
 | [a]  [a, b]  |            | [a]  [a, b] |
 | [b]          |            | [b]         |
 |--------------|            |-------------|  [a, b, c]
                               [c]  [a, c]
                                    [b, c] 

你可以验证更大的输入,但它总是成立的。如果你从更广阔的角度来看待这个问题,这一切都是有道理的。潜在的n的所有组合将是:潜在的n-1的所有组合加上一些额外的组合。


但是这里还有另一个更有趣的规则。

如果您有这样的输入:

     a, b
==============
 [a]  [a, b]
 [b]

你可以创建一个算法来计算下一个。首先创建前一个的副本(因为上面的规则):

    a, b, c
==============
 [a]  [a, b]
 [b]

对于第一行,你只需要添加“last”后面的字母。由于下一个是 a, b, c,所以你只需要在第一行中添加:c
    a, b, c
==============
 [a]  [a, b]
 [b]
 [c]

对于第二行,你需要使用前一行(减去添加的 c),并添加“下一个”字母,然后插入它。也就是说:
      a, b, c
===================
 [a] <-|    [a, b]
 [b]   |->  [a, c]  <-- "a" came from the previous row, "c" was added as the last one
 [c]   

对于一些b:


      a, b, c
===================
 [a]        [a, b]
 [b] <-|    [a, c]  
 [c]   |->  [b, c] <-- "b" came from the previous row, "c" then added as the last one

对于最后一行,我们只需将所有的“下一个”(a、b、c)相加:

            a, b, c
=================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]

使用上述相同的逻辑,您可以计算出a、b、c、d的结果:

首先复制:

            a, b, c, d
=================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]

添加 d

            a, b, c, d
=================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]

 [d]

取上一行并追加:

            a, b, c, d
=================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]

 [d]        [a, d]
            [b, d]
            [c, d]

再次,取出前一行并附加(记住:“除了已添加的行”):

                a, b, c, d
=================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]     [a, b, d]  <--- [a, b] + [d]
 [d]        [a, d]     [a, c, d]  <--- [a, c] + [d]
            [b, d]     [b, c, d]  <--- [b, c] + [d]
            [c, d] 

最后一个:

                    a, b, c, d
=================================================
 [a]        [a, b]
 [b]        [a, c]     [a, b, c]
 [c]        [b, c]     [a, b, d]  
 [d]        [a, d]     [a, c, d]   [a, b, c, d]
            [b, d]     [b, c, d] 
            [c, d] 

如果你理解上面发生的事情,那就是关于编写代码的一切。我所做的唯一更改是不添加我已经知道会未通过与max检查的元素。这就是它,看起来很复杂,但实际上它相当简单(除了一些边缘情况的例外):

static List<String> partitions(List<Choice> all, int max) {

    ArrayList<ArrayList<ArrayList<Choice>>> previousResult = new ArrayList<>();
    ArrayList<ArrayList<ArrayList<Choice>>> currentResult = new ArrayList<>();

    int i = 0;
    for(;i<all.size();++i) {
        // add the first element
        Choice current = all.get(i);
        if(currentResult.isEmpty()) {
            if(less(List.of(current), max)) {
                // looks complicated, but all it does is adds a single element in the first index
                ArrayList<Choice> inner = new ArrayList<>();
                inner.add(current);
                ArrayList<ArrayList<Choice>> in = new ArrayList<>();
                in.add(inner);
                currentResult.add(in);
                previousResult.add(in);
            }
        } else {
            if(less(List.of(current), max)) {
                ArrayList<Choice> element = new ArrayList<>();
                element.add(current);
                currentResult.get(0).add(element);
            }
            if(currentResult.size() > 1) {
                for(int j=0;j<i-1;++j) {
                    if(j < previousResult.size()) {
                        ArrayList<ArrayList<Choice>> p = previousResult.get(j);
                        for(int d=0;d<=p.size()-1;++d){
                            ArrayList<Choice> copy = new ArrayList<>(p.get(d));
                            copy.add(all.get(i));
                            if(less(copy, max)){
                                currentResult.get(j).add(copy);
                            }
                        }
                    }

                }
            }

            // add tail if possible
            ArrayList<ArrayList<Choice>> tail = new ArrayList<>();
            ArrayList<Choice> t = new ArrayList<>(all.subList(0, i + 1));
            if(less(t, max)) {
                tail.add(t);
                currentResult.add(tail);
            }

            if(currentResult.size() == 1) {
                ArrayList<Choice> l = currentResult.get(0).stream().flatMap(List::stream).collect(Collectors.toCollection(ArrayList::new));
                if(less(l, max)) {
                    tail.add(l);
                    currentResult.add(tail);
                }
            }

            // smart copy here
            previousResult = copy(previousResult, currentResult);
        }

    }

    return
        currentResult.stream()
                     .flatMap(List::stream)
                     .map(list -> {
                            int sum = list.stream().mapToInt(Choice::getCost).sum();
                            List<String> l = list.stream().map(Choice::getKey).collect(Collectors.toList());
                            return new AbstractMap.SimpleEntry<>(sum, l);
                     })
                     .sorted(Map.Entry.<Integer, List<String>>comparingByKey().reversed())
                     .filter(x -> x.getKey() <= max)
                     .map(Map.Entry::getValue)
                     .findFirst()
                     .orElse(List.of());
}

private static ArrayList<ArrayList<ArrayList<Choice>>> copy(ArrayList<ArrayList<ArrayList<Choice>>> previousResult, ArrayList<ArrayList<ArrayList<Choice>>> currentResult) {
    return currentResult.stream()
                 .map(x -> x.stream().map(y -> (ArrayList<Choice>)y.clone()).collect(Collectors.toCollection(ArrayList::new)))
                 .collect(Collectors.toCollection(ArrayList::new));

}

private static boolean less(List<Choice> in, int max) {
    return in.stream().mapToInt(Choice::getCost).sum() <= max;
}

0

解决方案是建立一个字典,包含您可能花费的所有总金额以及使您达到该金额的最后一笔购买。然后找到最大的金额,并向后遍历字典以查找您购买的物品清单。

以下是使用Python实现此操作的解决方案:

def optimal_buy (money, item_price):
    best = 0
    last_buy = {0: None}

    for item, price in item_price.iteritems():
        # Make a copy before altering the dictionary.
        prev_spent = [k for k in last_buy.keys()]
        for spent in prev_spent:
            next_spent = spent + price
            if next_spent <= money and next_spent not in last_buy:
                last_buy[next_spent] = item
                if best < next_spent:
                    best = next_spent

    answer = []
    while 0 < best:
        item = last_buy[best]
        answer.append(item)
        best = best - item_price[item]

    return sorted(answer)


print(optimal_buy(900, {'A': 450, 'B': 300, 'C': 350, 'D': 200}))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接