分治法解决凸包问题(快包)

假设集合 S S S是平面上 n > 1 n>1 n>1个点 p 1 ( x 1 , y 1 ) , . . . , p n ( x n , y n ) p_1(x_1,y_1),...,p_n(x_n,y_n) p1(x1,y1),...,pn(xn,yn)构成的。我们还假设这些点是按照它们的 x x x轴坐标升序排列的,如果 x x x轴坐标相同,则按照 y y y轴坐标升序排列。
算法步骤:

  1. 排序后找到最左边的点 p 1 p_1 p1和最右边的点 p 2 p_2 p2(因为这两个点经过证明是凸包的顶点)。

  2. 利用向量 p 1 p n → \overrightarrow{p_1p_n} p1pn 将点分成上包 S 1 S_1 S1和下包 S 2 S_2 S2,对于点 p r p_r pr p r p_r pr不能是 p 1 p n → \overrightarrow{p_1p_n} p1pn 上的点,因为 p 1 p n → \overrightarrow{p_1p_n} p1pn 上的点不可能再为凸包的顶点):

    • 如果 p 1 p n → × p 1 p r → > 0 \overrightarrow{p_1p_n}\times\overrightarrow{p_1p_r}>0 p1pn ×p1pr >0,则 p 1 p r → \overrightarrow{p_1p_r} p1pr p 1 p n → \overrightarrow{p_1p_n} p1pn 的左侧,即 p r p_r pr是上包 S 1 S_1 S1内的点。
    • 如果 p 1 p n → × p 1 p r → < 0 \overrightarrow{p_1p_n}\times\overrightarrow{p_1p_r}<0 p1pn ×p1pr <0,则 p 1 p r → \overrightarrow{p_1p_r} p1pr p 1 p n → \overrightarrow{p_1p_n} p1pn 的右侧,即 p r p_r pr是下包 S 2 S_2 S2内的点。
      注:这里进行的是向量的叉积运算,设二维向量 a ⃗ = ( a 1 , a 2 ) \vec{a}=(a_1,a_2) a =(a1,a2) b ⃗ = ( b 1 , b 2 ) \vec{b}=(b_1,b_2) b =(b1,b2),那么 a × b = a 1 b 2 − a 2 b 1 a\times b=a_1b_2-a_2b_1 a×b=a1b2a2b1(具体内容见高数的解析几何部分)
      在这里插入图片描述
  3. 将点分类到上包和下包后,分别在其中找出顶点 p m a x p_{max} pmax,它是使三角形 △ p 1 p m a x p n \triangle p_1p_{max}p_n p1pmaxpn面积最大的点。如果 q 1 ( x 1 , y 1 ) , q 2 ( x 2 , y 2 ) , q 3 ( x 3 , y 3 ) q_1(x_1,y_1),q_2(x_2,y_2),q_3(x_3,y_3) q1(x1,y1),q2(x2,y2),q3(x3,y3)是平面上的任意三个点,那么三角形 △ q 1 q 2 q 3 \triangle q_1q_2q_3 q1q2q3的面积等于下面这个行列式绝对值的二分之一
    ∣ x 1 y 1 1 x 2 y 2 1 x 3 y 3 1 ∣ = x 1 y 2 + x 3 y 1 + x 2 y 3 − x 3 y 2 − x 2 y 1 − x 1 y 3 \left| \begin{array}{cccc} x_1 & y_1 & 1 \\ x_2 & y_2 & 1 \\ x_3 & y_3 & 1 \end{array} \right|=x_1y_2+x_3y_1+x_2y_3-x_3y_2-x_2y_1-x_1y_3 x1x2x3y1y2y3111=x1y2+x3y1+x2y3x3y2x2y1x1y3

  4. 找到 p m a x p_{max} pmax之后,这里以上包举例,可以证明

    • p m a x p_{max} pmax是上包的顶点
    • 包含在 △ p 1 p m a x p n \triangle p_1p_{max}p_n p1pmaxpn之中的点不可能是上包的顶点(因此在后面不必考虑)。
    • 同时位于 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn 的左边的点是不存在的
  5. 然后利用向量 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax 找到位于 p 1 p m a x → \overrightarrow{p_1p_{max}} p1pmax 左侧的点,然后返回第3步的做法找到顶点;利用向量 p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn 找到位于 p m a x p n → \overrightarrow{p_{max}p_n} pmaxpn 右侧的点,然后返回第3步的做法找到顶点,依次类推,最后找出上包的所有顶点,下包的做法也类似。

实现代码(C++)

#include<vector>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define Up true
#define Down false
// 输出包围pts的凸包

