给定一组线段,查找面积最大的矩形

时间:2019-03-02 03:05:54

标签: algorithm data-structures time-complexity big-o dynamic-programming

想象一下,我给了您一组[(x1, y1), (x2, y2)]形式的线段。我们有两个定义线段的点。就我们的目的而言,此段始终为水平或垂直。我想找到由线段包围的任何矩形的最大面积。

例如,当给定以下线段集时,结果应为绿色阴影区域:

enter image description here

到目前为止,我能想到的唯一解决方案是蛮力-将每对水平段(O(N^2))与每对垂直段(O(N^2))一起检查是否为O(N^4)运行。显然,我们可以通过预先计算哪些段可以放在一起来优化此过程,但这仍将时间复杂度保持在O(N^4)

理想情况下,我正在寻找O(N^2)的解决方案,但是如果您的内容少于O(N^4),请分享!

3 个答案:

答案 0 :(得分:2)

您提供的示例:

![enter image description here

实际上,一旦我们提取并合并仅由交点形成的矩形,就可以简化为以下形式:

---------------------
|                   |
|                   |
|                   |
|                   |
---------           ------------------
        |                            |
        |____________________________|

然后问题就变成了在一个直线(也称为正交)多边形中找到最大的矩形,为此,有大量文献。

答案 1 :(得分:2)

您可以使用行扫描算法解决此问题。

在这种情况下,垂直线是从一组线中添加或删除的,以在向上移动时加以考虑。起点和终点o线都添加到扫描集中,水平线也添加到列表中。 enter image description here

  • 第1步:将一行添加到activeVertical
  • 第2步:将第二行添加到activeVertical
  • 步骤3:第三行添加到activeVertical(注意:它们按X顺序排列)。
  • 步骤4a:将第四行添加到activeVertical
  • 第4b步:找到水平线,是时候创建一个没有 有任何高度
  • 第5步:找到第二条水平线,是时候检查完成前一个矩形的时间

代码(C#)下面。您可以在此处找到有关行扫描算法的更多详细信息:https://en.wikipedia.org/wiki/Sweep_line_algorithm

using System;
using System.Collections.Generic;
using System.Linq;

namespace tt
{
    public class Point
    {
        public Point(double X, double Y)
        {
            this.X = X;
            this.Y = Y;
        }
        public double X { get; set; }
        public double Y { get; set; }
    }
    public class Line
    {
        public Point Start { get; set; }
        public Point End { get; set; }
    }

    public class Rectangle
    {
        public Rectangle()
        { }
        public Rectangle(Point BottomLeft, Point TopRight)
        {
            this.BottomLeft = BottomLeft;
            this.TopRight = TopRight;
        }
        public Point BottomLeft { get; set; }
        public Point TopRight { get; set; }
    }

    public class XComparer : IComparer<Line>
    {
        public int Compare(Line x, Line y)
        {
            return x.Start.X.CompareTo(y.Start.X);
        }
    }

    public class Program
    {
        public static int GetMinIndex(List<Line> Lines, Line Horizontal)
        {
            var xComp = new XComparer();
            int minIndex = Lines.BinarySearch(Horizontal, xComp);
            if (minIndex < 0) minIndex = ~minIndex;
            return minIndex;
        }

        public static int GetMaxIndex(List<Line> Lines, Line Horizontal)
        {
        var xComp = new XComparer();
        int maxIndex = Lines.BinarySearch(new Line() { Start = Horizontal.End }, xComp);
        if (maxIndex < 0) maxIndex = ~maxIndex - 1;
        return maxIndex;
    }
    public static void Main()
    {
        List<Line> lines = new List<Line>();
        lines.Add(new Line() { Start = new Point(0.5, 12.5), End = new Point(10, 12.5)  });
        lines.Add(new Line() { Start = new Point(2.5, 9.5), End = new Point(15.8, 9.5) });
        lines.Add(new Line() { Start = new Point(6, 8.5), End = new Point(16.3, 8.5) });
        lines.Add(new Line() { Start = new Point(3.5, 8.5), End = new Point(3.5, 12.5) });
        lines.Add(new Line() { Start = new Point(7, 4.2), End = new Point(7, 13.8) });
        lines.Add(new Line() { Start = new Point(10, 5.8), End = new Point(10, 14.2) });
        lines.Add(new Line() { Start = new Point(15.6, 0), End = new Point(15.6, 16) });
        lines.Add(new Line() { Start = new Point(1.6, 20), End = new Point(15.6, 20) });

        var activeVertical = new List<Line>();

        SortedList<double, List<Line>> sweepSet = new SortedList<double, List<Line>>();

        foreach (Line oneLine in lines.Where(x => x.Start.X == x.End.X))
        {
            if (!sweepSet.ContainsKey(oneLine.Start.Y)) sweepSet.Add(oneLine.Start.Y, new List<Line>());
            sweepSet[oneLine.Start.Y].Add(oneLine);

            if (!sweepSet.ContainsKey(oneLine.End.Y)) sweepSet.Add(oneLine.End.Y, new List<Line>());
            sweepSet[oneLine.End.Y].Add(oneLine);
        }

        var linesHorizontal = lines.Where(x => x.Start.Y == x.End.Y).OrderBy(x => x.Start.Y).ToList();

        List<Rectangle> rectangles = new List<Rectangle>();
        List<Rectangle> completedRectangles = new List<Rectangle>();
        var xComp = new XComparer();

        int horIndex = 0;
        int sweepIndex = 0;
        while (sweepIndex < sweepSet.Count)
        {
            double y = Math.Min(sweepSet.Keys[sweepIndex], linesHorizontal[horIndex].Start.Y);

            double verValue = linesHorizontal[horIndex].Start.Y;
            //add lines which are influencing
            if (sweepSet.ContainsKey(y))
            {
                foreach (Line oneLine in sweepSet[y].Where(x => x.Start.Y == y))
                {

                    int index = activeVertical.BinarySearch(oneLine, xComp);
                    if (index < 0) index = ~index;
                    activeVertical.Insert(index, oneLine);
               }
            }
            if (y == verValue)
            {
                int minIndex = GetMinIndex(activeVertical, linesHorizontal[horIndex]);
                int maxIndex = GetMaxIndex(activeVertical, linesHorizontal[horIndex]);


                if (minIndex != maxIndex && minIndex < activeVertical.Count && maxIndex < activeVertical.Count)
                {
                    double minX = activeVertical[minIndex].Start.X;
                    double maxX = activeVertical[maxIndex].Start.X;

                    foreach (Rectangle oneRec in rectangles)
                    {
                        if (minX > oneRec.BottomLeft.X) oneRec.BottomLeft.X = minX;
                        if (maxX < oneRec.TopRight.X) oneRec.TopRight.X = maxX;
                        oneRec.TopRight.Y = verValue;
                    }
                    completedRectangles.AddRange(rectangles);
                    rectangles.Clear();


                    rectangles.Add(new Rectangle(new Point(activeVertical[minIndex].Start.X, verValue), new Point(activeVertical[maxIndex].Start.X, verValue)));
                }
                else rectangles.Clear();
            }
            //Cleanup lines which end
            if (sweepSet.ContainsKey(y))
            {
                foreach (Line oneLine in sweepSet[y].Where(x => x.End.Y == y))
                {

                    activeVertical.Remove(oneLine);
                }
            }

            if (y >= verValue)
            {
                horIndex++;
                if (horIndex == linesHorizontal.Count) break;
                if (y == sweepSet.Keys[sweepIndex]) sweepIndex++;
            }
            else
            {
                sweepIndex++;
            }
        }
    }
}

}

答案 2 :(得分:1)

您可以通过扫描找到垂直线和水平线之间的所有交点。按y递增的顺序遍历所有行。维护一个包含所有垂直线(包括y的当前值)的缓冲区。使缓冲区按每个垂直线的x值排序。当您到达每条水平线时,请检查其是否与缓冲区中的任何线相交。最糟糕的情况是,当有O(N ^ 2)个交叉点时。

现在,您有了一个交点列表,以及每条线的交点列表。对于每个水平线,对于每个交叉点,我们都会感兴趣,您可以沿着该交叉点的垂直线向下走多远。将这些值存储在数组中。将这些值分成几对,并将每对的最大值存储在数组中。为每个最大值重复该过程,依此类推。这将构建一个值树,其中叶子是原始值(按原始顺序),并且每个节点都携带任何后代中的最大值。交叉点数量的总成本是线性的。

现在,将每个交叉点都假定为矩形的左下角。对于在其垂直线上的每个交叉点,请查看相交的水平线,并在此线上找到最右边的点,在该点上您至少可以下降到该交叉点。您已经建立了一棵树,使您可以在那条线上的交叉点数量上按时间对数找到该树:从树的顶部开始,如果该子项的值至少与您需要的走数一样远,则向右走,否则往左走。找到此位置后,您将使用该左下角和该水平线为您提供最大的矩形,因此,对每条水平线进行检查都会得到最大的矩形,其中包括该交点为左下角,并针对每个相交重复此操作,您将获得整体上最大的矩形。

如果线形成一个N x N网格,那么对于每个交叉点,您都要以O(log N)的代价检查其上方的O(N)条水平线,因此此阶段的总成本为O(N ^ 3log(N))在最坏的情况下。