【问题标题】:0-1 Knapsack algorithm0-1 背包算法
【发布时间】:2011-12-28 20:18:04
【问题描述】:

以下0-1背包问题是否可解:

  • 'float' 正值和
  • 'float' 权重(可以是正数或负数)
  • 'float' 背包容量 > 0

我平均有

【问题讨论】:

  • 你的意思是solvable,还是solvableefficient
  • 如果您不需要确切的答案,您可以研究模拟退火..
  • 2^10 是 1024。绝对是蛮力的,即使有“更好”的方法,它几乎肯定会慢得多。
  • 那么,“正值”和“权重”有什么区别?你想打的是什么?
  • @McKay:每个项目都有一个值和一个重量。我们希望最大化这些值的总和,使得权重的总和为

标签: c# algorithm dynamic-programming knapsack-problem


【解决方案1】:

这是一个比较简单的二进制程序。

我建议用蛮力修剪。如果您在任何时候超过了允许的重量,您不需要尝试其他项目的组合,您可以丢弃整棵树。

哦等等,你有权重吗?始终包括所有负权重,然后按上述方法处理正权重。还是负重的物品也有负值?

包括所有具有正值的负重量项目。排除所有权重为正、值为负的项目。

对于具有负值的负重量物品,减去它们的重量(增加背包容量)并使用表示拿走该物品的伪物品。伪项目将具有正的权重和价值。用蛮力进行修剪。

class Knapsack
{
    double bestValue;
    bool[] bestItems;
    double[] itemValues;
    double[] itemWeights;
    double weightLimit;

    void SolveRecursive( bool[] chosen, int depth, double currentWeight, double currentValue, double remainingValue )
    {
        if (currentWeight > weightLimit) return;
        if (currentValue + remainingValue < bestValue) return;
        if (depth == chosen.Length) {
            bestValue = currentValue;
            System.Array.Copy(chosen, bestItems, chosen.Length);
            return;
        }
        remainingValue -= itemValues[depth];
        chosen[depth] = false;
        SolveRecursive(chosen, depth+1, currentWeight, currentValue, remainingValue);
        chosen[depth] = true;
        currentWeight += itemWeights[depth];
        currentValue += itemValues[depth];
        SolveRecursive(chosen, depth+1, currentWeight, currentValue, remainingValue);
    }

    public bool[] Solve()
    {
        var chosen = new bool[itemWeights.Length];
        bestItems = new bool[itemWeights.Length];
        bestValue = 0.0;
        double totalValue = 0.0;
        foreach (var v in itemValues) totalValue += v;
        SolveRecursive(chosen, 0, 0.0, 0.0, totalValue);
        return bestItems;
    }
}

【讨论】:

  • 我认为修剪可能是个坏主意?它会增加开销(检查时),并且不会节省太多时间,但它确实取决于溢出的可能性,特别是如果您还必须根据正负重量将物品放入垃圾箱中。
  • 非常感谢您!如果要满足附加约束,算法将如何改变:将选择限制为最多 5 个项目总数?再次感谢。
  • @j_random_hacker:再检查一遍,没有减法。先前的值从调用堆栈中恢复(调用者有一个未修改的副本)。
  • @alhazen,这只是第二种“重量”。添加currentCount 参数和countLimit 字段。
  • @BenVoigt 所以我更新了我的代码,现在它实际上正在解决这个版本的背包。而且我得到的值比你“更好”,也就是说,当我们在同一个集合上运行时,我在我的麻袋里装了比你更高值的东西。也许我做错了什么?也许你是?小心看我的。它应该相当简单。你的将进一步解构以确保它覆盖所有路径。
【解决方案2】:

是的,暴力破解。这是一个 NP 完全问题,但这无关紧要,因为您将拥有少于 10 个项目。暴力破解不会有问题。

        var size = 10;
        var capacity = 0;
        var permutations = 1024;
        var repeat = 10000;

        // Generate items
        float[] items = new float[size];
        float[] weights = new float[size];
        Random rand = new Random();
        for (int i = 0; i < size; i++)
        {
            items[i] = (float)rand.NextDouble();
            weights[i] = (float)rand.NextDouble();
            if (rand.Next(2) == 1)
            {
                weights[i] *= -1;
            }
        }

        // solution
        int bestPosition= -1;

        Stopwatch sw = new Stopwatch();            
        sw.Start();

        // for perf testing
        //for (int r = 0; r < repeat; r++)
        {
            var bestValue = 0d;

            // solve
            for (int i = 0; i < permutations; i++)
            {
                var total = 0d;
                var weight = 0d;
                for (int j = 0; j < size; j++)
                {
                    if (((i >> j) & 1) == 1)
                    {
                        total += items[j];
                        weight += weights[j];
                    }
                }

                if (weight <= capacity && total > bestValue)
                {
                    bestPosition = i;
                    bestValue = total;
                }
            }
        }
        sw.Stop();
        sw.Elapsed.ToString();

