找到覆盖二进制矩阵的最小矩形集

时间:2012-07-17 17:02:12

标签: algorithm matrix

说我有以下二进制矩阵:

0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0

我想找到一个平行于x轴和y轴的矩形,至少覆盖每个1 ,并且不会覆盖一个基数最小的0 (最少量的矩形)。在上面的示例中,这将是矩形((0, 3), (6, 5))((3, 0), (5, 8))(符号的形式为(topleft, bottomright)) - 最小解决方案是使用两个矩形。

我之前的尝试是找到面积最大的矩形仅覆盖1&s,将该矩形添加到集合中,然后将所有1's标记为0& #39; s,直到所有1消失。虽然这个集合将涵盖每1个而不是单个0,但它不一定具有最小的基数(此算法将在上面的示例中失败)。

3 个答案:

答案 0 :(得分:4)

我认为你应该用2代替0而不是0代替覆盖1。这样你就可以在覆盖1时包括2,但仍然不包括任何0。

以下是我提出的建议:

#include <stdio.h>
#include <stdlib.h>

struct board {
  int **data;
  int w,h;
};

int  load_board(char *, struct board *);
void print_board(struct board *);

int  max_height_with_fixed_w(struct board *board, int i, int j, int w) {
  int jj = -1, ii;
  if (board->data[j][i] != 0) {
    for (jj = j; jj < board->h && board->data[jj][i] != 0; jj++) { 
      for (ii = i; ii - i < w; ii++) {
        if (board->data[jj][ii] == 0)
          return jj - j;
      }
    }
    printf("maximum height = %d\n", jj);
  }
  return jj - j;
}

void find_largest_rect_from(
    struct board *board, 
    int i, int j, int *ei, int *ej) {
  int max_w = 0, max_h = 0, max_a = 0;
  *ei = *ej = -1;
  for (max_w = 0; max_w < board->w - i && 
      (board->data[j][i + max_w] != 0); 
      max_w++) {
    int max_aa;
    int max_hh = max_height_with_fixed_w(board, i, j, max_w + 1);
    if (max_hh > max_h) {
      max_h  = max_hh; 
    }
    max_aa = max_hh * (max_w + 1);
    printf("  area: %d x %d = %d\n", max_hh, max_w + 1, max_aa);
    if (max_aa > max_a) {
      max_a = max_aa;
      *ei = i + max_w;
      *ej = j + max_hh - 1; 
    }
  }
  printf("max width : %d\n", max_w);
  printf("max height: %d\n", max_h);
  printf("max area  : %d\n", max_a);
}

int main(int arc, char **argv) {
  struct board board;
  int jj, ii, i = 0, j = 0;
  int total_rects = 0;
  if(load_board(argv[1], &board)) return 1;
  print_board(&board);
  for (j = 0; j < board.h; j++) {
    for (i = 0; i < board.w; i++) {
      if (board.data[j][i] == 1) {
        find_largest_rect_from(&board, i, j, &ii, &jj);
        printf("largest from %d, %d ends at %d,%d\n", i, j, ii, jj);
        int marki, markj;
        total_rects++;
        for (markj = j; markj <= jj; markj++) {
          for (marki = i; marki <= ii; marki++) {
            board.data[markj][marki] = 2;
          }
        }
        print_board(&board);
      }
    }
  }
  printf("minimum %d rects are required\n", total_rects);
  return 0;
}

int load_board(char *fname, struct board *board) {
  FILE *file = fopen(fname, "r");
  int j,i;
  if (!file) return 1;
  fscanf(file, "%d %d", &board->w, &board->h);
  board->data = (int**)malloc(sizeof(int*)*board->h);
  for (j = 0; j < board->h; j++) {
    board->data[j] = (int*)malloc(sizeof(int)*board->w);
    for (i = 0; i < board->w; i++) {
      fscanf(file, "%d", &board->data[j][i]);
    }
  }
  return 0;
}

void print_board(struct board *board) {
  int i,j;
  printf("board size: %d, %d\n", board->w, board->h);
  for (j = 0; j < board->h; j++) {
    for (i = 0; i < board->w; i++) {
      printf("%d ", board->data[j][i]);
    } printf("\n");
  }
}

示例输入1:

7 9
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 0 0 1 1 1 0
0 0 0 1 1 1 0
0 0 0 1 1 1 0

示例输入2:

7 7
0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0
0 0 0 1 0 0 0

答案 1 :(得分:0)

算法的想法:

  • 只要矩阵中有1 s:
  • 对于每个1以上没有1并且左侧没有1的人,请执行以下操作:
  • 贪婪:从这个1开始,向右和向下对角线,只要路上有1 - 创建一个矩形并更改1将创建的矩形的s放入0 s。

答案 2 :(得分:0)

我会选择一种算法来挑选点并进行扩展,直到它产生所有可能的空间,然后选择更多,直到网格上的所有点都被消耗掉。

对于你的例子,假设我们消耗了1s。

我会选择(0,3),最左边的1s的最上面。我的矩形的起始大小为0,0。我将它向右和向下扩展,直到它增长到6,2。在这一点上,我将这些点标记为已占用。

然后我会选择另一个点,比如说(3,0),用一个大小为0,0的矩形。我会把它长大下来直到占用最大的可用空间,大小为2,6。

请考虑以下事项:

0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0
0 0 0 1 0 0 0

您可以轻松确定对于任何随机起点,它总是需要4个矩形。

为了将点标记为“已占用”,您应该将它们标记为不同于标记为“不可消耗”的点。然后,您可以区分不可消耗的(不能扩展到)和“占用”(可以扩展到,但不一定是因为它们已经存在)。