背包问题:如何将物品类型添加到现有解决方案中

10

我一直在使用这个变式的动态规划算法来解决背包问题:

KnapsackItem = Struct.new(:name, :cost, :value)
KnapsackProblem = Struct.new(:items, :max_cost)


def dynamic_programming_knapsack(problem)
  num_items = problem.items.size
  items = problem.items
  max_cost = problem.max_cost

  cost_matrix = zeros(num_items, max_cost+1)

  num_items.times do |i|
    (max_cost + 1).times do |j|
      if(items[i].cost > j)
        cost_matrix[i][j] = cost_matrix[i-1][j]
      else
        cost_matrix[i][j] = [cost_matrix[i-1][j], items[i].value + cost_matrix[i-1][j-items[i].cost]].max
      end
    end
  end

  cost_matrix
end

def get_used_items(problem, cost_matrix)
  i = cost_matrix.size - 1
  currentCost = cost_matrix[0].size - 1
  marked = Array.new(cost_matrix.size, 0) 

  while(i >= 0 && currentCost >= 0)
    if(i == 0 && cost_matrix[i][currentCost] > 0 ) || (cost_matrix[i][currentCost] != cost_matrix[i-1][currentCost])
      marked[i] = 1
      currentCost -= problem.items[i].cost
    end
    i -= 1
  end
  marked
end

这对于上述结构非常有效,您只需提供名称、成本和价值即可创建项目。可以按照以下方式创建项目:

 items = [
      KnapsackItem.new('david lee', 8000, 30) , 
      KnapsackItem.new('kevin love', 12000, 50), 
      KnapsackItem.new('kemba walker', 7300, 10),
      KnapsackItem.new('jrue holiday', 12300, 30),
      KnapsackItem.new('stephen curry', 10300, 80),
      KnapsackItem.new('lebron james', 5300, 90),
      KnapsackItem.new('kevin durant', 2300, 30),
      KnapsackItem.new('russell westbrook', 9300, 30),
      KnapsackItem.new('kevin martin', 8300, 15),
      KnapsackItem.new('steve nash', 4300, 15),
      KnapsackItem.new('kyle lowry', 6300, 20),
      KnapsackItem.new('monta ellis', 8300, 30),
      KnapsackItem.new('dirk nowitzki', 7300, 25),
      KnapsackItem.new('david lee', 9500, 35),
      KnapsackItem.new('klay thompson', 6800, 28)
    ]

  problem = KnapsackProblem.new(items, 65000)

现在,我的问题是我需要为每个玩家添加一个位置,并告诉背包算法仍然需要在所有玩家中最大化价值,除了一个新的限制,即每个玩家都有一个位置,每个位置只能被选择一定次数。有些位置可以被选择两次,有些只能选择一次。物品理想情况下将变成这样:

KnapsackItem = Struct.new(:name, :cost, :position, :value)

职位将受到以下限制:

PositionLimits = Struct.new(:position, :max)

极限可能会像以下这样实例化:

limits = [Struct.new('PG', 2), Struct.new('C', 1), Struct.new('SF', 2), Struct.new('PF', 2), Struct.new('Util', 2)]

这让问题有些棘手的是每个球员都可以处于“万能”(Util)位置。如果我们想禁用“万能”位置,只需将2设置为0即可。

我们原始的物品数组看起来可能像以下这样:

items = [
          KnapsackItem.new('david lee', 'PF', 8000, 30) , 
          KnapsackItem.new('kevin love', 'C', 12000, 50), 
          KnapsackItem.new('kemba walker', 'PG', 7300, 10),
          ... etc ...
        ]

如何在背包算法中添加位置限制以便仍然保留提供的球员池的最大价值?


