Google Answers Logo
View Question
 
Q: Finding the maximum sum of values in a 2d array with certain restrictions ( Answered 3 out of 5 stars,   3 Comments )
Question  
Subject: Finding the maximum sum of values in a 2d array with certain restrictions
Category: Computers > Algorithms
Asked by: ronsz-ga
List Price: $10.00
Posted: 04 Nov 2002 03:05 PST
Expires: 04 Dec 2002 03:05 PST
Question ID: 98061
I am trying to compute the maximum possible sum of values from a 
matrix or 2d array or table or any suitable structure. The restriction
is that once you select a particular row,column value to add to your
sum, no other values from that row or column may be used in
calculating the sum.

The only way I have been able to solve this is by going through 
every possible combination and store the potential result in a set of
all possible potential results. I then select the maximum. This seems
to chew up a lot of memory in my program which generates errors. The
program is written in Java.

I have provided an example below for further clarification

e.g.
3 X 3 matrix with the following values 
0.5 0.1 0.4
0.3 0.8 0.7
0.2 0.4 0.6

All possible combinations
0.5 + 0.8 + 0.6 = 1.9
0.5 + 0.7 + 0.4 = 1.6
0.1 + 0.3 + 0.6 = 1.0
0.1 + 0.7 + 0.2 = 1.0
0.4 + 0.3 + 0.4 = 1.1
0.4 + 0.8 + 0.2 = 1.4

So the maximum possible sum is 1.9

If there is no other way to get the exact maximum, is there 
something I can do to get an approximate value?

Duplicates can appear in the matrix and the matrix is not necessarily
square.
Answer  
Subject: Re: Finding the maximum sum of values in a 2d array with certain restrictions
Answered By: leapinglizard-ga on 04 Nov 2002 05:53 PST
Rated:3 out of 5 stars
 
There's no need to approximate. You can efficiently find the maximum
sum by brute force, that is, by finding all possible sums. To
economize on memory, we store only the best sum found so far.

If we want to find feasible combinations of matrix elements, it helps
to have the following insight: given that the matrix has dimensions
n*m, i.e., n rows and m columns, with the condition that n is less
than or equal to m, a valid sum may only include one element from each
row. Think about that for a minute. It means that for an n*m matrix, a
feasible sum will consist of exactly n terms, where each term is drawn
from a different column. (If we are given a matrix that has more rows
than columns, we simply transpose it to obtain a matrix that meets the
requirement n <= m.)

The following program requires that you pass it the dimensions of a
matrix and the name of a file containing whitespace-separated matrix
elements.

//begin MatrixSum.java
import java.io.*;
import java.util.*;

