找到形成凸多边形的最大点子集

时间:2014-02-14 11:44:33

标签: algorithm polygon convex-hull convex

我正在寻找一种算法,用于寻找从给定点集形成凸多边形的最大点子集(通过最大数量来表示数量)。 我认为这可能是使用DP解决的,但我不确定。 是否可以在O(n ^ 3)中执行此操作? 实际上我只需要最大子集的大小,因此它不需要有唯一的解决方案

编辑:

只是为了保持这个简单,

给定输入: 2D中的一组点

所需输出:形成凸多边形的最大点数,如示例中输出为5(ABHCD是可能的凸多边形之一)

an example

有一个类似的问题spoj.com/problems/MPOLY可以使用DP在O(N ^ 3)中解决,我的问题是关于那个问题的概括,它不必包含(0,0)

4 个答案:

答案 0 :(得分:4)

我认为我通过扩展Andrew's algorithm for convex hulls想出了一个算法。

最初,我想出了一个O(N ^ 4)算法,但注意到它过于复杂,并将其归结为O(N ^ 2)算法。看起来某个地方的代码中可能存在一个错误,至少在垂直线点的情况下会导致问题。这可能是一种特殊情况(需要更改几行代码),或者算法中存在较大缺陷的迹象。如果它是后者,那么我倾向于说它是NP难的,并提供算法作为启发式。我花了很多时间来编写和调试它。在任何情况下,它似乎在其他情况下工作正常。但是,当正确的算法似乎是O(2 ^ N)时,很难测试正确性。

def maximal_convex_hull(points):
    # Expects a list of 2D points in the format (x,y)


    points = sorted(set(points)) # Remove duplicates and sort
    if len(points) <= 1: # End early
        return points

    def cross(o, a, b): # Cross product
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])


    # Use a queue to extend Andrew's algorithm in the following ways:
    # 1. Start a new convex hull for each successive point
    # 2. Keep track of the largest convex hull along the way
    Q = []
    size = 0
    maximal_hull = None
    for i,p in enumerate(points):
        remaining = len(points) - i + 1
        new_Q = []
        for lower, upper in Q: # Go through the queue
            # Build upper and lower hull similtanously, slightly less efficent
            while len(lower) >= 2 and cross(lower[-2], lower[-1], p) < 0:
                lower.pop()
            lower.append(p)
            while len(upper) >= 2 and cross(upper[-2], upper[-1], p) > 0:
                upper.pop()
            upper.append(p)

            new_size = len(lower) + len(upper)
            if new_size > size: # Check for a new highest maximal convex hull
                size = new_size
                maximal_hull = (lower, upper)


        # There is an odd bug? that when one or both if statements are removed
        #  xqg237.tsp (TSPLIB) gives slightly different solutions and is
        #   missing a number of points it should have.
        #  Seems like if the opposite should be true if anything since they
        #   were intended to be easy optimizations not required code.
            if remaining + new_size >= size: # Don't bother if not enough
                new_Q.append((lower, upper)) # Add an updated convex hulls
        if remaining > size: # Don't bother if not enough
            new_Q.append(([p], [p])) # Add a new convex hull
        Q = new_Q # Update to the new queue

    # Extract and merge the lower and upper to a maximium convex hull
    # Only one of the last points is ommited because it is repeated
    #    Asserts and related variables used for testing
    #    Could just have "return lower[:-1] + list(reversed(upper[:-1]))[:-1]"
    lower, upper = maximal_hull
    max_hull_set = set(lower) | set(lower) | set(upper)
    lower = lower
    upper = list(reversed(upper[:-1]))[:-1]
    max_convex_hull = lower + upper
    assert len(lower) + len(upper) == len(max_hull_set)
    assert max_hull_set == set(max_convex_hull)
    return max_convex_hull

编辑:这个算法不正确,但它是正确算法的灵感来源,因此我将它留在这里。

答案 1 :(得分:3)

这个问题已经在这些比赛中发布:

它的解决方案(O(N 3 )算法)可以在这个页面找到:"USACO DEC08 Problem 'fence' Analysis"由Bruce Merry和Jacob Steinhardt。

以下是尝试解释此算法。也是C ++ 11中此算法的here is my implementation。此代码适用于N <= 250且范围为0的整数坐标1023.在同一行上不应有三个点。以下是Python script,可生成满足这些要求的测试数据。

O(N 2 )算法用于简化问题

简化问题:找到形成凸多边形的最大点子集,并包含给定集合的最左边的点(或者如果有几个最左边的点,则该多边形应该包含最顶层的点)。

要解决这个问题,我们可以通过有向线段连接每对点,然后(隐式地)将每个段视为图节点,如图所示:

representing point set as a graph

此处父节点和相应的段具有蓝色。与其左子(红色)对应的线段从&#34;父母&#34;的末尾开始。段,它是相对于&#34; parent&#34;的方向最不可能左转的线段。分割。对应于其右子(绿色)的线段从&#34;父母&#34;开始。段,它也是相对于&#34; parent&#34;的方向最不可能左转的线段。段。

任何以最左边点结束的段对应于&#34; leaf&#34;节点,即它没有子节点。图的这种构造保证没有周期,换句话说,该图是DAG。