2
如果您对Ruby实现不是太具体,那么用简单的非代码语言描述问题会更容易理解。 - Abhishek Bansal
我需要一些帮助来在Ruby中实现这个,所以我在问题上添加了赏金。 - randombits
不幸的是,它似乎包含了这个额外的限制条件,你可能需要通过一些暴力方法或整数线性规划求解来解决。你是否愿意尝试这些方法,还是你特别想要一个动态规划的解决方案? - Abhishek Bansal
我真的很开放 -- 需要通过背包运行的物品数量永远不会超过50-60个,所以只要最终结果准确,就不太担心计算时间。 - randombits
你能告诉我我的解决方案有什么问题吗?我已经给出了一个可以使用DP解决的递归方程,就像以前一样。我是否误解了问题? - Vikram Bhat
4个回答

4
有一些高效的Ruby库可以适用于您的任务,很明显您正在寻找一些约束优化,Ruby中有一些开源库可供使用,只需将它们包含在项目中。您需要做的就是从约束中生成线性规划模型的目标函数,库的优化器将生成满足所有约束的解决方案,或者如果给定的约束没有结论,则表明不存在解决方案。
Ruby中可用的这些库包括: OPL遵循与广泛使用的优化软件IBM CPLEX相似的LP语法,因此您可以获得有关如何使用此方法对LP进行建模的良好参考。此外,此库也基于RGLPK构建。

你能否分享一下如何将我的变量插入到这些 gem 中?我不太确定从哪里开始。 - randombits

2
据我所知,您正在指定的额外约束条件如下:

应该有一组元素,其中在解决方案中只能选择至多k(k = 1或2)个元素。应该有多个这样的集合。

我想到了两种方法,但都不够高效。
方法1:
  1. 将元素分成位置组。因此,如果有5个位置,则每个元素应分配给其中一个位置组。

  2. 通过从每个组中选择1个(或2个)元素并检查总值和成本来迭代(或递归)所有组合。有一些方法可以理解一些组合。例如,在一个组中,如果有两个元素,其中一个以较小的成本提供更多的价值,则其他元素可以从所有解决方案中拒绝。

方法2:
Mixed Integer Linear Programming Approach.

将问题表述如下:

Maximize summation (ViXi) {i = 1 to N} 
where Vi is value and 
Xi is a 1/0 variable denoting presence/absence of an element from the solution.

Subject to constraints:
summation (ciXi) <= C_MAX {total cost}
And for each group:
summation (Xj) <= 1 (or 2 depending on position)
All Xi = 0 or 1.

然后您需要找到一个求解器来解决上述的MILP问题。


1
这个问题类似于约束车辆路径问题。您可以尝试像Clarke&Wright的节省算法这样的启发式算法。您也可以尝试使用更少玩家的暴力算法。

0

考虑到玩家有五个位置,您的背包问题将是:

   Knpsk(W,N,PG,C,SF,PF,Util) = max(Knpsk(W-Cost[N],N-1,...)+Value[N],Knpsk(W,N-1,PG,C,SF,PF,Util),Knpsk(W-Cost[N],N-1,PG,C,SF,PF,Util-1)+Value[N])

    if(Pos[N]=="PG") then Knpsk(W-Cost[N],N-1,....) = Knpsk(W-Cost[N],N-1,PG-1,....)

    if(Pos[N]=="C") then Knpsk(W-Cost[N],N-1,....) = Knpsk(W-Cost[N],N-1,PG,C-1....)

    so on...

  PG,C,SF,PF,Util are current position capacities 
  W is current knapsack capacity
  N number of items available

动态规划可以像以前一样使用7-D表,而且在您的情况下,位置的值很小,这将使算法减速16倍,这对于n-p完全问题非常好。

以下是JAVA中的动态规划解决方案:

public class KnapsackSolver {

    HashMap CostMatrix;
    // Maximum capacities for positions
    int posCapacity[] = {2,1,2,2,2};
    // Total positions 
    String[] positions = {"PG","C","SF","PF","util"}; 
    ArrayList playerSet = new ArrayList<player>();  
    public ArrayList solutionSet;
    public int bestCost;


    class player {

        int value;
        int cost;
        int pos;
        String name;

        public player(int value,int cost,int pos,String name) {
            this.value = value;
            this.cost = cost;
            this.pos = pos;
            this.name = name;
        }

