ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

最小圆覆盖

2022-01-25 21:31:25  阅读:171  来源: 互联网

标签:p2 p3 p1 覆盖 point 最小 边界点 枚举


最小圆覆盖

平面上有多个点,求最小圆,能覆盖所有点。

随机增量法

先说说增量法。

增量法适用于一种算法,子问题往往是可以一层处理转移到原问题的。

\[T(n) = T(n-1)+g(n) \]

对增量法进行随机化,可以避免由于数据构造造成的最复杂情况出现。

做法1 暴力枚举

目标是求最小圆,能包括所有点。

最暴力的做法就是枚举三个点作为圆边界上的点,三点定圆,取最大圆。

为什么最大圆就是呢?

如果存在点不在当前最大圆中,那么要囊括这个点,这个当前最大圆必须扩大自身,然后去包含它。(直观理解一下。。。三点定住了一个圆,圆必须加大半径,才可以自由一下。。。)

这样复杂度即是O(n3)

做法2 随机增量

那么我们如果一个点一个点去加入圆中,或者说对圆进行扩张,就能省不少复杂度。

首先我们对点进行打乱。(随机增量)

首先选择第一个点作为初始圆O(半径为0)【也就是只有一个点的情况】。

然后三重循环。

  1. 枚举每一个点i,作为第一边界点。

    若在当前圆内则无事发生,continue。(不进入循环2)

    否则舍弃当前圆,将i点定为第一边界点,并和第一个点构成直径的圆作为当前圆O,并进入循环2。

  2. 枚举i之前的每个点j,作为第二边界点。

    若在当前圆内则无事发生,continue。(不进入循环3)

    否则j定为第二边界点,将i,j构成直径的圆作为当前圆O进入循环3。

  3. 枚举j之前的每个点k,作为第三边界点。

    若在当前圆内则无事发生。

    否则k定为第三边界点,三点定圆,此圆作为当前圆O。

以上就是算法步骤。

原理解释如下

考虑第一重循环,假设我们在i-1之前的点通过以上步骤完成了最小圆覆盖得到O,但i点却不在O内,那么我们就必须重新构造圆,而且,i一定在新O的边界上(做法1中的“圆扩张理论”),因此将i作为第一边界点。

既然圆已经更新了,那要寻找第二更新点

考虑第二重循环,如果点j不在新圆中,说明j一定在扩张后的圆中,并且为边界点,作为第二边界点。

第三重循环,找到不在新圆中的一个点,也一定为第三边界点,三点定一个圆。

由上,三重循环定圆。

直接上代码。

#include <bits/stdc++.h>
using namespace std;
#define eps 1e-12
struct point
{
    double x, y;
}a[100010], o;
double r;
double dis(point a, point b)
{
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
point get_o(point p1, point p2, point p3)
{
    double a1, b1, c1, a2, b2, c2;
    a1 = 2 * (p1.x - p2.x);
    b1 = 2 * (p1.y - p2.y);
    c1 = (p1.x * p1.x - p2.x * p2.x + p1.y * p1.y - p2.y * p2.y);
    a2 = 2 * (p1.x - p3.x);
    b2 = 2 * (p1.y - p3.y);
    c2 = (p1.x * p1.x - p3.x * p3.x + p1.y * p1.y - p3.y * p3.y);
    point ans;
    ans.x = (b2 * c1 - b1 * c2) / (a1 * b2 - b1 * a2);
    ans.y = (c1 * a2 - c2 * a1) / (b1 * a2 - a1 * b2);
    return ans;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i].x >> a[i].y;
    random_shuffle(a + 1, a + n + 1);
    o = a[1], r = 0;
    for (int i = 2; i <= n; ++i)
    {
        if (dis(o, a[i]) - r < eps) 
            continue;
        o.x = (a[1].x + a[i].x) / 2;
        o.y = (a[1].y + a[i].y) / 2;
        r = dis(a[1], a[i]) / 2;
        for (int j = 1; j < i; ++j)
        {
            if (dis(o, a[j]) - r < eps) 
                continue;
            o.x = (a[i].x + a[j].x) / 2;
            o.y = (a[i].y + a[j].y) / 2;
            r = dis(a[i], a[j]) / 2;
            for (int k = 1; k < j; ++k)
            {
                if (dis(o, a[k]) - r < eps)
                    continue;
                o = get_o(a[i], a[j], a[k]);
                r = dis(o, a[i]);
            }
        }
    }
    printf("%.10f\n", r);
    printf("%.10f %.10f", o.x, o.y);
    return 0;
}

我在自己学习过程中其实产生了疑问。

Q:每次只是取点构圆,会不会出现取了后面的点三点构圆而使前面的点反而不在圆内了?

A:不会。从理论上讲,每次取的点都是不在当前圆内,都一定是边界点,所以三点构圆最大化。

而且假如存在四个点ABCD,当前边界点为D和C。取ACD构圆不包含B,取BCD构圆不包含A,那么取ABC三点构圆的时候就已经包含D了,和边界点选取不符合。如图。

image

因此,可以放心大胆用这种扩张方法。

Q:三点共圆求法

A:emmm,三个点列方程求解一下,咕咕咕咕。

Q:复杂度保证

A:第一重循环枚举i,第二重循环枚举j,由于可能在圆内,复杂度为

\[\sum_1^iO(i)g(i) \]

其中g(i)为第三重循环的复杂度

每个点被取为边界点的可能性为\(\frac3i\)

\[g(i)=\frac3i \]

\[T(n)=O(n) \]

标签:p2,p3,p1,覆盖,point,最小,边界点,枚举
来源: https://www.cnblogs.com/0CarryT0/p/15844538.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有