现在要找到最大的凸点子集,就可以在此图中找到最长的路径。并且在时间O(E)中使用动态编程算法可以找到DAG中的最长路径,其中E是图中边缘的数量。由于每个节点只有2个输出边,每个边对应一对点,因此可以在O(N 2 )时间内解决这个问题。

如果我们预处理输入数据,按角度从同一点开始对有向线段进行排序,这一切都可以实现。这允许在图中执行深度优先遍历。我们应该记住从每个处理过的节点开始的最长路径,这样每个图形边缘最多访问一次,并且我们有O(E)= O(N 2 )时间算法(忽略预处理时间为现在)。空间要求也是O(N 2 ),因为我们需要存储每对点的排序方向,并且记忆使用每个节点一个值(也是一对点)。

这是动态编程算法的C ++实现:

unsigned dpStep(ind_t i_left, ind_t from, ind_t to_ind)
{
    ind_t child = sorted[from][to_ind];

    while (child < i_left)
        child = sorted[from][++to_ind];

    if (child == i_left)
        return 1;

    if (auto v = memorize[from][child])
        return v;

    const auto x = max(dpStep(i_left, child, lefts[from][child]) + 1,
                       dpStep(i_left, from, static_cast<ind_t>(to_ind + 1)));

    memorize[from][child] = static_cast<ind_t>(x);
    return x;
}

输入参数是最左边的点和形成当前线段的一对点(其中段的起始点是直接给出的,但是终点是作为按角度的点阵列排序的索引给出的)。单词&#34; left&#34;在这个代码片段中稍微过度使用:它表示最左边的点(i_left)和预处理的数组包含图的左子项(lefts[][])。

O(N 3 )算法

一般问题(最左边的点不固定)可以通过这种方式解决:

  1. 按左右方向排序点
  2. 预处理点以获得两个数组:(a)每个点按相对于彼此点的方向排序,以及(b)线段末端的第一个数组中的位置使得相对于&#的方向的左转最少34;父&#34;段。
  3. 使用每个点作为最左边的点,找到最长的凸多边形,其中&#34;简化&#34;算法
  4. 定期修剪&#34;最左边&#34;左边的所有点。指出预处理数组。
  5. 选择步骤3中找到的最长路径。
  6. 步骤4是可选的。它不会改善O(N 3 )时间复杂度。但它通过排除不需要的点来略微提高DP算法的速度。在我的测试中,这提高了33%的速度。

    预处理有多种选择。我们可以计算每个角度(使用atan2)并对它们进行排序,就像Bruce Merry和Jacob Steinhardt在示例代码中所做的那样。这是最简单的方法,但atan2可能过于昂贵,尤其是对于较小的点集。

    或者我们可以排除atan2并对切线进行排序而不是角度,就像在我的实现中一样。这有点复杂但更快。

    这两种替代方案都需要O(N 2 log N)时间(除非我们使用一些分布排序)。第三种选择是使用众所周知的计算几何方法(排列和对偶)来获得O(N 2 )预处理。但是我认为它对我们的问题没有用处:对于较小的点集,它可能太慢了,因为大的常数因子,对于较大的点集,它可能会提高速度,但是太微不足道,因为第3步将会大大超过第2步。此外,实施起来要困难得多。

    其他一些结果:我尝试实现迭代DP而不是递归;这没有明显改变速度。此外,我尝试一次执行两次DP搜索,交错每次搜索的步骤(类似于光纤或软件中实现的超线程);这可能会有所帮助,因为DP主要由具有高延迟的内存访问组成,并阻止CPU吞吐量的充分利用;结果不是很令人印象深刻,只有大约11%的速度提升,最有可能的是真正的HyperThreading它会更快。

答案 2 :(得分:1)

这是我的动态编程O(N ^ 4)算法,来自Nuclearman发布的Andrew算法的想法,我仍在寻找O(N ^ 3)算法

upper_hull(most left point, previous point, current point)
{
    ret = 0
    if (current point != most left point)   /* at anytime we can decide to end the upper hull and build lower_hull from current point ending at most left point */
        ret = min(ret,lower_hull(most left point, -1, current point)) 
    for all point on the right of current point /* here we try all possible next point that still satisfy the condition of convex polygon */
        if (cross(previous point,current point,next point) >= 0) max(ret,1+upper_hull(most left point, current point, next point))
    return ret;
}

lower_hull(most left point, previous point, current point)
{
    if (current point == most left point) return 0;
    ret = -INF /* it might be impossible to build a convex hull here, so if it does it will return -infinity */
    for all point on the left of current point and not on the left of most left point
        if (cross(previous point,current point,next point) >= 0) max(ret,1+lower_hull(most left point, current point, next point))
    return ret;
}

首先根据x轴对点进行排序,然后按y轴排序,然后尝试所有点作为最左点运行upper_hull(p,-1,p),请告诉我此算法是否有任何缺陷

答案 3 :(得分:0)

您可以使用delaunay三角剖分并移除最长边和顶点。我使用类似的算法来找到凹形船体。您可以根据人口数据@ http://www.phpdevpad.de/geofence找到jan示例。我还写了一个php类凹壳@ phpclasses.org。