        public String toString() {
            return("'"+name+"'"+", "+value+", "+cost+", "+positions[pos]);
        }

    }

    // Used to add player to list of available players
    void  additem(String name,int cost,int value,String pos) {
        int i;
        for(i=0;i<positions.length;i++) {
            if(pos.equals(positions[i]))
                break;
        }
        playerSet.add(new player(value,cost,i,name));
    }

    // Converts subproblem data to string for hashing
    public String encode(int Capacity,int Totalitems,int[] positions) {
        String Data = Capacity+","+Totalitems;
        for(int i=0;i<positions.length;i++) {
            Data = Data + "," + positions[i];
        }
        return(Data);
    }

    // Check if subproblem is in hash tables
    int isDone(int capacity,int players,int[] positions) {

        String k = encode(capacity,players,positions);

        if(CostMatrix.containsKey(k)) {
            //System.out.println("Key found: "+k+" "+(Integer)CostMatrix.get(k));
            return((Integer)CostMatrix.get(k));
        }

        return(-1);
    }

    // Adds subproblem added hash table
    void addEncode(int capacity,int players,int[] positions,int value) {

        String k = encode(capacity,players,positions);
        CostMatrix.put(k, value);
    }

    boolean checkvalid(int capacity,int players) {

        return(!(capacity<1||players<0));
    }

    // Solve the Knapsack recursively with Hash look up
    int solve(int capacity,int players,int[] posCapacity) {

        // Check if sub problem is valid

        if(checkvalid(capacity,players)) {

            //System.out.println("Processing: "+encode(capacity,players,posCapacity));

            player current = (player)playerSet.get(players);

            int sum1 = 0,sum2 = 0,sum3 = 0;

            int temp = isDone(capacity,players-1,posCapacity);

            // Donot add player

            if(temp>-1) {

                sum1 = temp;
            }
            else sum1 = solve(capacity,players-1,posCapacity);

            //check if current player can be added to knapsack

            if(capacity>=current.cost) {

                posCapacity[posCapacity.length-1]--;

                temp = isDone(capacity-current.cost,players-1,posCapacity);

                posCapacity[posCapacity.length-1]++;

                // Add player to util

                if(posCapacity[posCapacity.length-1]>0) {

                    if(temp>-1) {

                        sum2 = temp+current.value;
                    }
                    else {

                        posCapacity[posCapacity.length-1]--;
                        sum2 = solve(capacity-current.cost,players-1,posCapacity)+current.value;
                        posCapacity[posCapacity.length-1]++;

                    }

                }

                // Add player at its position

                int i = current.pos;

                    if(posCapacity[i]>0) {

                        posCapacity[i]--;
                        temp  = isDone(capacity-current.cost,players-1,posCapacity);
                        posCapacity[i]++;
                        if(temp>-1) {

                            sum3 = temp+current.value;
                        }
                        else {

                            posCapacity[i]--;
                            sum3 = solve(capacity-current.cost,players-1,posCapacity)+current.value;
                            posCapacity[i]++;

                        }
                    }
                }   

            //System.out.println(sum1+ " "+ sum2+ " " + sum3 );


            // Evaluate the maximum of all subproblem   
            int res = Math.max(Math.max(sum1,sum2), sum3);

            //add current solution to Hash table
            addEncode(capacity, players, posCapacity,res);
            //System.out.println("Encoding: "+encode(capacity,players,posCapacity)+" Cost: "+res);

            return(res);


        }
        return(0);
    }

