【问题标题】:Knapsack - brute force algorithm背包 - 蛮力算法
【发布时间】:2015-06-22 12:53:49
【问题描述】:

我发现这段代码使用蛮力机制解决背包问题(这主要是为了学习,所以不需要指出动态更有效)。我得到了代码,并且理解了其中的大部分内容。最多。问题来了:

我注意到这两个条件,我不知道它们是如何工作的以及为什么它们在代码中 - 我知道它们至关重要,因为我所做的任何更改都会导致算法产生错误的结果:

// if bit not included then skip
if (((i >> j) & 1) != 1) continue;

// if bit match then add
if (((bestPosition >> j) & 1) == 1)
{
    include.Add(Items[j]);
}

这是整个班级,以及我从 main 中调用它的方式:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace KnapSack2
{
    class BruteForce
    {
        public double Capacity { get; set; }
        public Item[] Items { get; set; }

        public Data Run()
        {
            int bestValue = 0;
            int bestPosition = 0;
            int size = Items.Length;

            var permutations = (long)Math.Pow(2,size);

            for (int i = 0; i<permutations; i++)
            {
                int total = 0;
                int weight = 0;
                for (int j = 0; j<size; j++)
                {
                    //jeżeli bit "not included" to omin"
                    if(((i>>j)&1)!=1)
                        continue;
                    total += Items[j].v;
                    weight += Items[j].w;
                }
                if (weight <= Capacity && total>bestValue)
                {
                    bestPosition = i;
                    bestValue = total;
                }
            }
            var include = new List<Item>();
            for (int j = 0; j<size; j++)
            {
                // jeżeli bit pasuje, wtedy dodaj
                if (((bestPosition>>j) & 1)==1)
                    include.Add(Items[j]);
            }
            return new Data { BestValue = bestValue, Include = include };

        }//End of Run


    }
}

在 main 中调用

var ks = new BruteForce
{
    Capacity = MaxWeight,
    Items = items.ToArray()
};

Data result = ks.Run();

Item 类只是一个简单的对象,包含值、重量和 ID

【问题讨论】:

  • 看起来您的问题更适合 Code Review 网站,因为它正在工作并且您没有遇到错误。
  • this: ((i &gt;&gt; j) &amp; 1) != 1 与之前的评论完全一致:它将检查i 中的jth(从0 开始)位是否已设置(或者更好的是未设置在在这种情况下) - 它将删除i 中的最后一个j 位,方法是将它们移出,然后按位和&amp;..000001 一起移动,看看这是否是1跨度>
  • 我可以补充一点,它是二进制计数器技术来生成所有组合,所以我们必须使用这种按位运算
  • @AndyKorneyev 我不认为 Code Review 接受“向我解释这个别人写的代码”的问题。更多的是关于“我写了这个。我怎样才能让它变得更好?”。

标签: c# algorithm brute-force knapsack-problem


【解决方案1】:

这个&amp;bitwise-AND

按位与运算符将其第一个操作数的每一位与 它的第二个操作数的相应位。如果两个位都为 1,则 相应的结果位设置为 1。否则,相应的 结果位设置为 0。

虽然&gt;&gt; 是右移运算符

右移运算符 (>>) 将其第一个操作数右移 第二个操作数指定的位数。

话虽如此,让我们采用以下表达式

if (((i >> j) & 1) != 1) continue;

并尝试理解它。

最初,这个i &gt;&gt; j 会将i 的位右移j 的位置。

例如,让我们有以下作业:

int number = 5;

number 的二进制表示为:

0000 0000 0000 0000 0000 0000 0000 0101

如果我们定义一个新的整数为:

int shiftNumbersBitByOne = a >> 1;

那么shiftNumbersBitByOne 的二进制表示将是:

0000 0000 0000 0000 0000 0000 0000 0010

然后在这个运算的结果和 1 上,我们应用按位与运算符。

这个操作符到底是做什么的?

尽管定义很清楚,但举个例子会更清楚。

假设我们有二进制数ab,那么a&amp;b的结果如下:

a =     0001 0100 1010 0001 1000 1010 1101 0011
b =     0010 1100 1111 0111 0011 1010 1011 0111
a & b = 0000 0100 1010 0001 0000 1010 1001 0011

话虽如此,在 (i &gt;&gt; j) &amp; 1 这个运算中,我们在 i &gt;&gt; j 的结果和 1 的二进制表示之间应用位与运算符

0000 0000 0000 0000 0000 0000 0000 0001