void helper(vector<pair<int,int>> pts,pair<int,int>A,pair<int,int>B,bool up,vector<pair<int,int>>& res) {
	if (pts.size()==0) return;
	vector<pair<int,int>> LeftPts;
    vector<pair<int,int>> RightPts;
	pair<int,int> AB = make_pair(B.first-A.first, B.second-A.second);
	int maxArea = 0;
	pair<int, int> Pmax;
    bool flag = false;
    if(up){
        // 找到使得APmaxB面积最大的点
		for(auto p:pts) {
            int Area = abs(A.first*p.second + B.first*A.second + p.first*B.second - B.first*p.second - p.first*A.second - A.first*B.second)/2; 
            if(Area > maxArea) {
                Pmax = p;
                maxArea = Area;
                flag = true;
            }
		}
        pair<int,int> APmax = make_pair(Pmax.first-A.first, Pmax.second-A.second);
        pair<int,int> PmaxB = make_pair(B.first-Pmax.first, B.second-Pmax.second);
        for(auto p:pts) {
            pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
            pair<int,int> PmaxP = make_pair(p.first-Pmax.first, p.second-Pmax.second);
            // 找出APmax左边的点
            if((APmax.first * AP.second - AP.first * APmax.second) > 0) {
                LeftPts.push_back(p);   
            }
            // 找出PmaxB右边的点
            else if(PmaxB.first * PmaxP.second - PmaxP.first * PmaxB.second > 0) {
                RightPts.push_back(p);
            }
        }
	}
	
    else {
        // 找到使得APmaxB面积最大的点
        for(auto p:pts) {
            int Area = abs(A.first*p.second + B.first*A.second + p.first*B.second - B.first*p.second - p.first*A.second - A.first*B.second)/2; 
            if(Area > maxArea) {
                Pmax = p;
                maxArea = Area;
                flag = true;
            }
		}
        pair<int,int> APmax = make_pair(Pmax.first-A.first, Pmax.second-A.second);
        pair<int,int> PmaxB = make_pair(B.first-Pmax.first, B.second-Pmax.second);
        for(auto p:pts) {
            pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
            pair<int,int> PmaxP = make_pair(p.first-Pmax.first, p.second-Pmax.second);
            // 找出APmax左边的点
            if((APmax.first * AP.second - AP.first * APmax.second) < 0) {
                LeftPts.push_back(p);   
            }
            // 找出PmaxB右边的点
            else if(PmaxB.first * PmaxP.second - PmaxP.first * PmaxB.second < 0) {
                RightPts.push_back(p);
            }
        }

	}
    if(flag) {
        res.push_back(Pmax);
        helper(LeftPts, A, Pmax, up, res);
        helper(RightPts, Pmax, B, up, res);
    }


}
void quickHull(vector<pair<int,int>> pts, vector<pair<int,int>>&res) {
	// 少于三点直接返回点集合
	if(pts.size()<=3) {
		res = pts;
	}
	// 点按x坐标排序
	sort(pts.begin(),pts.end());
	// 最左边的点和最右边的点就是凸包的顶点
    pair<int,int> A = pts.front();
    pair<int,int> B = pts.back();
    res.push_back(A);
	
	
    vector<pair<int, int>> UpPts;
    vector<pair<int, int>> DownPts;
    pair<int,int> AB = make_pair(B.first-A.first, B.second-A.second);
    for(auto p:pts) {
        pair<int,int> AP = make_pair(p.first-A.first, p.second-A.second);
        // 找出上凸包的里面的点
        if((AB.first * AP.second - AP.first * AB.second) > 0) {
            UpPts.push_back(p);
        }
        // 找出下凸包的里面的点
        else {
            DownPts.push_back(p);
        }
    }
	helper(UpPts,A,B,Up,res);
	// 找出下凸包的顶点
	helper(DownPts,A,B,Down,res);
    res.push_back(B);
}
int main() {
    vector<int> xpts = {0, 1, 5, 12, 17, 21, 24, 6, 19, 3, 9, 15, 7, 11, 6, 16};
    vector<int> ypts = {4, 9, 11, 12, 12, 9, 7, 0, 1, 8, 9, 8, 6, 6, 2, 2};
    vector<pair<int, int>> pts;
    for (int i = 0; i < xpts.size(); ++i) {
        pts.push_back(make_pair(xpts[i], ypts[i]));
    }
    vector<pair<int, int>> res;
    quickHull(pts, res);
    for(auto r:res) {
        cout << "(" << r.first << "," << r.second << ")" << endl;
    }
    system("pause");
    return 0;
}

绘图程序(python):

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns

ptsdata=[[0, 4],[1, 9],[5, 11],[12, 12],
[17, 12],
[21, 9],
[24, 7],
[6, 0],
[19, 1],
[3, 8],
[9, 9],
[15, 8],
[7, 6],
[11, 6],
[6, 2],
[16, 2]]
data=pd.DataFrame(ptsdata).rename(columns={0:'x',1:'y'})

res=[(0,4),(24,7),(12,12),(1,9),(5,11),(17,12),(19,1),(6,0)]
res.sort()

def frange(start, stop, step):
     x = start
     while x < stop:
        yield x
        x += step

fig, ax = plt.subplots()
sns.scatterplot(x='x',y='y',data=data)
uppts=[]
downpts=[]
pts=[]
A=res[0]
B=res[-1]
uppts.append(A)
downpts.append(A)
AB=(B[0]-A[0],B[1]-A[1])
for i in range(len(res)):
    AP=(res[i][0]-A[0],res[i][1]-A[1])
    if((AB[0] * AP[1] - AP[0] * AB[1]) > 0):
        uppts.append(res[i])
    elif((AB[0] * AP[1] - AP[0] * AB[1]) < 0):
        downpts.append(res[i])
uppts.append(B)
downpts.append(B)
pts=uppts+downpts
for i in range(len(pts)-1):
    X=np.array(list(frange(pts[i][0],pts[i+1][0],0.01)))
    Y=pts[i][1]+((pts[i][1]-pts[i+1][1])/(pts[i][0]-pts[i+1][0]))*(X-pts[i][0])
    ax.plot(X,Y,'b-')
plt.show()

绘图结果:
在这里插入图片描述
相关题目:
POJ1113题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值