【问题标题】:Java Optimization AlgorithmJava 优化算法
【发布时间】:2020-02-09 03:00:22
【问题描述】:

我是编程新手,目前我在构建优化算法时遇到问题,我无法找到一个好的有效解决方案来实现我对当前面临的场景的预期。是这样的:

假设现在 James 有 900 美元,并且一家商店有 4 件不同价格的商品。

项目 A:450 美元

B 项:300 美元

项目 C:350 美元

项目 D:200 美元

*每件商品的库存数量只有一件。

现在詹姆斯需要最大限度地利用他现有的钱(900 美元)。换句话说,他可以购买任何物品,但剩余的钱需要尽可能少。在这种情况下,最好的结果是:James 带来了 B、C 和 D 项,他的余额为 50 美元。

用文字很容易解释,但是当为这种情况编写程序或编写算法时,情况就完全不同了。

我曾尝试写逻辑:将商品价格从低到高排序,然后从最低价格的商品中扣除货币余额900美元,直到没有余额可以购买的商品,但是我意识到这种逻辑并不能实现金钱的最大化利用。例如,当 900 美元的金额变为 800 美元时,最好的情况是购买 450 美元和 350 美元的商品,剩余将为零,但我的逻辑将购买 300 美元和 200 美元的商品,因为商品排序较早.

因此,我在这里提出这个问题,以找出处理这种情况的任何解决方案。我知道这可能是个愚蠢的问题,但我真的在努力学习和提高。

算法应该:

  • 能够灵活处理店内商品数量(不一定只有4件,可以多于或少于4件)和可变的起始预算(不需要900美元,可以随时更改)。
  • 每件产品只能购买一次。

*请提供参考,让我从您的解决方案中学习。谢谢。

【问题讨论】:

  • 如果您知道这是计算机科学中最著名的问题之一的变体:knapsack problem,它可能会帮助您研究算法。
  • 为什么不从高到低排序呢?
  • @Jordan Knapsack 有两个变量:使用整数权重约束最大化值。我在这里看不到等效的重量限制。
  • @jrook 想象这样一个情况,买家有 100 美元,商品的价格分别为 80 美元、50 美元和 40 美元。从高到低排序会导致购买 80 美元的商品并剩下 20 美元。但最佳解决方案是同时购买 50 美元和 40 美元的物品,剩余 10 美元未花费。因此,从高到低排序并不总是产生最佳解决方案。同样,尝试用 20 美元、60 美元和 90 美元从低到高排序,你会发现它也不是最优的。
  • 这是子集和问题的优化形式。

标签: java algorithm optimization knapsack-problem


【解决方案1】:

对于那些关注这个问题的人,我已经找到了这个问题的解决方案:

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 -&gt; key.getKey() != getMapKey); 正在对Integer 对象执行引用比较,而不是比较它们的值。但无论如何,这是一个奇怪的操作。映射中的键只能出现一次,因此这会删除除一个键之外的所有键。但是最小的条目在通过qualifyItemsCombinationList.get(0) 排序后已经可用,无需将列表复制到LinkedHashMap。事实上,即使排序已经过时,Collections.min(qualifyItemsCombination, Entry.comparingByKey()) 确实已经给出了最小的条目......
【解决方案2】:

您可以通过递归解决任务。当您有一种方法可以为给定预算选择最佳项目组合时,请按如下方式实施:

遍历项目并为每个项目检查它是否在预算范围内,如果是,则将其从可用项目中删除并从预算中减去其成本。然后,递归地询问剩余项目与剩余预算的最佳组合,并将其与您当前的项目组合。检查得到的组合是否比前一种更好,只保留最好的。

这可以通过使用按价格排序的项目列表来优化,一旦所有剩余项目的成本高于我们当前的预算,就可以停止迭代。使用列表,剩余的项目可以通过索引来表示,而不需要创建新的集合。我们不需要考虑当前项目之前的项目,因为这会导致我们之前已经检查过的组合:

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());

【讨论】:

  • 这是一个有趣的解决方案,但更有趣的是这给我带来了多少关于计算机奥运会在家乡举办的回忆。令人震惊。
【解决方案3】:

解决方案是建立一个字典,其中包含您可以花费的所有总金额,以及最后一次购买是什么让您到达那里。然后取出您找到的最大金额,然后将字典往回走,找到您购买的商品列表。

这是一个执行此操作的 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}))

【讨论】:

    【解决方案4】:

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

       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]
    

    对于第一行,您只需添加“最后一个”下一个字母。由于下一个是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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-11
      • 1970-01-01
      • 1970-01-01
      • 2016-07-30
      • 2023-02-03
      • 1970-01-01
      相关资源
      最近更新 更多