(i &gt;&gt; j) &amp; 1 的结果何时为 1?

这将发生当且仅当i &gt;&gt; j 的最后一位数字是 1。

更新

我们在上面讨论了如何部分 -我不知道它们是如何工作的。现在我们将讨论为什么部分 -为什么它们会出现在代码中

让我们定义我们的问题,背包问题。根据wikipedia

背包问题或背包问题是组合问题 优化:给定一组项目,每个项目都有一个质量和一个值, 确定要包含在集合中的每个项目的数量,以便 总重量小于或等于给定限制且总重量 值尽可能大。

根据上述,很简单

// This is the total weight limit.
public double Capacity { get; set; }

// This is an array with all the given items.
public Item[] Items { get; set; }

此外,根据您的代码,我们可以推断出每个项目都有一个值和一个权重,可以分别以item.vitem.w 访问。我建议您将其分别重命名为 value 和 weight,以使您的代码更有意义。

显然,这个int size = Items.Length;是可用物品的数量。

为什么部分从这里开始

var permutations = (long)Math.Pow(2,size);

permutations 是什么? permutations 代表什么?

在回答这个问题之前,让我们考虑一下如何表示项目集合中的哪些项目将在最终解决方案中使用。我认为如果我们有 n 个项目,我们可以用一个 n 位数来表示这一点。这怎么可能?如果 n 位数中的每一位都指向 n 项中的一项,那么很明显我们可以这样做。如果 n-th 项不包含在最终解决方案中,则 n-th 位的值将为 0。虽然它的值是 1,但如果它被包含在内。

话虽这么说,排列代表什么很清楚。 它代表了最终解决方案中所有可能的项目组合。这很清楚,因为每个位可以有 2 个值,0 或 1。假设我们有 n 位,可能的组合数是 2^n。

实际上,出于这个原因,该算法是一种蛮力算法(我们进行了详尽的搜索)。我们访问所有可能的组合以找到最佳组合。在以下循环中:

for (int i = 0; i<permutations; i++)
{ 
    // ...
}

你遍历所有可能的组合。

然后foreach组合,循环遍历items集合:

for (int j = 0; j < size; j++)
{
    // Here you check if the item in position j
    // is included in the current combination.
    // If it is not, you go to the next value of the loop's variable
    // and you make the same check.
    if(((i>>j)&1)!=1)
        continue;

    // The j-th item is included in the current combination. 
    // Hence we add it's
    // value to the total value accumulator and it's weight to 
    // the total weight accumulator.
    total += Items[j].v;
    weight += Items[j].w;
}

现在,如果weight 小于限制值并且总值大于当前最佳总值,我们选择此组合作为当前最佳:

bestPosition = i;
bestValue = total;

最后,在遍历所有可用组合后,我们将拥有最好的组合。

找到最佳组合后,我们必须遍历项目以查看其中哪些包含在此组合中。

// The include is a list that will hold the items of the best combination.
var include = new List<Item>();

// We loop through all the available items
for (int j = 0; j<size; j++)
{
    // If the items is included in the best combination,
    // add this item to the include list.
    if (((bestPosition>>j) & 1)==1)
        include.Add(Items[j]);
}

【讨论】:

  • 这并没有说明他们为什么在代码中,他们在这里完成了什么,这似乎是OP的主要关注点。
  • @downhand 我认为现在更好。
  • 我不认为任何数量的支持都能让你对那个伙伴表示赞赏。谢谢
  • @BartoszCieszewski 你欢迎老兄。我很高兴能帮上忙:)
  • 感谢详细的解释!关于“当 (i >> j) & 1 的结果为 1 时?”的注释只有一条。我认为它应该是“当且仅当 i >> j 的最后一位是 1,因为其余的都由 & 和 1 归零”
【解决方案2】:

显然,有问题的代码部分是检查是否设置了某个位,如 cmets 所示。条件

((i >> j) & 1) != 1

当且仅当ij-th 位为零时为真;条件

((bestPosition >> j) & 1) == 1

当且仅当bestPosition 的第j 位为1 时为真。关于更大的图景,显然实现使用int 对一组项目建模,其中j-th 位设置当且仅当j-th 项目包含在集合中;因此,成员资格测试可以通过位检查来完成。该实现枚举项目的所有子集(使用int 表示它们)以执行穷举搜索。

请注意,集合的 Delphi 实现使用相同的方法,但对客户端代码隐藏了位索引。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多