【讨论】:

  • 谢谢。但我认为您的代码返回满足约束的第一个有效组合,而不是总值尽可能大的组合(当然满足约束)。
  • 这是一种修剪形式。但是,如果您想编写更优化的解决方案。随意。这段代码在我的盒子上运行需要 650 微秒。在没有进一步优化的情况下运行它似乎不是问题。
  • 既然你已经有了测试框架,你能不能对我的递归解决方案进行性能测试?
  • 这段代码恐怕不能解决背包问题:-1。此外,使用按位运算比生成二进制数字字符串更简单、更快捷。在 C 语言中,您可以将 if (splitOut[j] == '1') 替换为 if ((i &gt;&gt; j) &amp; 1),我确信在 C# 中的语法是相似的。
  • 感谢麦凯的更新。这是一个很好且简单的实现,适用于负权和正权重。干杯!
【解决方案3】:

如果你只能有正值,那么每一个负重量的项目都必须进入。

然后我猜你可以计算价值/重量比,并根据该顺序蛮力剩余的组合,一旦你得到一个适合你可以跳过其余的。

问题可能在于分级和排序实际上比只进行所有计算更昂贵。

显然,根据集合的大小和分布,会有不同的盈亏平衡点。

【讨论】:

    【解决方案4】:
    public class KnapSackSolver {
    
    public static void main(String[] args) {
        int N = Integer.parseInt(args[0]); // number of items
        int W = Integer.parseInt(args[1]); // maximum weight of knapsack
    
        int[] profit = new int[N + 1];
        int[] weight = new int[N + 1];
    
        // generate random instance, items 1..N
        for (int n = 1; n <= N; n++) {
            profit[n] = (int) (Math.random() * 1000);
            weight[n] = (int) (Math.random() * W);
        }
    
        // opt[n][w] = max profit of packing items 1..n with weight limit w
        // sol[n][w] = does opt solution to pack items 1..n with weight limit w
        // include item n?
        int[][] opt = new int[N + 1][W + 1];
        boolean[][] sol = new boolean[N + 1][W + 1];
    
        for (int n = 1; n <= N; n++) {
            for (int w = 1; w <= W; w++) {
    
                // don't take item n
                int option1 = opt[n - 1][w];
    
                // take item n
                int option2 = Integer.MIN_VALUE;
                if (weight[n] <= w)
                    option2 = profit[n] + opt[n - 1][w - weight[n]];
    
                // select better of two options
                opt[n][w] = Math.max(option1, option2);
                sol[n][w] = (option2 > option1);
            }
        }
    
        // determine which items to take
        boolean[] take = new boolean[N + 1];
        for (int n = N, w = W; n > 0; n--) {
            if (sol[n][w]) {
                take[n] = true;
                w = w - weight[n];
            } else {
                take[n] = false;
            }
        }
    
        // print results
        System.out.println("item" + "\t" + "profit" + "\t" + "weight" + "\t"
                + "take");
        for (int n = 1; n <= N; n++) {
            System.out.println(n + "\t" + profit[n] + "\t" + weight[n] + "\t"
                    + take[n]);
        }
    }
    
    }
    

    【讨论】:

    • 时间复杂度是O(NW),空间复杂度也是O(NW)
    【解决方案5】:
    import java.util.*;
    class Main{
        static int max(inta,int b)
        {
          if(a>b)
            return a;
          else
            return b;
        }
        public static void main(String args[])
        {
          int n,i,cap,j,t=2,w;
          Scanner sc=new Scanner(System.in);
          System.out.println("Enter the number of values  ");
          n=sc.nextInt();
          int solution[]=new int[n];
          System.out.println("Enter the capacity of the knapsack :- ");
          cap=sc.nextInt();
          int v[]=new int[n+1];
          int wt[]=new int[n+1];
          System.out.println("Enter the values  ");
          for(i=1;i<=n;i++)
          {
            v[i]=sc.nextInt();
          }
          System.out.println("Enter the weights  ");
          for(i=1;i<=n;i++)
          {
            wt[i]=sc.nextInt();
          }
          int knapsack[][]=new int[n+2][cap+1];
          for(i=1;i<n+2;i++)
          {
            for(j=1;j<n+1;j++)
            {
              knapsack[i][j]=0;
            }
          }
          /*for(i=1;i<n+2;i++)
             {
               for(j=wt[1]+1;j<cap+2;j++)
               {
                  knapsack[i][j]=v[1];
               }
             }*/
          int k;
          for(i=1;i<n+1;i++)
          {
             for(j=1;j<cap+1;j++)
             {
             /*if(i==1||j==1)
               {
                knapsack[i][j]=0;
               }*/
               if(wt[i]>j)
               {
                 knapsack[i][j]=knapsack[i-1][j];
               }
               else
               {
                  knapsack[i][j]=max(knapsack[i-1][j],v[i]+knapsack[i-1][j-wt[i]]);
               }
             }
        }
        //for displaying the knapsack
         for(i=0;i<n+1;i++)
         {
           for(j=0;j<cap+1;j++)
           {
             System.out.print(knapsack[i][j]+" ");
           }
           System.out.print("\n");
         }
         w=cap;k=n-1;
         j=cap;
         for(i=n;i>0;i--)
         {
           if(knapsack[i][j]!=knapsack[i-1][j])
            {
              j=w-wt[i];
              w=j; 
              solution[k]=1;
              System.out.println("k="+k);
              k--;
           }
           else
           {
             solution[k]=0;
             k--;
           }
        }
        System.out.println("Solution for given knapsack is :- ");
        for(i=0;i<n;i++)
        {
           System.out.print(solution[i]+", ");
        }
        System.out.print("  =>  "+knapsack[n][cap]);
      }
    }
    

    【讨论】:

      【解决方案6】:

      这可以使用动态规划来解决。下面的代码可以帮助您使用动态规划解决 0/1 背包问题。

          internal class knapsackProblem
          {
          private int[] weight;
          private int[] profit;
          private int capacity;
          private int itemCount;
          private int[,] data;
      
          internal void GetMaxProfit()
          {
              ItemDetails();
      
              data = new int[itemCount, capacity + 1];
      
              for (int i = 1; i < itemCount; i++)
              {
                  for (int j = 1; j < capacity + 1; j++)
                  {
                      int q = j - weight[i] >= 0 ? data[i - 1, j - weight[i]] + profit[i] : 0;
      
                      if (data[i - 1, j] > q)
                      {
                          data[i, j] = data[i - 1, j];
                      }
                      else
                      {
                          data[i, j] = q;
                      }
                  }
              }
      
              Console.WriteLine($"\nMax profit can be made : {data[itemCount-1, capacity]}");
              IncludedItems();
          }
      
          private void ItemDetails()
          {
              Console.Write("\nEnter the count of items to be inserted : ");
              itemCount = Convert.ToInt32(Console.ReadLine()) + 1;
              Console.WriteLine();
      
              weight = new int[itemCount];
              profit = new int[itemCount];
      
              for (int i = 1; i < itemCount; i++)
              {
                  Console.Write($"Enter weight of item {i} : ");
                  weight[i] = Convert.ToInt32(Console.ReadLine());
      
                  Console.Write($"Enter the profit on the item {i} : ");
                  profit[i] = Convert.ToInt32(Console.ReadLine());
      
                  Console.WriteLine();
              }
      
              Console.Write("\nEnter the capacity of the knapsack : ");
              capacity = Convert.ToInt32(Console.ReadLine());
          }
      
          private void IncludedItems()
          {
              int i = itemCount - 1;
              int j = capacity;
      
              while(i > 0)
              {
                  if(data[i, j] == data[i - 1, j])
                  {
                      Console.WriteLine($"Item {i} : Not included");
                      i--;
                  }
                  else
                  {
                      Console.WriteLine($"Item {i} : Included");
                      j = j - weight[i];
                      i--;
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-21
        • 1970-01-01
        • 1970-01-01
        • 2013-01-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多