66 votes

L'obtention de la submatrix avec le maximum de la somme?

Entrée: Un tableau en 2 dimensions NxN - Matrice avec des éléments positifs et négatifs.

Sortie: Un submatrix de toute taille, tels que leur somme est le maximum parmi tous les possibles submatrices.

Exigence: Algorithme de complexité de O(N^3)

L'histoire: Avec l'aide de la Algorithmist, Larry et une modification de Kadane de l'Algorithme, j'ai réussi à résoudre le problème en partie ce qui est de la détermination de la sommation seulement - dessous en Java.
Grâce à Ernesto qui a réussi à résoudre le reste du problème, qui est de déterminer les limites de la matrice, c'est à dire en haut à gauche, en bas à droite - ci-dessous en Ruby.

49voto

Rick Giuly Points 81

Voici une explication pour aller avec le posté code. Il y a deux trucs à faire ce travail efficacement: (I) Kandane de l'algorithme et (II) à l'aide du préfixe sommes. Vous devez également (III) appliquer les astuces de la matrice.

Partie I: Kandane de l'algorithme de

Kandane de l'algorithme est un moyen de trouver une ligne de sous-suite avec le maximum de la somme. Permet de commencer avec une approche par force brute pour trouver le max contigus sous-suite et ensuite pensez à optimiser pour obtenir Kandane de l'algorithme.

Supposons que vous disposez de la séquence:

-1,  2,  3, -2

Pour l'approche par force brute, marcher le long de la séquence de générer tous les possible de sous-séquences comme indiqué ci-dessous. En considérant toutes les possibilités, on peut commencer, prolonger, ou à la fin une liste avec chaque étape.

At index 0, we consider appending the -1
-1,  2,  3, -2
 ^
Possible subsequences:
-1   [sum -1]

At index 1, we consider appending the 2
-1,  2,  3, -2
     ^
Possible subsequences:
-1 (end)      [sum -1]
-1,  2        [sum  1]
 2            [sum  2]

At index 2, we consider appending the 3
-1,  2,  3, -2
         ^
Possible subsequences:
-1, (end)       [sum -1]
-1,  2 (end)    [sum -1]
 2 (end)        [sum 2]
-1,  2,  3      [sum 4]
 2,  3          [sum 5]
 3              [sum 3]

At index 3, we consider appending the -2
-1,  2,  3, -2
             ^
Possible subsequences:
-1, (end)          [sum -1]
-1,  2 (end)       [sum  1]
 2 (end)           [sum  2]
-1,  2  3 (end)    [sum  4]
 2,  3 (end)       [sum  5]
 3, (end)          [sum  3]
-1,  2,  3, -2     [sum  2]
 2,  3, -2         [sum  3]
 3, -2             [sum  1]
-2                 [sum -2]

Pour cette approche par force brute, nous avons enfin chercher la liste avec le meilleur de la somme, (2, 3), et c'est la réponse. Cependant pour faire de cette efficace, considérez que vous n'avez pas vraiment besoin de garder tous l'une des listes. Sortir des listes qui n'ont pas terminé, vous avez seulement besoin de garder le meilleur, les autres ne peuvent pas faire mieux. Sortir des listes qui se sont terminées, vous ne pourriez avoir besoin de garder le meilleur, et seulement si elle est meilleure que celles qui ne l'ont pas terminé.

Ainsi, vous pouvez garder une trace de ce que vous avez besoin avec juste une position de tableau et une somme de tableau. La position de la matrice est définie comme ceci: position[r] = s conserve la trace de la liste qui se termine à la r et commence à s. Et, somme[r] donne de la somme pour la sous-suite qui se termine à l'indice de r. C'est l'approche optimisée est Kandane de l'algorithme.

Cours d'exécution à travers l'exemple de nouveau en gardant la trace de nos progrès, de cette façon:

At index 0, we consider appending the -1
-1,  2,  3, -2
 ^
We start a new subsequence for the first element.
position[0] = 0
sum[0] = -1