    void getSolution(int capacity,int players,int[] posCapacity) {


        if(players>=0) {
           player curr = (player)playerSet.get(players);
           int bestcost = isDone(capacity,players,posCapacity);
           int sum1 = 0,sum2 = 0,sum3 = 0;
           //System.out.println(encode(capacity,players-1,posCapacity)+" "+bestcost);
           sum1 = isDone(capacity,players-1,posCapacity);
           posCapacity[posCapacity.length-1]--;
           sum2 = isDone(capacity-curr.cost,players-1,posCapacity) + curr.value;
           posCapacity[posCapacity.length-1]++;
           posCapacity[curr.pos]--;        
           sum3 = isDone(capacity-curr.cost,players-1,posCapacity) + curr.value;
           posCapacity[curr.pos]++;

           if(bestcost==0)
               return;

           // Check if player is not added
           if(sum1==bestcost) {
               getSolution(capacity,players-1,posCapacity);
           }
           // Check if player is added to util
           else if(sum2==bestcost) {
               solutionSet.add(curr);
               //System.out.println(positions[posCapacity.length-1]+" added");
               posCapacity[posCapacity.length-1]--;
               getSolution(capacity-curr.cost,players-1,posCapacity);
               posCapacity[posCapacity.length-1]++;

           }
           else {
               solutionSet.add(curr);
               //System.out.println(positions[curr.pos]+" added");
               posCapacity[curr.pos]--;
               getSolution(capacity-curr.cost,players-1,posCapacity);
               posCapacity[curr.pos]++;     


           }   
        }


   }



    void getOptSet(int capacity) {

        CostMatrix = new HashMap<String,Integer>();
        bestCost = solve(capacity,playerSet.size()-1,posCapacity);
        solutionSet = new ArrayList<player>();
        getSolution(capacity, playerSet.size()-1, posCapacity);

    }



    public static void main(String[] args) {

        KnapsackSolver ks = new KnapsackSolver();
        ks.additem("david lee", 8000, 30, "PG"); 
        ks.additem("kevin love", 12000, 50, "C"); 
        ks.additem("kemba walker", 7300, 10, "SF");
        ks.additem("jrue holiday", 12300, 30, "PF");
        ks.additem("stephen curry", 10300, 80, "PG");
        ks.additem("lebron james", 5300, 90, "PG");
        ks.additem("kevin durant", 2300, 30, "C");
        ks.additem("russell westbrook", 9300, 30, "SF");
        ks.additem("kevin martin", 8300, 15, "PF");
        ks.additem("steve nash", 4300, 15, "C");
        ks.additem("kyle lowry", 6300, 20, "PG");
        ks.additem("monta ellis", 8300, 30, "C");
        ks.additem("dirk nowitzki", 7300, 25, "SF");
        ks.additem("david lee", 9500, 35, "PF");
        ks.additem("klay thompson", 6800, 28,"PG");
        //System.out.println("Items added...");
       // System.out.println(ks.playerSet);
        int maxCost = 30000;
        ks.getOptSet(maxCost);
        System.out.println("Best Value: "+ks.bestCost);
        System.out.println("Solution Set: "+ks.solutionSet);
    }




}

注意: 如果超出了特定位置的球员数量,那么添加到 util 中,因为可以将来自任何位置的球员添加到 util 中。


在我试图分解你的解决方案并弄清如何将其嵌入到我的上面的代码之前,我想澄清一下,W和N是你为了这个例子而创造的额外位置吗? - randombits
在您的情况下,W 是背包容量,即最大成本,N 是球员数量。 - Vikram Bhat
Vikram - 我运行了你的代码,有两个评论。1)在getSolution中,你赋值给sum3,但从未使用其值。这是有意为之吗?2)我不认为你的结果是OP所寻找的。假设你有位置C、G、F和U(U是C、G和F的通配符)。假设你需要1C、2G、2F和1U。你的解决方案似乎解决了每个位置的最大值,而我猜测OP(和我自己)想要一个恰好从每个位置获得那个数字的解决方案。本质上,你想组建一个最佳(权重)团队,在maxCost下适合。 - FuriousGeorge
@user1146334 是的,我们不需要sum3,因为显然如果玩家没有被添加到util中,它会被添加到它的位置(忘记将其删除)。 - Vikram Bhat
@user1146334 我在发布这个答案后也有同样的疑问,所以向 OP 询问了一下,但他没有回复(请查看我上面的评论)。如果是这种情况,那么这根本不是背包问题,但是可以通过相对简单的遗传算法实现找到好的解决方案。我认为这些限制条件会使问题变得更加困难。 - Vikram Bhat

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