Java 联合矩形周长的计算算法

Java 联合矩形周长的计算算法,java,algorithm,geometry,grahams-scan,Java,Algorithm,Geometry,Grahams Scan,我试图计算n个矩形的并集的周长,其中有左下角和右上角的点。每个矩形位于x轴上(每个矩形的左下角是(x,0))。我一直在研究不同的方法来实现这一点,似乎扫描线算法是最好的方法。我也看过格雷厄姆扫描。我的目标是一个O(n logn)算法。老实说,虽然我不知道如何继续,但我希望这里的人能尽最大努力让我哑口无言,并帮助我准确地理解如何完成这一任务 我从我所做的研究中收集了一些东西: 我们需要对这些点进行排序(我不确定我们对它们进行排序的标准) 我们将分裂和征服某些东西(以实现O(logn)) 我们需要计

我试图计算n个矩形的并集的周长,其中有左下角和右上角的点。每个矩形位于x轴上(每个矩形的左下角是(x,0))。我一直在研究不同的方法来实现这一点,似乎扫描线算法是最好的方法。我也看过格雷厄姆扫描。我的目标是一个O(n logn)算法。老实说,虽然我不知道如何继续,但我希望这里的人能尽最大努力让我哑口无言,并帮助我准确地理解如何完成这一任务

我从我所做的研究中收集了一些东西:

我们需要对这些点进行排序(我不确定我们对它们进行排序的标准)

我们将分裂和征服某些东西(以实现O(logn))

我们需要计算交叉口(最好的方法是什么?)

我们需要某种数据结构来保存这些点(可能是二叉树?)


我最终将用Java实现这个算法

该算法需要进行大量精细的案例分析。不是超级复杂,但很难完全正确

假设所有矩形都存储在左下角和右上角(x0,y0,x1,y1)的数组A中。所以我们可以将矩形的任何边表示为一对(e,i),其中e\in{L,R,T,B}表示左,右,上,下边缘,i表示a[i]。将所有对(L,i)放在起始列表S中,并在[i].x0上排序

我们还需要一个扫描线C,这是一个BST的三元组(T,i,d)的顶部边缘和(B,i,d)的底部。这里i是一个矩形索引,d是一个整数深度,如下所述。BST的关键是边的y坐标。最初它是空的

请注意,您可以随时按顺序遍历C,并确定扫描线的哪些部分被矩形隐藏,哪些部分不被矩形隐藏。通过保持深度计数器(初始为零)来执行此操作。从最小y到最大y,当遇到底部边缘时,向计数器添加1。当看到顶部边缘时,递减1。对于计数器为零的区域,扫描线可见。否则它会被一个矩形隐藏

现在你永远不会真正完成整个遍历。相反,您可以通过增量维护深度来提高效率。C中每个三元组的d元素是其上方区域的深度。(C中第一条边下方的区域深度始终为0。)

最后,我们需要一个输出寄存器p。它存储一组多段线(双链接边列表对此很方便),并允许以下形式的查询:“给我所有末端y坐标在[y0..y1]范围内的多段线”。该算法的一个特性是,这些多段线始终有两条横穿扫描线的水平边作为其端点,所有其他边位于扫描线的左侧。此外,没有两条多段线相交。它们是“正在构造”的输出多边形的线段。请注意,输出多边形可能不是简单的,由多个“循环”和洞。“另一个BST将用于P。它最初也是空的

现在算法大致上是这样的。我不想窃取所有计算细节的乐趣

 while there are still edges in S
   Let V = leftmost vertical edge taken from S
   Determine Vv, the intersection of V with the visible parts of C
   if V is of the form (L, i) // a left edge
     Update P with Vv (polylines may be added or joined)
     add (R, i) to S
     add (T, i) and (B, i) to C, incrementing depths as needed
   else // V is of the form (R, i) // a right edge
     Update P with Vv (polylines may be removed or joined)
     remove (T, i) and (B, i) from C, decrementing depths as needed
随着p的更新,您将生成复杂的多边形。最右边的边应闭合最后一个循环

最后,请注意,重合边可能会产生一些棘手的特殊情况。当您遇到这些情况时,请再次发布,我们可以讨论

排序的运行时间当然是O(n logn),但更新扫描线的成本取决于可以重叠的多边形数量:退化情况下的O(n)或整个计算的O(n^2)

祝你好运。我已经实现了这个算法(几年前)和其他一些类似的算法。它们是严格的逻辑案例分析的巨大练习。非常令人沮丧,但当你成功时也会得到回报。

诀窍是首先找到沿x轴的每个线段的最大高度(见上图)。一旦知道这一点,周长就很容易了:

注意:我还没有测试代码,因此可能会出现拼写错误

// Calculate perimeter given the maxY at each line segment.
double calcPerimeter(List<Double> X, List<Double> maxY) {
    double perimeter = 0;

    for(int i = 1; i < X.size(); i++){
        // Add the left side of the rect, maxY[0] == 0
        perimeter += Math.abs(maxY.get(i) - maxY.get(i - 1))

        // add the top of the rect
        perimeter += X.get(i) - X.get(i-1);
    }

    // Add the right side and return total perimeter
    return perimeter + maxY.get(maxY.size() - 1);
}

@RyanVincent我肯定有一些我没有看到,但我看了很多不同的链接。其中绝大多数是关于计算面积的,然后说:你可以修改这个周长算法。不幸的是,我对我们在这里所做的基本原理了解不够,无法修改周长算法eter.可能很有趣?.互联网搜索:“计算联合矩形的周长”。@RyanVincent这是我看过的一个,它是在考虑面积的情况下创建的。“这个想法只用于计算面积,但是,你可以修改它来计算周长。”
double calcUnionPerimeter(Set<Rect> rects){
    // list of x points, with reference to Rect
    List<Entry<Double, Rect>> orderedList = new ArrayList<>();

    // create list of all x points
    for(Rect rect : rects){
        orderedList.add(new Entry(rect.getX(), rect));
        orderedList.add(new Entry(rect.getX() + rect.getW(), rect));
    }

    // sort list by x points 
    Collections.sort(orderedList, new Comparator<Entry<Double,Rect>>(){
        @Override int compare(Entry<Double, Rect> p1, Entry<Double, Rect> p2) {
            return Double.compare(p1.getKey(), p2.getKey());
        }
    });

    // Max PriorityQueue based on Rect height
    Queue<Rect> maxQ = new PriorityQueue<>(orderedList, new Comparator<Rect>(){
        @Override int compare(Rect r1, Rect r2) {
            return Double.compare(r1.getH(), r2.getH());
        }
    }

    List<Double> X = new ArrayList<>();
    List<Double> maxY = new ArrayList<>();

    // loop through list, building up X and maxY
    for(Entry<Double, Rect> e : orderedList) {
        double x = e.getKey();
        double rect = e.getValue();
        double isRightEdge = x.equals(rect.getX() + rect.getW());

        X.add(x);
        maxY.add(maxQ.isEmpty() ? 0 : maxQ.peek().getY());

        if(isRightEdge){ 
            maxQ.dequeue(rect);   // remove rect from queue
        } else {         
            maxQ.enqueue(rect);   // add rect to queue
        }
    }

    return calcPerimeter(X, maxY);
}