At index 1, we consider appending the 2
-1,  2,  3, -2
     ^
We choose to start a new subsequence because that gives a higher sum than extending.
position[0] = 0      sum[0] = -1
position[1] = 1      sum[1] = 2


At index 2, we consider appending the 3
-1,  2,  3, -2
         ^
We choose to extend a subsequence because that gives a higher sum than starting a new one.
position[0] = 0      sum[0] = -1
position[1] = 1      sum[1] = 2
position[2] = 1      sum[2] = 5

Again, we choose to extend because that gives a higher sum that starting a new one.
-1,  2,  3, -2
             ^
position[0] = 0      sum[0] = -1
position[1] = 1      sum[1] = 2
position[2] = 1      sum[2] = 5
positions[3] = 3     sum[3] = 3

Encore une fois, le meilleur de la somme est 5 et la liste est à partir de l'index 1 index 2, qui est (2, 3).

Partie II: Préfixe sommes

Nous voulons avoir un moyen de calculer la somme, le long d'une ligne, pour n'importe quel point de départ de tout point de fin. Je veux calculer la somme en O(1) temps plutôt que de simplement ajouter, qui prend O(m) temps où m est le nombre d'éléments dans la somme. Avec quelques precomputing, cela peut être obtenue. Voici comment. Supposons que vous disposez d'une matrice:

a   d   g
b   e   h 
c   f   i

Vous pouvez précalculer cette matrice:

a      d      g
a+b    d+e    g+h
a+b+c  d+e+f  g+h+i

Une fois que c'est fait, vous pouvez obtenir la somme de courir le long d'une colonne à partir du tout début à la fin, point dans la colonne juste par soustraction de deux valeurs.

Partie III: Apporter des trucs ensemble pour trouver le max submatrix