public class MatrixSum {
    static int n, m;
    static double bestsum;
    static double[][] mat;
    static boolean[] used;
    static void best(int i, double sum) {
        if (i == n) {
            if (sum > best)
                bestsum = sum;
            return;
        }
        for (int j = 0; j < m; j++)
            if (!used[j]) {
                used[j] = true;
                best(i+1, sum+mat[i][j]);
                used[j] = false;
            }
    }
    static void error(String msg) {
        System.out.println(msg);
        System.exit(0);
    }
    public static void main(String[] argv) {
        int i, j, imax = 0, jmax = 0;
        boolean transpose = false;
        BufferedReader in = null;
        String str = "", line = "";
        StringTokenizer tokenizer;
        if (argv.length != 3)
            error("usage: java MatrixSum <n> <m> <filename>");
        try {
            imax = n = Integer.parseInt(argv[0]);
            jmax = m = Integer.parseInt(argv[1]);
            if (n > m) {
                n -= m;
                m += n;
                n = m-n;
                transpose = true;
            }
        } catch (NumberFormatException e) { error("error: integers
required"); }
        try {
            in = new BufferedReader(new FileReader(argv[2]));
        } catch (IOException e) { error("error: missing file"); }
        try {
            while ((line = in.readLine()) != null)
                str += line + " ";
        } catch (IOException e) { error("bad file"); }
        tokenizer = new StringTokenizer(str, " \t\n\r\f");
        if (tokenizer.countTokens() != n*m)
            error("" + n*m + " values required; only have " +
tokenizer.countTokens());
        mat = new double[n][m];
        used = new boolean[m];
        try {
            for (i = 0; i < imax; i++) 
                for (j = 0; j < jmax; j++) 
                    if (transpose)
                        mat[j][i] =
Double.parseDouble(tokenizer.nextToken());
                    else
                        mat[i][j] =
Double.parseDouble(tokenizer.nextToken());
        } catch (Exception e) { error("bad number"); }
        for (i = 0; i < m; i++)
            used[i] = false;
        bestsum = Double.MIN_VALUE;
        best(0, 0.0);
        System.out.println("best sum = " + bestsum);
    }
}
//end MatrixSum.java

Don't be intimidated by the length of this program. You'll find upon
closer inspection that the vast majority of it consists of string
processing and attempts to catch various user errors, since I've tried
to make it reasonably robust.

The real work is done by the recursive function best(). Let me
describe what takes place before, during, and after its execution. By
the time we reach the line "bestsum = Double.MIN_VALUE;", the
two-dimensional array mat[][] will have been filled with n rows and m
columns such that n < m. We also have a boolean array used[] of size
m, with all elements initialized to false. Since we have yet to
calculate any sum, we initialize bestsum to the smallest sum that can
conceivably be obtained. We now call best() with the arguments 0 and
0.0, telling it that we want to choose an element from row 0 and that
our running sum is 0.0 so far.

The function best() first asks whether we have exhausted all rows of
the matrix. If so, we compare the running sum to the best sum, and, if
necessary, update the latter. Otherwise, we have to choose an element
from the current row. We iterate over the row, considering each column
number. If the array used[] tells us that we have already chosen an
element from that column, we move on. Otherwise, we set the
corresponding element of used[] to true, add the matrix element from
this column to our running sum, increment the row number, and make a
recursive call to best(). In effect, we have made our choice, and are
now moving on to the next row. Upon returning from the recursive call,
we are back at our current row and column number. We reset the used[]
element to false, and continue to the next column.

And that's it. Once we've returned from the original call to best(),
every feasible sum will have been evaluated, and we are assured that
the highest one has been assigned to bestsum.

There is a more efficient way to calculate the best sum, involving a
stack, but it's computationally equivalent to the recursive solution.
I'm afraid a general explanation of recursivity lies beyond the scope
of your question, but I trust you'll be satisfied with this solution
to your immediate problem.

Regards,

leapinglizard

Clarification of Answer by leapinglizard-ga on 04 Nov 2002 06:19 PST
There's a slight error in the code above, since I renamed a variable
without bothering to recompile.

In the function best(), change the line
            if (sum > best) 
to
            if (sum > bestsum)
and you're all set.

Regards,

leapinglizard

Request for Answer Clarification by ronsz-ga on 10 Nov 2002 00:54 PST
I have tested this out and while my code isn't running out of memory
(big plus :) ) the algorithm is still taking quite some time to
compute. I don't have an exact figure but we're talking about greater
than 10 minutes. I'm glad that this solution performs better than at
least my old one but I really need something a bit quicker.

Clarification of Answer by leapinglizard-ga on 10 Nov 2002 08:58 PST
The brute-force algorithm computes C(m,n)*n! =
m*(m-1)*(m-2)*...*(m-n+1) sums, which may, indeed, take quite a while.
For a 12x10 matrix, for example, we have C(m,n)*n! = 66*3628800 =
239,500,800 sums.

We can speed up the work somewhat by considering, among the m elements
in a given row, only the n greatest ones. Now, we compute n! sums,
which, in the example above, yields 10! = 3,628,800. This is still a
formidable number on a human scale, but more manageable
computationally. Code follows.

//begin MatrixSum.java
import java.io.*;
import java.util.*;

public class MatrixSum {
    static int n, m;
    static double bestsum;
    static double[][] mat;
    static boolean[][] top;
    static boolean[] used;
    static void best(int i, double sum) {
        if (i == n) {
            if (sum > bestsum)
                bestsum = sum;
            return;
        }
        for (int j = 0; j < m; j++)
            if (top[i][j] && !used[j]) {
                used[j] = true;
                best(i+1, sum+mat[i][j]);
                used[j] = false;
            }
    }
    static void error(String msg) {
        System.out.println(msg);
        System.exit(0);
    }
    public static void main(String[] argv) {
        int i, j, imax = 0, jmax = 0;
        double[] row;
        boolean transpose = false;
        BufferedReader in = null;
        String str = "", line = "";
        StringTokenizer tokenizer;
        if (argv.length != 3)
            error("usage: java MatrixSum <n> <m> <filename>");
        try {
            imax = n = Integer.parseInt(argv[0]);
            jmax = m = Integer.parseInt(argv[1]);
            if (n > m) {
                n -= m;
                m += n;
                n = m-n;
                transpose = true;
            }
        } catch (NumberFormatException e) { error("error: integers
required"); }
        try {
            in = new BufferedReader(new FileReader(argv[2]));
        } catch (IOException e) { error("error: missing file"); }
        try {
            while ((line = in.readLine()) != null)
                str += line + " ";
        } catch (IOException e) { error("bad file"); }
        tokenizer = new StringTokenizer(str, " \t\n\r\f");
        if (tokenizer.countTokens() != n*m)
            error("" + n*m + " values required; only have " +
tokenizer.countTokens());
        mat = new double[n][m];
        top = new boolean[n][m];
        used = new boolean[m];
        try {
            for (i = 0; i < imax; i++)
                for (j = 0; j < jmax; j++)
                    if (transpose)
                        mat[j][i] =
Double.parseDouble(tokenizer.nextToken());
                    else
                        mat[i][j] =
Double.parseDouble(tokenizer.nextToken());
        } catch (Exception e) { error("bad number"); }
        for (i = 0; i < n; i++) {
            row = mat[i];
            Arrays.sort(row);
            for (j = 0; j < m; j++)
                if (Arrays.binarySearch(row, mat[i][j]) < n)
                    top[i][j] = true;
                else
                    top[i][j] = false;
        }
        for (i = 0; i < m; i++)
            used[i] = false;
        bestsum = Double.MIN_VALUE;
        best(0, 0.0);
        System.out.println("best sum = " + bestsum);
    }
}
//end MatrixSum.java

Further optimizations are possible, but they require increasing
algorithmic sophistication. If you find that this crude pruning is
inadequate, I'd like you to give me an idea of the size and
composition of your problem instances.

Regards,

leapinglizard

Clarification of Answer by leapinglizard-ga on 10 Nov 2002 09:03 PST
The code I've given above is flawed. Let me get back to you with a correction.

leapinglizard

Clarification of Answer by leapinglizard-ga on 10 Nov 2002 09:15 PST
I've fixed the errors; fresh code follows. It's faster than my first
version by an order of magnitude.

//begin MatrixSum.java
import java.io.*;
import java.util.*;

public class MatrixSum {
    static int n, m;
    static double bestsum;
    static double[][] mat;
    static boolean[][] top;
    static boolean[] used;
    static void best(int i, double sum) {
        if (i == n) {
            if (sum > bestsum)
                bestsum = sum;
            return;
        }
        for (int j = 0; j < m; j++)
            if (!used[j] && top[i][j]) {
                used[j] = true;
                best(i+1, sum+mat[i][j]);
                used[j] = false;
            }
    }
    static void error(String msg) {
        System.out.println(msg);
        System.exit(0);
    }
    public static void main(String[] argv) {
        int i, j, imax = 0, jmax = 0;
        double[] row;
        boolean transpose = false;
        BufferedReader in = null;
        String str = "", line = "";
        StringTokenizer tokenizer;
        if (argv.length != 3)
            error("usage: java MatrixSum <n> <m> <filename>");
        try {
            imax = n = Integer.parseInt(argv[0]);
            jmax = m = Integer.parseInt(argv[1]);
            if (n > m) {
                n -= m;
                m += n;
                n = m-n;
                transpose = true;
            }
        } catch (NumberFormatException e) { error("error: integers
required"); }
        try {
            in = new BufferedReader(new FileReader(argv[2]));
        } catch (IOException e) { error("error: missing file"); }
        try {
            while ((line = in.readLine()) != null)
                str += line + " ";
        } catch (IOException e) { error("bad file"); }
        tokenizer = new StringTokenizer(str, " \t\n\r\f");
        if (tokenizer.countTokens() != n*m)
            error("" + n*m + " values required; only have " +
tokenizer.countTokens());
        mat = new double[n][m];
        top = new boolean[n][m];
        used = new boolean[m];
        row = new double[m];
        try {
            for (i = 0; i < imax; i++)
                for (j = 0; j < jmax; j++)
                    if (transpose)
                        mat[j][i] =
Double.parseDouble(tokenizer.nextToken());
                    else
                        mat[i][j] =
Double.parseDouble(tokenizer.nextToken());
        } catch (Exception e) { error("bad number"); }
        for (i = 0; i < n; i++) {
            for (j = 0; j < m; j++)
                row[j] = mat[i][j];
            Arrays.sort(row);
            for (j = 0; j < m; j++)
                if (Arrays.binarySearch(row, mat[i][j]) >= (m-n))
                    top[i][j] = true;
                else
                    top[i][j] = false;
        }
        for (i = 0; i < m; i++)
            used[i] = false;
        bestsum = Double.MIN_VALUE;
        best(0, 0.0);
        System.out.println("best sum = " + bestsum);
    }
}
//end MatrixSum.java

Regards,

leapinglizard
ronsz-ga rated this answer:3 out of 5 stars
see request for clarification

Comments  
Subject: Re: Finding the maximum sum of values in a 2d array with certain restrictions
From: eagleye-ga on 06 Nov 2002 12:57 PST
 
It can be formulated as an integer programming problem. 
The, you can find many well studied techniques to
solve or approximate it.
Subject: Re: Finding the maximum sum of values in a 2d array with certain restrictions
From: ronsz-ga on 10 Nov 2002 03:09 PST
 
Thanks eagleye. I've briefly looked at this but i'm not quite sure how
to proceed. I can post a formal question on this if it will help find
an exact solution to my problem.
Subject: Re: Finding the maximum sum of values in a 2d array with certain restrictions
From: rbnn-ga on 10 Nov 2002 21:59 PST
 
I think the problem is np-complete

Important Disclaimer: Answers and comments provided on Google Answers are general information, and are not intended to substitute for informed professional medical, psychiatric, psychological, tax, legal, investment, accounting, or other professional advice. Google does not endorse, and expressly disclaims liability for any product, manufacturer, distributor, service or service provider mentioned or any opinion expressed in answers or comments. Please read carefully the Google Answers Terms of Service.

If you feel that you have found inappropriate content, please let us know by emailing us at answers-support@google.com with the question ID listed above. Thank you.
Search Google Answers for
Google Answers  


Google Home - Answers FAQ - Terms of Service - Privacy Policy