25

Given a convex polygon, how do I find the 3 points that define a triangle with the greatest area.

Related: Is it true that the circumcircle of that triangle would also define the minimum bounding circle of the polygon?

willc2
  • 38,991
  • 25
  • 88
  • 99
  • 1
    No, I working on collision detection of polygonal shapes for iPhone games. The minimum bounding circle would let me cull the set of potentially colliding shapes before doing more expensive polygon-polygon intersection tests. In the process, I'm learning computational geometry algorithms and translating them into Objective-C. In the future I will probably just use a physics library but I want to know how it works from the ground up. – willc2 Oct 25 '09 at 18:56
  • 2
    I've found that putting too much detail in a question is bad as people tend to answer the most interesting (implied) questions rather than the stated one if it's "too simple". As a newbie, nothing's simple. – willc2 Oct 25 '09 at 19:01
  • Note the answer of the secondary question but not the primary. Sorry to call you out Stephan, it's my fault for trying for a two-fer. – willc2 Oct 25 '09 at 19:13

5 Answers5

35

Yes, you can do significantly better than brute-force.

By brute-force I assume you mean checking all triples of points, and picking the one with maximum area. This runs in O(n3) time, but it turns out that it is possible to do it in not just O(n2) but in O(n) time!

[Update: A paper published in 2017 showed by example that the O(n) solution doesn't work for a specific class of polygons. See this answer for more details. But the O(n2) algorithm is still correct.]

By first sorting the points / computing the convex hull (in O(n log n) time) if necessary, we can assume we have the convex polygon/hull with the points cyclically sorted in the order they appear in the polygon. Call the points 1, 2, 3, … , n. Let (variable) points A, B, and C, start as 1, 2, and 3 respectively (in the cyclic order). We will move A, B, C until ABC is the maximum-area triangle. (The idea is similar to the rotating calipers method, as used when computing the diameter (farthest pair).)

With A and B fixed, advance C (e.g. initially, with A=1, B=2, C is advanced through C=3, C=4, …) as long as the area of the triangle increases, i.e., as long as Area(A,B,C) ≤ Area(A,B,C+1). This point C will be the one that maximizes Area(ABC) for those fixed A and B. (In other words, the function Area(ABC) is unimodal as a function of C.)

Next, advance B (without changing A and C) if that increases the area. If so, again advance C as above. Then advance B again if possible, etc. This will give the maximum area triangle with A as one of the vertices.

(The part up to here should be easy to prove, and simply doing this separately for each A would give an O(n2) algorithm.)

Now advance A again, if it improves the area, and so on.(The correctness of this part is more subtle: Dobkin and Snyder gave a proof in their paper, but a recent paper shows a counterexample. I have not understood it yet.)

Although this has three "nested" loops, note that B and C always advance "forward", and they advance at most 2n times in total (similarly A advances at most n times), so the whole thing runs in O(n) time.

Code fragment, in Python (translation to C should be straightforward):

 # Assume points have been sorted already, as 0...(n-1)
 A = 0; B = 1; C = 2
 bA= A; bB= B; bC= C #The "best" triple of points
 while True: #loop A

   while True: #loop B
     while area(A, B, C) <= area(A, B, (C+1)%n): #loop C
       C = (C+1)%n
     if area(A, B, C) <= area(A, (B+1)%n, C): 
       B = (B+1)%n
       continue
     else:
       break

   if area(A, B, C) > area(bA, bB, bC):
     bA = A; bB = B; bC = C

   A = (A+1)%n
   B = (A+1)%n
   C = (A+2)%n
   if A==0: break

This algorithm is proved in Dobkin and Snyder, On a general method for maximizing and minimizing among certain geometric problems, FOCS 1979, and the code above is a direct translation of their ALGOL-60 code. Apologies for the while-if-break constructions; it ought to be possible to transform them into simpler while loops.

Community
  • 1
  • 1
ShreevatsaR
  • 38,402
  • 17
  • 102
  • 126
  • 1
    is this really O(n) ? – Ninja420 Aug 24 '13 at 19:31
  • @Ninja420: Yes it is. I've already given a proof in the answer above; you can also read the linked paper for a more detailed one. – ShreevatsaR Aug 25 '13 at 09:34
  • Related question, would be good if you could provide a detailed description here, http://stackoverflow.com/questions/18423040/largest-triangle-in-convex-hull – Ninja420 Aug 25 '13 at 14:12
  • 1
    @Ninja420: Ah I see. Yes, the fact that B and C advance only 2n times each must have seemed obvious to me when I wrote the answer, but it's seeming subtle now... clearly I'm getting older! I'll think about it and post an answer at the other question when it's clear to me again. – ShreevatsaR Aug 26 '13 at 09:54
  • @Ninja420: Thanks for bringing that to my attention... I understand the proof again now, and I've posted an answer on the other question. I could elaborate more, if it isn't clear enough. – ShreevatsaR Aug 26 '13 at 12:01
  • Since it is using the rotating calipers, can we also conclude that, the diameter will always be a side of the maximising triangle? – Vikash Balasubramanian Sep 30 '16 at 12:13
  • 2
    @VikashB No that's not true. E.g. if you look at the figure from the paper (see [this answer](http://stackoverflow.com/a/18443566/4958)), it does not appear to be the case that the diameter of the polygon is a side of even any of the three "anchored local maxima". More generally, imagine a regular n-gon for large n, so that it almost resembles a circle. The maximum-area triangle is an equilateral triangle, not a triangle having the diameter as one of the sides. – ShreevatsaR Sep 30 '16 at 16:45
  • Shouldn't you reset B and C each time you move A based on https://arxiv.org/pdf/1705.11035.pdf? "In contrast to Algorithm 1, each time we move a, we reset b and c, but just like in Algorithm 1, each time we move b,c stays where it is." – pablo Apr 29 '21 at 07:36
  • A new algorithm https://arxiv.org/pdf/1705.11035.pdf not sure how to translate it to python – pablo Apr 30 '21 at 06:54
  • Note, the code was edited to fix a bug. The fix looks correct but an independent confirmation would be nice. See [edit history](https://stackoverflow.com/posts/1621913/revisions) for the original code. – n. m. could be an AI May 15 '22 at 19:27
6

According to this paper, there is a class of convex polygons in which the algorithm cited by ShreevatsaR's answer fails. The paper also proposes a O(n log n) divide and conquer algorithm for solving the problem.

Apparently, the simpler O(n2) algorithm in which you move points B and C for all A is still valid.

tomasf
  • 63
  • 1
  • 6
  • Since this is not responding to the question itself, it should probably go as a comment on the answer by @ShreevatsaR – B. Eckles Jun 02 '17 at 19:26
  • 2
    @B.Eckles Sorry, it's my first time contributing. Unfortunately, I don't have enough reputation to comment on the answer. And since the paper linked proposed an algorithm to solve the problem, I thought that my post qualified as an answer. – tomasf Jun 02 '17 at 19:58
  • Makes sense to me! – B. Eckles Jun 05 '17 at 16:50
  • 1
    Thanks for the correction. I somehow missed this earlier. I still haven't tried or understood the counterexample, but it's good to know that it's disputed. – ShreevatsaR Mar 06 '18 at 02:18
4

answering your related question:

The circumcircle of the triangle is not necessarily the minimum bounding circle of the polygon. To see this, consider a very flat isosceles triangle, say with vertices at (0,0), (10,0) and (5,1). The minimum bounding circle has center (5,0) and radius 5, but this circle doesn't touch the vertex at (5,1), so it's not the circumcircle. (The circumcircle has center (5,-12) and radius 13)

edit:

Choosing the smaller of the circumcircle or the circle containing the antipodal points of the diameter of the polygon also doesn't suffice, because it is possible to construct polygons that have points outside the circumcircle of the maximal triangle. Consider the pentagon with vertices at:

(-5,  0)
(-4, -1)
( 5,  0)
( 4,  1)
(-4,  1)

The maximal triangle has vertices at (-4,-1), (5, 0), and (-4, 1). Its circumcircle does not include the point at (-5, 0).

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • How about if I take the smaller of: the Circumcircle (of the largest triangle) or a circle centered between the antipodal points? – willc2 Oct 25 '09 at 19:28
  • Cool, that's a clever counterexample. I suspected that such would exist, but didn't come up with one... BTW I think it's possible to prove that that the minimal bounding circle will either have some pair as diameter, or be the circumcircle of *some* triangle. – ShreevatsaR Oct 25 '09 at 20:47
  • Yes, I believe that that is true. – Stephen Canon Oct 25 '09 at 21:25
0

from http://www.wolframalpha.com/input/?i=triangle The area of the triangle = sqrt((a+b-c)(a-b+c)(-a+b+c)*(a+b+c)) / 4 If you use c connected to the end points of your convex polygon and if a and b would touch your convex polygon you could iterate around your polygon allowing a to grow and b to shrink until you find your maximum area. I would start mid point and try each direction for a larger area.

0

I know this is an old post but by referencing the answer above I was able to modify the code to maximize the area for an n-sided polygon.

Note: The convex hull was found using Emgu OpenCV library. I'm using CvInvoke.ContourArea() method to calculate the given area of a polygon. This is written in C#. It also assumes that the points are arranged in clockwise order. This can be specified in the method CvInvoke.ConvexHull().

private PointF[] GetMaxPolygon(PointF[] convexHull, int vertices)
{
         // validate inputs
        if(convexHull.Length < vertices)
        {
         return convexHull;
        }
        int numVert = vertices;
        // triangles are the smalles polygon with an area.
        if (vertices < 3)
           numVert = 3;        

        PointF[] best = new PointF[numVert]; // store the best found
        PointF[] next = new PointF[numVert];  // test collection of points to compare
        PointF[] current = new PointF[numVert]; // current search location.

        int[] indexes = new int[numVert]; // map from output to convex hull input.
        int[] nextIndices = new int[numVert];

        //starting values 0,1,2,3...n
        for(int i = 0; i < numVert; i++)
        {
            best[i] = convexHull[i];
            next[i] = convexHull[i];
            current[i] = convexHull[i];
        }

        // starting indexes 0,1,2,3... n
        for(int i = 0; i < numVert; i++)
        {
            nextIndices[i] = i;
            indexes[i] = i;                
        }

        //  starting areas are equal.
        double currentArea = GetArea(current);
        double nextArea = currentArea;
        int exitCounter = 0;

        while(true)
        {
            // equivelant to n-1 nested while loops 
            for(int i = numVert - 1; i > 0; i--)
            {
                while (exitCounter < convexHull.Length)
                {
                    // get the latest area
                    currentArea = GetArea(current);
                    nextIndices[i] = (nextIndices[i] + 1) % convexHull.Length;
                    next[i] = convexHull[nextIndices[i]]; // set the test point
                    nextArea = GetArea(next);
                    if (currentArea <= nextArea) // compare.
                    {
                         indexes[i]= (indexes[i]+1) % convexHull.Length;
                         current[i] = convexHull[indexes[i]];
                         currentArea = GetArea(current);
                         exitCounter++; // avoid infinite loop.
                    }
                    else //stop moving that vertex
                    {
                        for(int j = 0; j< numVert; j++)
                        {
                            nextIndices[j] = indexes[j];
                            next[j] = convexHull[indexes[j]];//reset values.

                        }
                        break;
                    }
                }
            }

            // store the best values so far.  these will be the result.
            if(GetArea(current)> GetArea(best))
            {
                for (int j = 0; j < numVert; j++)
                {
                    best[j] = convexHull[indexes[j]];
                }
            }
            // The first index is the counter.  It should traverse 1 time around.
            indexes[0] = (indexes[0] + 1) % convexHull.Length;

            for(int i = 0; i < vertices-1;i++)
            {
                if(indexes[i] == indexes[i+1])// shift if equal.
                {
                    indexes[i + 1] = (indexes[i + 1] + 1) % convexHull.Length;
                }
            }

            //set new values for current and next.
            for(int i = 0; i < numVert; i++)
            {
                current[i] = convexHull[indexes[i]];
                next[i] = convexHull[indexes[i]];
            }

            // means first vertex finished traversing the whole convex hull.
            if (indexes[0] == 0)
                break;


        }
        return best;
}

The area method used. This could change depending on what is needed to maximize.

private double GetArea(PointF[] points)
{
    return CvInvoke.ContourArea( new Emgu.CV.Util.VectorOfPointF(points),false);
}
Felix Castor
  • 1,598
  • 1
  • 18
  • 39