Supposons que vous savez le haut et le bas de ligne du max submatrix. Vous pouvez faire ceci:

  1. Ignorer les lignes au-dessus de votre rangée du haut, et ignorer les lignes en dessous de votre fond ligne.
  2. Avec ce que la matrice reste, envisager l'utilisation de la somme de chaque colonne pour forme d'une séquence (un peu comme une ligne qui représente plusieurs lignes). (Vous pouvez calculer n'importe quel élément de cette séquence rapide avec le préfixe sommes approche.)
  3. Utilisation Kandane l'approche de la figure mieux en sous-suite dans ce la séquence. Les indices que vous obtiendrez vous dire la gauche et la droite les positions de la meilleure submatrix.

Maintenant, quelle est effectivement déterminer le haut et le bas de ligne? Juste essayer toutes les possibilités. Essayez de mettre le haut partout où vous le pouvez et de mettre le bas n'importe où vous le pouvez, et exécuter la Kandane-base de la procédure décrite précédemment pour chaque possibilité. Lorsque vous trouvez un max, vous de garder le haut et le bas de la position.

Trouver la ligne et la colonne prend O(M^2) où M est le nombre de lignes. Trouver la colonne prend O(N) le temps où N est le nombre de lignes. Donc, le temps total est O(M^2 * N). Et, si M=N, le temps est O(N^3).

22voto

Ernesto Points 492

A propos de la récupération de la réelle submatrix, et pas seulement la somme maximale, voici ce que j'ai. Désolé je n'ai pas le temps de traduire mon code pour que votre version de java, donc je poste mon code Ruby avec certains commentaires dans les pièces principales

def max_contiguous_submatrix_n3(m)
  rows = m.count
  cols = rows ? m.first.count : 0

  vps = Array.new(rows)
  for i in 0..rows
    vps[i] = Array.new(cols, 0)
  end

  for j in 0...cols
    vps[0][j] = m[0][j]
    for i in 1...rows
      vps[i][j] = vps[i-1][j] + m[i][j]
    end
  end

  max = [m[0][0],0,0,0,0] # this is the result, stores [max,top,left,bottom,right]
  # these arrays are used over Kandane
  sum = Array.new(cols) # obvious sum array used in Kandane
  pos = Array.new(cols) # keeps track of the beginning position for the max subseq ending in j

  for i in 0...rows
    for k in i...rows
      # Kandane over all columns with the i..k rows
      sum.fill(0) # clean both the sum and pos arrays for the upcoming kandane
      pos.fill(0)
      local_max = 0 # we keep track of the position of the max value over each Kandane's execution
      # notice that we do not keep track of the max value, but only its position
      sum[0] = vps[k][0] - (i==0 ? 0 : vps[i-1][0])
      for j in 1...cols
        value = vps[k][j] - (i==0 ? 0 : vps[i-1][j])
        if sum[j-1] > 0
          sum[j] = sum[j-1] + value
          pos[j] = pos[j-1]
        else
          sum[j] = value
          pos[j] = j
        end
        if sum[j] > sum[local_max]
          local_max = j
        end
      end
      # Kandane ends here

      # Here's the key thing
      # If the max value obtained over the past kandane's execution is larger than
      # the current maximum, then update the max array with sum and bounds
      if sum[local_max] > max[0]
        # sum[local_max] is the new max value
        # the corresponding submatrix goes from rows i..k.
        # and from columns pos[local_max]..local_max
        # the array below contains [max_sum,top,left,bottom,right]
        max = [sum[local_max], i, pos[local_max], k, local_max]
      end
    end
  end

  return max # return the array with [max_sum,top,left,bottom,right]
end

Quelques notes de précisions:

Je utiliser un tableau pour stocker toutes les valeurs relatives à la résultat à des fins de commodité. Vous pouvez utiliser seulement cinq autonome variables: max, haut, gauche, bas, droite. C'est juste plus facile d'attribuer les dans une ligne dans le tableau, puis la sous-routine retourne un tableau avec toutes les informations nécessaires.

Si vous copiez et collez ce code dans un texte-sélectionnez activé éditeur avec Ruby, vous aurez évidemment de mieux le comprendre. Espérons que cette aide!

11voto

Larry Points 3161

C'est une Dynamique de Programmation de problème, et vous pouvez lire à ce sujet l' O(N^3) solution à Algorithmist.

11voto

The111 Points 2103

Il y a déjà beaucoup de réponses, mais voici une autre implémentation de Java que j'ai écrit. Il compare, 3 solutions:

  1. Naïf (brute force) - O(n^6) temps
  2. L'évidence DP solution en O(n^4) en temps et O(n^3) de l'espace
  3. Le plus intelligent DP solution basée sur Kadane de l'algorithme O(n^3) en temps et O(n^2) l'espace

Il y a des exemples de pistes pour n = 10 thru n = 70 par incréments de 10 avec une belle sortie de comparer le temps d'exécution et les exigences de l'espace.

enter image description here

Code:

public class MaxSubarray2D {

    static int LENGTH;
    final static int MAX_VAL = 10;

    public static void main(String[] args) {

        for (int i = 10; i <= 70; i += 10) {
            LENGTH = i;

            int[][] a = new int[LENGTH][LENGTH];

            for (int row = 0; row < LENGTH; row++) {
                for (int col = 0; col < LENGTH; col++) {
                    a[row][col] = (int) (Math.random() * (MAX_VAL + 1));
                    if (Math.random() > 0.5D) {
                        a[row][col] = -a[row][col];
                    }
                    //System.out.printf("%4d", a[row][col]);
                }
                //System.out.println();
            }
            System.out.println("N = " + LENGTH);
            System.out.println("-------");

            long start, end;
            start = System.currentTimeMillis();
            naiveSolution(a);
            end = System.currentTimeMillis();
            System.out.println("   run time: " + (end - start) + " ms   no auxiliary space requirements");
            start = System.currentTimeMillis();
            dynamicProgammingSolution(a);
            end = System.currentTimeMillis();
            System.out.println("   run time: " + (end - start) + " ms   requires auxiliary space for "
                    + ((int) Math.pow(LENGTH, 4)) + " integers");
            start = System.currentTimeMillis();
            kadane2D(a);
            end = System.currentTimeMillis();
            System.out.println("   run time: " + (end - start) + " ms   requires auxiliary space for " +
                    + ((int) Math.pow(LENGTH, 2)) + " integers");
            System.out.println();
            System.out.println();
        }
    }

    // O(N^2) !!!
    public static void kadane2D(int[][] a) {
        int[][] s = new int[LENGTH + 1][LENGTH]; // [ending row][sum from row zero to ending row] (rows 1-indexed!)
        for (int r = 0; r < LENGTH + 1; r++) {
            for (int c = 0; c < LENGTH; c++) {
                s[r][c] = 0;
            }
        }
        for (int r = 1; r < LENGTH + 1; r++) {
            for (int c = 0; c < LENGTH; c++) {
                s[r][c] = s[r - 1][c] + a[r - 1][c];
            }
        }
        int maxSum = Integer.MIN_VALUE;
        int maxRowStart = -1;
        int maxColStart = -1;
        int maxRowEnd = -1;
        int maxColEnd = -1;
        for (int r1 = 1; r1 < LENGTH + 1; r1++) { // rows 1-indexed!
            for (int r2 = r1; r2 < LENGTH + 1; r2++) { // rows 1-indexed!
                int[] s1 = new int[LENGTH];
                for (int c = 0; c < LENGTH; c++) {
                    s1[c] = s[r2][c] - s[r1 - 1][c];
                }
                int max = 0;
                int c1 = 0;
                for (int c = 0; c < LENGTH; c++) {
                    max = s1[c] + max;
                    if (max <= 0) {
                        max = 0;
                        c1 = c + 1;
                    }
                    if (max > maxSum) {
                        maxSum = max;
                        maxRowStart = r1 - 1;
                        maxColStart = c1;
                        maxRowEnd = r2 - 1;
                        maxColEnd = c;
                    }
                }
            }
        }

        System.out.print("KADANE SOLUTION |   Max sum: " + maxSum);
        System.out.print("   Start: (" + maxRowStart + ", " + maxColStart +
                ")   End: (" + maxRowEnd + ", " + maxColEnd + ")");
    }

    // O(N^4) !!!
    public static void dynamicProgammingSolution(int[][] a) {
        int[][][][] dynTable = new int[LENGTH][LENGTH][LENGTH + 1][LENGTH + 1]; // [row][col][height][width]
        int maxSum = Integer.MIN_VALUE;
        int maxRowStart = -1;
        int maxColStart = -1;
        int maxRowEnd = -1;
        int maxColEnd = -1;

        for (int r = 0; r < LENGTH; r++) {
            for (int c = 0; c < LENGTH; c++) {
                for (int h = 0; h < LENGTH + 1; h++) {
                    for (int w = 0; w < LENGTH + 1; w++) {
                        dynTable[r][c][h][w] = 0;
                    }
                }
            }
        }

        for (int r = 0; r < LENGTH; r++) {
            for (int c = 0; c < LENGTH; c++) {
                for (int h = 1; h <= LENGTH - r; h++) {
                    int rowTotal = 0;
                    for (int w = 1; w <= LENGTH - c; w++) {
                        rowTotal += a[r + h - 1][c + w - 1];
                        dynTable[r][c][h][w] = rowTotal + dynTable[r][c][h - 1][w];
                    }
                }
            }
        }

        for (int r = 0; r < LENGTH; r++) {
            for (int c = 0; c < LENGTH; c++) {
                for (int h = 0; h < LENGTH + 1; h++) {
                    for (int w = 0; w < LENGTH + 1; w++) {
                        if (dynTable[r][c][h][w] > maxSum) {
                            maxSum = dynTable[r][c][h][w];
                            maxRowStart = r;
                            maxColStart = c;
                            maxRowEnd = r + h - 1;
                            maxColEnd = c + w - 1;
                        }
                    }
                }
            }
        }

        System.out.print("    DP SOLUTION |   Max sum: " + maxSum);
        System.out.print("   Start: (" + maxRowStart + ", " + maxColStart +
                ")   End: (" + maxRowEnd + ", " + maxColEnd + ")");
    }


    // O(N^6) !!!
    public static void naiveSolution(int[][] a) {
        int maxSum = Integer.MIN_VALUE;
        int maxRowStart = -1;
        int maxColStart = -1;
        int maxRowEnd = -1;
        int maxColEnd = -1;

        for (int rowStart = 0; rowStart < LENGTH; rowStart++) {
            for (int colStart = 0; colStart < LENGTH; colStart++) {
                for (int rowEnd = 0; rowEnd < LENGTH; rowEnd++) {
                    for (int colEnd = 0; colEnd < LENGTH; colEnd++) {
                        int sum = 0;
                        for (int row = rowStart; row <= rowEnd; row++) {
                            for (int col = colStart; col <= colEnd; col++) {
                                sum += a[row][col];
                            }
                        }
                        if (sum > maxSum) {
                            maxSum = sum;
                            maxRowStart = rowStart;
                            maxColStart = colStart;
                            maxRowEnd = rowEnd;
                            maxColEnd = colEnd;
                        }
                    }
                }
            }
        }

        System.out.print(" NAIVE SOLUTION |   Max sum: " + maxSum);
        System.out.print("   Start: (" + maxRowStart + ", " + maxColStart +
                ")   End: (" + maxRowEnd + ", " + maxColEnd + ")");
    }

}

7voto

guirgis Points 420

Voici une version Java de Ernesto mise en œuvre avec quelques modifications:

public int[][] findMaximumSubMatrix(int[][] matrix){
    int dim = matrix.length;
    //computing the vertical prefix sum for columns
    int[][] ps = new int[dim][dim];
    for (int i = 0; i < dim; i++) {
        for (int j = 0; j < dim; j++) {
            if (j == 0) {
                ps[j][i] = matrix[j][i];
            } else {
                ps[j][i] = matrix[j][i] + ps[j - 1][i];
            }
        }
    }

    int maxSum = matrix[0][0];
    int top = 0, left = 0, bottom = 0, right = 0; 

    //Auxiliary variables 
    int[] sum = new int[dim];
    int[] pos = new int[dim];
    int localMax;                        

    for (int i = 0; i < dim; i++) {
        for (int k = i; k < dim; k++) {
            // Kandane over all columns with the i..k rows
            reset(sum);
            reset(pos);
            localMax = 0;
            //we keep track of the position of the max value over each Kandane's execution
            // notice that we do not keep track of the max value, but only its position
            sum[0] = ps[k][0] - (i==0 ? 0 : ps[i-1][0]);
            for (int j = 1; j < dim; j++) {                    
                if (sum[j-1] > 0){
                    sum[j] = sum[j-1] + ps[k][j] - (i==0 ? 0 : ps[i-1][j]);
                    pos[j] = pos[j-1];
                }else{
                    sum[j] = ps[k][j] - (i==0 ? 0 : ps[i-1][j]);
                    pos[j] = j;
                }
                if (sum[j] > sum[localMax]){
                    localMax = j;
                }
            }//Kandane ends here

            if (sum[localMax] > maxSum){
                  /* sum[localMax] is the new max value
                    the corresponding submatrix goes from rows i..k.
                     and from columns pos[localMax]..localMax
                     */
                maxSum = sum[localMax];
                top = i;
                left = pos[localMax];
                bottom = k;
                right = localMax;
            }      
        }
    }
    System.out.println("Max SubMatrix determinant = " + maxSum);
    //composing the required matrix
    int[][] output = new int[bottom - top + 1][right - left + 1];
    for(int i = top, k = 0; i <= bottom; i++, k++){
        for(int j = left, l = 0; j <= right ; j++, l++){                
            output[k][l] = matrix[i][j];
        }
    }
    return output;
}

private void reset(int[] a) {
    for (int index = 0; index < a.length; index++) {
        a[index] = 0;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X