83

I have a set of rectangles and I would like to "reduce" the set so I have the fewest number of rectangles to describe the same area as the original set. If possible, I would like it to also be fast, but I am more concerned with getting the number of rectangles as low as possible. I have an approach now which works most of the time.

Currently, I start at the top-left most rectangle and see if I can expand it out right and down while keeping it a rectangle. I do that until it can't expand anymore, remove and split all intersecting rectangles, and add the expanded rectangle back in the list. Then I start the process again with the next top-left most rectangle, and so on. But in some cases, it doesn't work. For example: enter image description here

With this set of three rectangles, the correct solution would end up with two rectangles, like this: enter image description here

However, in this case, my algorithm starts by processing the blue rectangle. This expand downwards and splits the yellow rectangle (correctly). But then when the remainder of the yellow rectangle is processed, instead of expanding downwards, it expands right first and takes back the portion that was previously split off. Then the last rectangle is processed and it can't expand right or down, so the original set of rectangles is left. I could tweak the algorithm to expand down first and then right. That would fix this case, but it would cause the same problem in a similar scenario that was flipped.

Edit: Just to clarify, the original set of rectangles do not overlap and do not have to be connected. And if a subset of rectangles are connected, the polygon which completely covers them can have holes in it.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Mike Dour
  • 3,616
  • 2
  • 22
  • 24
  • 5
    possible duplicate of [filling a polygon created from rectangles with rectangles](http://stackoverflow.com/questions/5876188/filling-a-polygon-created-from-rectangles-with-rectangles) – Mr.Wizard May 07 '11 at 07:31
  • 4
    possible duplicate of [Find the set of largest contiguous rectangles to cover multiple areas](http://stackoverflow.com/questions/4701887/find-the-set-of-largest-contiguous-rectangles-to-cover-multiple-areas) – finnw May 07 '11 at 10:30

2 Answers2

142

Despite the title to your question, I think you’re actually looking for the minimum dissection into rectangles of a rectilinear polygon. (Jason’s links are about minimum covers by rectangles, which is quite a different problem.)

David Eppstein discusses this problem in section 3 of his 2010 survey article Graph-Theoretic Solutions to Computational Geometry Problems, and he gives a nice summary in this answer on mathoverflow.net:

The idea is to find the maximum number of disjoint axis-parallel diagonals that have two concave vertices as endpoints, split along those, and then form one more split for each remaining concave vertex. To find the maximum number of disjoint axis-parallel diagonals, form the intersection graph of the diagonals; this graph is bipartite so its maximum independent set can be found in polynomial time by graph matching techniques.

Here’s my gloss on this admirably terse description, using figure 2 from Eppstein’s article. Suppose we have a rectilinear polygon, possibly with holes.

When the polygon is dissected into rectangles, each of the concave vertices must be met by at least one edge of the dissection. So we get the minimum dissection if as many of these edges as possible do double-duty, that is, they connect two of the concave vertices.

So let’s draw the axis-parallel diagonals between two concave vertices that are contained entirely within the polygon. (‘Axis-parallel’ means ‘horizontal or vertical’ here, and a diagonal of a polygon is a line connecting two non-adjacent vertices.) We want to use as many of these lines as possible in the dissection as long as they don’t intersect.

(If there are no axis-parallel diagonals, the dissection is trivial—just make a cut from each concave vertex. Or if there are no intersections between the axis-parallel diagonals then we use them all, plus a cut from each remaining concave vertex. Otherwise, read on.)

The intersection graph of a set of line segments has a node for every line segment, and an edge joins two nodes if the lines cross. Here’s the intersection graph for the axis-parallel diagonals:

It’s bipartite with the vertical diagonals in one part, and the horizontal diagonals in the other part. Now, we want to pick as many of the diagonals as possible as long as they don’t intersect. This corresponds to finding the maximum independent set in the intersection graph.

Finding the maximum independent set in a general graph is an NP-hard problem, but in the special case of a bipartite graph, König’s theorem shows that it’s equivalent to the problem of finding a maximum matching, which can be solved in polynomial time, for example by the Hopcroft–Karp algorithm. A given graph can have several maximum matchings, but any of them will do, as they all have the same size. In the example, all the maximum matchings have three pairs of vertices, for example {(2, 4), (6, 3), (7, 8)}:

(Other maximum matchings in this graph include {(1, 3), (2, 5), (7, 8)}; {(2, 4), (3, 6), (5, 7)}; and {(1, 3), (2, 4), (7, 8)}.)

To get from a maximum matching to the corresponding minimum vertex cover, apply the proof of König’s theorem. In the matching shown above, the left set is L = {1, 2, 6, 7}, the right set is R = {3, 4, 5, 8}, and the set of unmatched vertices in L is U = {1}. There is only one alternating path starting in U, namely 1–3–6, so the set of vertices in alternating paths is Z = {1, 3, 6} and the minimum vertex cover is thus K = (L \ Z) ∪ (R ∩ Z) = {2, 3, 7}, shown in red below, with the maximum independent set in green:

Translating this back into the dissection problem, this means that we can use five axis-parallel diagonals in the dissection:

Finally, make a cut from each remaining concave vertex to complete the dissection:

Gareth Rees
  • 64,967
  • 9
  • 133
  • 163
  • 2
    Thanks Gareth, I was waiting to try to implement this before marking it as the answer, but I haven't been back on this project in a while. I just had to do a bug fix in that area so I took a closer look at this and your solution does look like it will work, so I will mark it as the answer, but due to time constraints, I can't implement it right now. Thanks again. – Mike Dour Jan 24 '12 at 16:45
  • Why are diagonals 4 and 5 made but not diagonals from the outer vertices of the two hole? (Upper left to Upper Right, if that makes sense?) Aren't those diagonals between concave corners? – Sam Washburn Jan 05 '15 at 06:23
  • Also how do you know which way to make the cut from each remaining concave corner? – Sam Washburn Jan 05 '15 at 06:26
  • @SamWashburn: (i) I'm afraid that your question doesn't make sense to me. Maybe you can draw a diagram and link to it? (ii) It doesn't matter which way you make the cut from the remaining concave corners: you get the same number of rectangles in the dissection either way. – Gareth Rees Jan 05 '15 at 10:14
  • @GarethRees: Segment 4, for example, connects two top corners of the larger holes. The top-right vertex of the left hole, and the top-left of the right hole. I understand how that is an axis-parallel diagonal. But, isn't top-left to top-left also an axis-parallel diagonal? That segment would pass through the top-right vertex of the left hole as well, but would it still be considered an axis-parallel diagonal? I'm just thinking of how to write the algorithm in a general sense. I hope that makes more sense. – Sam Washburn Jan 05 '15 at 16:00
  • @SamWashburn: I understand now. Obviously we're only interested in axis-parallel diagonals that lie wholly within the polygon. – Gareth Rees Jan 05 '15 at 18:32
  • @GarethRees Thanks, I've worked out an algorithm to create the diagonals. But now I don't see how you make the move from the maximum matching to the minimum vertex cover. Am I missing something? – Sam Washburn Jan 06 '15 at 06:21
  • 1
    @SamWashburn: Use [König's theorem](https://en.wikipedia.org/wiki/König%27s_theorem_(graph_theory)). – Gareth Rees Jan 06 '15 at 09:43
  • @GarethRees Is it safe for me to extend your follow-up answer to Sam and assume that even if my initial shape doesn't contain any concave vertices that can be connected, "it doesn't matter which way you make the cut from the remaining concave corners?" In other words, we only need to apply the above algorithm when edges between concave corners exists. When they do not, we simply make arbitrary edges and we'll still result in the same number of resulting rectangles. – ricklane Feb 16 '16 at 19:40
  • @ricklane: Yes, if there are no axis-parallel diagonals, the dissection is trivial. – Gareth Rees Feb 16 '16 at 20:24
  • @GarethRees I've been working through this for my own project. I found an implementation of Hopcraft-Karp and to test it I plugged in your given intersection graph. I didn't get the same maximum matching result. So, I found another implementation online. http://www-m9.ma.tum.de/graph-algorithms/matchings-hopcroft-karp/index_en.html It gave me the same result as the implementation I found, but still different from the graphic in this answer. http://imgur.com/pmzKz2s I'm still trying to grok the necessary pieces, and would really like to understand why I'm see this inconsistency. – Michael Peddicord Jun 07 '16 at 05:51
  • 2
    @MichaelPeddicord: The maximum matching is not necessarily unique — in this case there are several. The one you found is (1, 3), (2, 4), (5, 7) but there are also (1, 3), (2, 5), (7, 8) and (2, 4), (3, 6), (5, 7) and (1, 3), (2, 4), (7, 8) and maybe others I didn't spot. Any of them will do for the purpose of finding the maximum independent set. – Gareth Rees Jun 07 '16 at 08:50
  • @GarethRees: Thanks for your last, it helped a lot. König's theorem is pretty straight forward after brushing up on set notation, but I don't understand the language on the wiki page to one (very important) piece of the algorithm. Z is vertices in U (Unmatched L vertices) or connected to U by "alternating paths". I can't seem to figure out what alternating paths actually look like. I can interpret is as bouncing back and forth until I have a huge of verts group (selecting almost the entire graph, with seems obviously wrong), or just U -> R -> L. Or perhaps I'm way off. Thanks. – Michael Peddicord Jun 15 '16 at 06:22
  • @MichaelPeddicord: Using the example in the post, let L be {1, 2, 6, 7} and R be {3, 4, 5, 8}. Then U = {1} and the only alternating path starting from U is 1–3–6. So Z = {1, 3, 6} and K = {2, 3, 7} which is the minimum vertex cover. Alternatively, let L be {3, 4, 5, 8} and R be {1, 2, 6, 7}. This way round, we have U = {5} and there are two alternating paths 5–2–4 and 5–7–8. So Z = {2, 4, 5, 7, 8} and K = {2, 3, 7}. – Gareth Rees Jun 16 '16 at 19:54
  • Is there a stable Python package that implements this procedure? – blazs Mar 16 '18 at 11:54
  • @blazs: The only hard bit is the matching step, and for that you could use the [hopcroftkarp](https://pypi.python.org/pypi/hopcroftkarp/) package. – Gareth Rees Mar 16 '18 at 12:06
  • Here is an implementation in python: https://github.com/mittalgovind/Polygon-Partition – Christoph Meißner Apr 23 '18 at 18:08
  • 1
    And another one in Javascript which seems to do something similar: https://github.com/mikolalysenko/rectangle-decomposition Hope it helps someone. – Christoph Meißner Apr 23 '18 at 18:14
  • @GarethRees sorry but could you tell me what it actually means if no free vertices are found after the first phase of the Hopcroft–Karp algorithm? also should we draw the diagonals if none of them intersect with another one. So that's two questions :). Thanks in advance – JSmith Aug 08 '18 at 23:14
  • 1
    @JSmith: (i) When there are no more free vertices, there are no more augmenting paths, so the Hopcroft–Karp algorithm terminates. (ii) If none of the axis-parallel diagonals intersect, then the maximum matching in the intersection graph is empty, and so the maximum independent set contains all the diagonals, and so you can use them all in the dissection. – Gareth Rees Aug 09 '18 at 07:26
  • @GarethRees just for info I've also found this match ({1,3}{2,4}{7,5}) – JSmith Aug 12 '18 at 22:36
  • If the set of rectangles can be represented as a binary image, see these methods [Decomposition of binary images—A survey and comparison](https://www.sciencedirect.com/science/article/abs/pii/S0031320312002427) – jnfran92 Jul 10 '19 at 13:55
1

Today I found O(N^5) solution for this problem, and I will share it here.

For the first step, you need to find a way to get the sum of any rectangle in a matrix, with complexity O(1). It's pretty easy to do.

Now for the second step, you need to know dynamic programming. The idea is to store a rectangle and break it into smaller pieces. If the rectangle is empty, you can return 0. And if it's filled, return 1.

There are N^4 states to store the rectangle, plus the O(N) complexity for each state... So you will get an O(N^5) algorithm.

Here's my code. I think it will help.

The input is simple. N, M (size of matrix) After that, the following N lines will have 1s and 0s. Example:

4 9
010000010
111010111
101111101
000101000
#include <bits/stdc++.h>
#define MAX 51
int tab[MAX][MAX];
int N,M;
int sumed[MAX][MAX];
int t(int x,int y) {
    if(x<0||y<0)return 0;
    return sumed[x][y];
}
int subrec(int x1,int y1,int x2,int y2) {
    return t(x2,y2)-t(x2,y1-1)-t(x1-1,y2)+t(x1-1,y1-1);
}
int resp[MAX][MAX][MAX][MAX];
bool exist[MAX][MAX][MAX][MAX];
int dp(int x1,int y1,int x2,int y2) {
    if(exist[x1][y1][x2][y2])return resp[x1][y1][x2][y2];
    exist[x1][y1][x2][y2]=true;
    int soma = subrec(x1,y1,x2,y2);
    int area = (x2-x1+1)*(y2-y1+1);
    if(soma==area){return resp[x1][y1][x2][y2]=1;}
    if(!soma) {return 0;}
    int best = 1000000;
    for(int i = x1;i!=x2;++i) {
        best = std::min(best,dp(x1,y1,i,y2)+dp(i+1,y1,x2,y2));
    }
    for(int i = y1;i!=y2;++i) {
        best = std::min(best,dp(x1,y1,x2,i)+dp(x1,i+1,x2,y2));
    }
    return resp[x1][y1][x2][y2]=best;
}
void backtracking(int x1,int y1,int x2,int y2) {
    int soma = subrec(x1,y1,x2,y2);
    int area = (x2-x1+1)*(y2-y1+1);
    if(soma==area){std::cout<<x1+1<<" "<<y1+1<<" "<<x2+1<<" "<<y2+1<<"\n";return;}
    if(!soma) {return;}
    int best = 1000000;
    int obj = resp[x1][y1][x2][y2];
    for(int i = x1;i!=x2;++i) {
        int ans = dp(x1,y1,i,y2)+dp(i+1,y1,x2,y2);
        if(ans==obj){
            backtracking(x1,y1,i,y2);
            backtracking(i+1,y1,x2,y2);
            return;
        }
    }
    for(int i = y1;i!=y2;++i) {
        int ans = dp(x1,y1,x2,i)+dp(x1,i+1,x2,y2);
        if(ans==obj){
            backtracking(x1,y1,x2,i);
            backtracking(x1,i+1,x2,y2);
            return;
        }
    }
}
int main()
{
    std::cin >> N >> M;
    for(int i = 0; i != N;++i) {
        std::string s;
        std::cin >> s;
        for(int j = 0; j != M;++j) {
            if(s[j]=='1')tab[i][j]++;
        }
    }
    for(int i = 0; i != N;++i) {
        int val = 0;
        for(int j = 0; j != M;++j) {
            val += tab[i][j];
            sumed[i][j]=val;
            if(i)sumed[i][j]+=sumed[i-1][j];
        }
    }
    std::cout << dp(0,0,N-1,M-1) << std::endl;
    backtracking(0,0,N-1,M-1);
}
  • Ok. I fed your code a 50 by 50 example and it outputs 120.... what now? – Niko O Apr 16 '22 at 20:00
  • I didn't code it, but with a bit of backtracking you will be able to get all the rectangles generated by the program. Really small addition, only around 20 lines. – Rafael Soares Apr 17 '22 at 22:21
  • If by "I didn't code it" you mean that it's not your code, then where did you get it from? If you meant that you didn't add it to the code in the answer, then why not? I'd say it's pretty important for answering the question to get the actual result from the code. – Niko O Apr 20 '22 at 13:51
  • I mean that I didn't code the backtracking (it's not in the code after all), only the DP to find the minimum amount of rectangles. This is my code. – Rafael Soares Apr 21 '22 at 19:25
  • This problem is very similar to IOI 2009 Raisins, https://oj.uz/problem/view/IOI09_raisins almost the same solution. https://ioinformatics.org/page/ioi-2009/35. I think I will add the backtracking later on. – Rafael Soares Apr 21 '22 at 19:28
  • I added the backtracking. Now the code says which rectangles should be created. – Rafael Soares Apr 21 '22 at 19:35