Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# 0-1 Knapsack Problem with known sum and number of zeros in set

I have a 5x5 table of values from 0 to 3 inclusive with all values unknown. I know both the sum of the values and the number of zeros for each row and column. How would I go about solving this 0-1 knapsack problem using C# and retrieving the possible solutions that satisfy the known sums and number of zeros? The tables will always be five rows and five columns, so it's not quite a traditional knapsack.

For example, say we input:

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

We would get this as a possible solution:

[[ 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 ]]

What type of algorithm should I employ in this rather strange situation? Would I also have to write a class just to enumerate the permutations?

Edit for clarification: the problem isn't that I can't enumerate the possibilities; it's that I have no clue how to go about efficiently determining the permutations adding to an arbitrary sum while containing the specified number of zeros and a maximum of 5 items.

like image 540
hydroiodic Avatar asked Oct 10 '22 02:10

hydroiodic


2 Answers

Here there is the code. If you need any comment feel free to ask:

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 {1}ms", 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;
        }
    }
}
like image 74
xanatos Avatar answered Oct 22 '22 14:10

xanatos


How fast does it have to be? I just tested a naive "try pretty much anything" with some early aborts but less than would be possible, and it was pretty fast (less than a millisecond). It gave the solution:

[[ 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 ]]

If that's an acceptable solution to you, I can post the code (or just discuss it, it's quite verbose but the underlying idea is trivial)

edit: it is also trivially extendable to enumerating all solutions. It found 400 of them in 15 milliseconds, and claims that there are no more than that. Is that correct?


What I did, was start at 0,0 and try all values I could fill in at that place (0 though min(3, rowsum[0])), fill it it (subtracting it from rowsum[y] and colsum[x] and subtracting one from rowzero[y] and colzero[x] if the value was zero), then recursively do this for 0,1; 0,2; 0,3; then at 0,4 I have a special case where I just fill in the remaining rowsum if it is non-negative (otherwise, abort the current try - ie go up in the recursion tree), and something similar for when y=4. In the mean time, I abort when any rowsum colsum colzero or rowzero becomes negative.

The current board is a solution if and only if all remaining rowsums columnsums colzero's and rowzero's are zero. So I just test for that, and add it to the solutions if it is one. It won't have any negative entries by construction.

like image 22
harold Avatar answered Oct 22 '22 12:10

harold