C# 0-1 背包问题,已知集合中的总和和零个数
Posted
技术标签:
【中文标题】C# 0-1 背包问题,已知集合中的总和和零个数【英文标题】:C# 0-1 Knapsack Problem with known sum and number of zeros in set 【发布时间】:2011-09-04 08:09:41 【问题描述】:我有一个从 0 到 3 的 5x5 值表,所有值都未知。我知道每行和每列的值的总和和零的数量。我将如何使用 C# 解决这个 0-1 背包问题并检索满足已知和和零数的可能解决方案?这些表格总是五行五列,所以它不是一个传统的背包。
例如,假设我们输入:
Row[0]: Sum=4, Zeros=1
[1]: Sum=5, Zeros=1
[2]: Sum=4, Zeros=2
[3]: Sum=8, Zeros=0
[4]: Sum=3, Zeros=2
Col[0]: Sum=5, Zeros=1
[1]: Sum=3, Zeros=2
[2]: Sum=4, Zeros=2
[3]: Sum=5, Zeros=1
[4]: Sum=7, Zeros=0
我们会将此作为一种可能的解决方案:
[[ 0 1 1 1 1 ]
[ 1 0 2 1 1 ]
[ 2 1 0 0 1 ]
[ 1 1 1 2 3 ]
[ 1 0 0 1 1 ]]
在这种相当奇怪的情况下,我应该采用什么类型的算法?我是否还必须编写一个类来枚举排列?
编辑澄清:问题不在于我无法列举可能性;那是我不知道如何有效地确定添加到任意总和的排列,同时包含指定数量的零和最多 5 个项目。
【问题讨论】:
到现在为止你写了什么代码来解决这个问题?可以发一下吗? 并且可能添加“作业”标签(如果不是作业,我真的很想知道它是什么)。 这和背包有什么关系? 从技术上讲,您可以简单地列举所有解决方案。有 2^50 个可能的表,然后检查约束。 @Valmond:不完全是作业,只是为了缓解我在益智游戏中的挫败感。 【参考方案1】:这里有代码。如果您需要任何意见,请随时提出:
using System;
using System.Diagnostics;
namespace ConsoleApplication15
class Program
static void Main(string[] args)
RowOrCol[] rows = new RowOrCol[]
new RowOrCol(4, 1),
new RowOrCol(5, 1),
new RowOrCol(4, 2),
new RowOrCol(8, 0),
new RowOrCol(3, 2),
;
RowOrCol[] cols = new RowOrCol[]
new RowOrCol(5, 1),
new RowOrCol(3, 2),
new RowOrCol(4, 2),
new RowOrCol(5, 1),
new RowOrCol(7, 0),
;
int[,] table = new int[5, 5];
Stopwatch sw = Stopwatch.StartNew();
int solutions = Do(table, rows, cols, 0, 0);
sw.Stop();
Console.WriteLine();
Console.WriteLine("Found 0 solutions in 1ms", solutions, sw.ElapsedMilliseconds);
Console.ReadKey();
public static int Do(int[,] table, RowOrCol[] rows, RowOrCol[] cols, int row, int col)
int solutions = 0;
int oldValueRowSum = rows[row].Sum;
int oldValueRowZero = rows[row].Zeros;
int oldValueColSum = cols[col].Sum;
int oldValueColZero = cols[col].Zeros;
int nextCol = col + 1;
int nextRow;
bool last = false;
if (nextCol == cols.Length)
nextCol = 0;
nextRow = row + 1;
if (nextRow == rows.Length)
last = true;
else
nextRow = row;
int i;
for (i = 0; i <= 3; i++)
table[row, col] = i;
if (i == 0)
rows[row].Zeros--;
cols[col].Zeros--;
if (rows[row].Zeros < 0)
continue;
if (cols[col].Zeros < 0)
continue;
else
if (i == 1)
rows[row].Zeros++;
cols[col].Zeros++;
rows[row].Sum--;
cols[col].Sum--;
if (rows[row].Sum < 0)
break;
else if (cols[col].Sum < 0)
break;
if (col == cols.Length - 1)
if (rows[row].Sum != 0 || rows[row].Zeros != 0)
continue;
if (row == rows.Length - 1)
if (cols[col].Sum != 0 || cols[col].Zeros != 0)
continue;
if (!last)
solutions += Do(table, rows, cols, nextRow, nextCol);
else
solutions++;
Console.WriteLine("Found solution:");
var sums = new int[cols.Length];
var zeross = new int[cols.Length];
for (int j = 0; j < rows.Length; j++)
int sum = 0;
int zeros = 0;
for (int k = 0; k < cols.Length; k++)
Console.Write("0,2 ", table[j, k]);
if (table[j, k] == 0)
zeros++;
zeross[k]++;
else
sum += table[j, k];
sums[k] += table[j, k];
Console.WriteLine("| Sum 0,2 | Zeros 1", sum, zeros);
Debug.Assert(sum == rows[j].OriginalSum);
Debug.Assert(zeros == rows[j].OriginalZeros);
Console.WriteLine("---------------");
for (int j = 0; j < cols.Length; j++)
Console.Write("0,2 ", sums[j]);
Debug.Assert(sums[j] == cols[j].OriginalSum);
Console.WriteLine();
for (int j = 0; j < cols.Length; j++)
Console.Write("0,2 ", zeross[j]);
Debug.Assert(zeross[j] == cols[j].OriginalZeros);
Console.WriteLine();
// The for cycle was broken at 0. We have to "readjust" the zeros.
if (i == 0)
rows[row].Zeros++;
cols[col].Zeros++;
// The for cycle exited "normally". i is too much big because the true last cycle was at 3.
if (i == 4)
i = 3;
// We readjust the sums.
rows[row].Sum += i;
cols[col].Sum += i;
Debug.Assert(oldValueRowSum == rows[row].Sum);
Debug.Assert(oldValueRowZero == rows[row].Zeros);
Debug.Assert(oldValueColSum == cols[col].Sum);
Debug.Assert(oldValueColZero == cols[col].Zeros);
return solutions;
public class RowOrCol
public readonly int OriginalSum;
public readonly int OriginalZeros;
public int Sum;
public int Zeros;
public RowOrCol(int sum, int zeros)
this.Sum = this.OriginalSum = sum;
this.Zeros = this.OriginalZeros = zeros;
【讨论】:
标记为答案和 +1。谢谢您的帮助!是递归吸引了我。 @hydroiodic 也许它可以在没有递归或堆栈的情况下完成,你知道吗?【参考方案2】:它必须有多快?我刚刚用一些早期中止测试了一个天真的“尝试几乎任何事情”,但比可能的要少,而且它非常快(不到一毫秒)。它给出了解决方案:
[[ 0 1 1 1 1 ]
[ 1 0 1 1 2 ]
[ 1 0 0 1 2 ]
[ 2 1 2 2 1 ]
[ 1 1 0 0 1 ]]
如果这对你来说是一个可以接受的解决方案,我可以发布代码(或者只是讨论它,它非常冗长但基本思想很简单)
edit:它也可以简单地扩展到枚举所有解决方案。它在 15 毫秒内找到了 400 个,并声称仅此而已。对吗?
我所做的是从 0,0 开始,然后尝试我可以在该位置填写的所有值(0 到 min(3, rowsum[0])),填充它(从 rowsum[y] 中减去它并colsum[x] 并从 rowzero[y] 和 colzero[x] 中减去 1(如果值为零),然后对 0,1 递归执行此操作; 0,2; 0,3;然后在 0,4 我有一个特殊情况,如果它是非负的,我只需填写剩余的行和(否则,中止当前的尝试 - 即在递归树中上升),并且在 y=4 时类似。同时,当任何 rowsum colsum colzero 或 rowzero 变为负数时,我会中止。
当且仅当所有剩余的 rowsums columnsums colzero's 和 rowzero's 都为零时,当前的棋盘才是一个解决方案。所以我只是对此进行测试,如果它是一个,则将其添加到解决方案中。它不会有任何构造的否定条目。
【讨论】:
好的,添加了我所做的解释。你也想要代码吗? +1。这让我再次站起来。谢谢!在我看到答案之前,我在 foreach 循环中的 for 循环中做了一堆丑陋的 for 循环。 我只是尝试更多地限制每个条目的选择(使用查找表来获取零和总和的数量),虽然这丢弃了大约一半的选择,但它最终变慢了。问题实例真的太小了,这样的技巧无法得到回报:(以上是关于C# 0-1 背包问题,已知集合中的总和和零个数的主要内容,如果未能解决你的问题,请参考以下文章
算法---- 01背包问题和完全背包问题LeetCode系列问题题解
算法---- 01背包问题和完全背包问题LeetCode系列问题题解