凸包算法-GrahamScan+暴力+分治

RT。求平面上点集的凸包。

1. GrahamScan算法,《算法导论》上的例子,先找到y最小的点O,以O建立极坐标,其它点按极角排序后再从头开始扫描(配合stack实现)。

2.BruteForce算法,依赖定理:如果一个点在平面上某三个点组成的三角形内,那么这个点不可能是凸包上的点。

所以暴力的思路是平面上的点每4个进行枚举,并判断是否满足定理,若满足,则删除这个点继续找;一直找到没有满足定理的点为止。。

3.DivideAndConquer思路:有很多种,这里我实现的只是严格意义上最后一步的分治,基于递归的分治暂时还没想明白;

思路即:首先找点集x-坐标的中位数(排序法O(nlogn),或随机法更快O(n)),将每次将点集分为左右两部分。

分别递归求左右部分的凸包,这里我就是用Graham求的凸包。这样会过滤掉许多内部点,将左边凸包上的点序列称为left,右边凸包上的点序列称为right。

right按顺时针和逆时针分成两部分right1和right2(以y最大和y最小的点为界),得到这三个极角有序列表后,合并这三个有序表为一个list(merge的操作O(n),原理类似归排的merge)

对新list直接进行GrahamScan,只需过滤掉极少的点即可得到大凸包。

ConvexHull.py:

#coding=utf-8
import math
import numpy
import pylab as pl
#画原始图
def drawGraph(x,y):
    pl.figure(1)
    pl.subplot(131) #1
    pl.title("ConvexHull-GrahamScan")
    pl.xlabel("x axis")
    pl.ylabel("y axis")
    pl.plot(x,y,'ro')
    pl.subplot(132) #2
    pl.title("ConvexHull-BruteForce")
    pl.xlabel("x axis")
    pl.ylabel("y axis")
    pl.plot(x,y,'ro')
    pl.subplot(133) #3
    pl.title("ConvexHull-DivideConquer")
    pl.xlabel("x axis")
    pl.ylabel("y axis")
    pl.plot(x,y,'ro')
#画凸包
def drawCH(CHQ):
    x,y=[],[]
    for p in CHQ:
        x.append(p[0])
        y.append(p[1])
    pl.plot(x,y,color='blue',linewidth=2)
    pl.plot(x[-1],y[-1],x[0],y[0])
    lastx=[x[-1],x[0]]
    lasty=[y[-1],y[0]]
    pl.plot(lastx,lasty,color='blue',linewidth=2)   #画最后一条封闭线
#存点的矩阵,每行一个点,列0->x坐标,列1->y坐标,列2->代表极角
def matrix(rows,cols):
    cols=3
    mat = [[0 for col in range (cols)]
              for row in range(rows)]
    return mat
#Graham用的叉积
def crossMut(stack,p3):
    p2=stack[-1]
    p1=stack[-2]
    vx,vy=(p2[0]-p1[0],p2[1]-p1[1])
    wx,wy=(p3[0]-p1[0],p3[1]-p1[1])
    return (vx*wy-vy*wx)
#Graham扫描法O(nlogn),mat是经过极角排序的点集
def GrahamScan(x,y):
    #print mat
    max_num=len(x)
    mat = matrix(max_num,3) #根据点数申请矩阵,mat[i][2]代表极角
    minn = 300
    for i in range(max_num):    #存点集
        mat[i][0],mat[i][1]=x[i],y[i]
        if y[i]<minn: #(x[tmp],y[tmp])-->y轴最低坐标
            minn=y[i]
            tmp = i
    d = {}  #利用字典的排序
    for i in range(max_num):    #计算极角
        if (mat[i][0],mat[i][1])==(x[tmp],y[tmp]):mat[i][2]=0
        else:mat[i][2]=math.atan2((mat[i][1]-y[tmp]),(mat[i][0]-x[tmp]))
        d[(mat[i][0],mat[i][1])]=mat[i][2]
    lst=sorted(d.items(),key=lambda e:e[1]) #按极角由小到大排序
    for i in range(max_num):    #更新mat为排序后的矩阵
        ((x,y),eth0)=lst[i]
        mat[i][0],mat[i][1],mat[i][2]=x,y,eth0
    points=len(mat) #点数
    stack=[]
    stack.append((mat[0][0],mat[0][1])) #push p0
    stack.append((mat[1][0],mat[1][1])) #push p1
    stack.append((mat[2][0],mat[2][1])) #push p2
    for i in range(3,points):
        #print stack
        p3=(mat[i][0],mat[i][1])
        while crossMut(stack,p3)<0:stack.pop()
        stack.append(p3)
    return stack
#蛮力法判断叉积,返回ABxAC的向量中j的系数
def Product(A,B,C):
    return (B[0]-A[0])*(C[1]-A[1])-(C[0]-A[0])*(B[1]-A[1])
#判断P是否在三角形ABC中
def isInTriangle(A,B,C,P):
    if Product(A,B,P)>=0 and Product(B,C,P)>=0 and Product(C,A,P)>=0:
        return 1
    return 0

#凸包蛮力算法
def BruteForce(x,y):
    max_num=len(x)
    mat = matrix(max_num,3) #根据点数申请矩阵,mat[i][2]代表访问标记
    for i in range(max_num):    #存点集
        mat[i][0],mat[i][1],mat[i][2]=x[i],y[i],1
    #任选4个,即C(10,4)的功能
    for a in range(0,max_num-3):
        for b in range(a+1,max_num-2):
            for c in range(b+1,max_num-1):
                for d in range(c+1,max_num):
                    #如果在三角形中,则删除内部的点
                    #if 0 in (mat[a][2],mat[b][2],mat[c][2],mat[d][2]):continue
                    if isInTriangle(mat[b],mat[c],mat[d],mat[a]):mat[a][2]=0    #顺时针算一类
                    if isInTriangle(mat[b],mat[d],mat[c],mat[a]):mat[a][2]=0    #逆时针算另一类
                    if isInTriangle(mat[a],mat[c],mat[d],mat[b]):mat[b][2]=0
                    if isInTriangle(mat[a],mat[d],mat[c],mat[b]):mat[b][2]=0
                    if isInTriangle(mat[a],mat[b],mat[d],mat[c]):mat[c][2]=0
                    if isInTriangle(mat[a],mat[d],mat[b],mat[c]):mat[c][2]=0
                    if isInTriangle(mat[a],mat[b],mat[c],mat[d]):mat[d][2]=0
                    if isInTriangle(mat[a],mat[c],mat[b],mat[d]):mat[d][2]=0
    #后处理,按极角排序,以便输出图形
    #print mat
    newmat = matrix(max_num,3)  #newmat[i][2]是极角,和mat[i][2]不同
    pos = 0 #记录newmat行数
    minn = 300
    for i in range(len(mat)):
        if mat[i][2]==1:
            if mat[i][1]<minn: #(mat[tmp][0],mat[tmp][1])-->y坐标最低的点
                minn=mat[i][1]
                tmp = i
            newmat[pos][0],newmat[pos][1]=mat[i][0],mat[i][1]
            pos+=1
    d={}    #排序字典
    for i in range(pos):    #计算极角
        #print newmat[i][0],newmat[i][1],newmat[i][2]
        if (newmat[i][0],newmat[i][1])==(mat[tmp][0],mat[tmp][1]):newmat[i][2]=0
        else:newmat[i][2]=math.atan2((newmat[i][1]-mat[tmp][1]),(newmat[i][0]-mat[tmp][0]))
        d[(newmat[i][0],newmat[i][1])]=newmat[i][2]
    lst=sorted(d.items(),key=lambda e:e[1]) #按极角由小到大排序
    #print lst
    stack=[]
    for i in range(pos):    #更新mat为排序后的矩阵
        ((x,y),eth0)=lst[i]
        stack.append((x,y))
    return stack

#凸包分治算法
def DivideConquer(x,y):
    max_num=len(x)
    d={}
    for i in range(max_num):
        d[(x[i],y[i])]=x[i]
    #print d
    lst=sorted(d.items(),key=lambda e:e[1]) #首先将点集按x坐标排序
    #for k in lst:print k
    #print
    left=len(lst)/2  #左边的个数
    right=len(lst)-left #右边的个数
    #print left,right
    x,y=[],[]   #画左半部分
    for i in range(left):
        ((xi,yi),noUse)=lst[i]
        x.append(xi)
        y.append(yi)
    CHQ_L=GrahamScan(x,y)
    #print CHQ_L
    x,y=[],[]   #画右半部分
    for i in range(right):
        ((xi,yi),noUse)=lst[left+i]
        x.append(xi)
        y.append(yi)
    CHQ_R=GrahamScan(x,y)
    for i in range(len(CHQ_R)): #找到y最高的位置high
        if i==len(CHQ_R)-1 or CHQ_R[i][1]>CHQ_R[i+1][1]:
            high = i
            break
    #将右半部分分成两个序列
    sq2=[]
    for i in range(high+1):
        sq2.append((CHQ_R[i][0],CHQ_R[i][1]))
    #print sq2
    sq3=[]
    for i in range(len(CHQ_R)-1,high,-1):
        sq3.append((CHQ_R[i][0],CHQ_R[i][1]))
    #print sq3
    merge = CHQ_L+sq2+sq3   #合并操作,应该按顺序来merge
    #print merge
    x,y=[],[]   #画左半部分
    for i in range(len(merge)):
        ((xi,yi))=merge[i]
        x.append(xi)
        y.append(yi)
    CHQ=GrahamScan(x,y)
    return CHQ

#main
max_num = 30 #最大点数
x=100*numpy.random.random(max_num)#[0,100)
y=100*numpy.random.random(max_num)
drawGraph(x,y)  #原始图

CHQ=GrahamScan(x,y)
pl.subplot(131)
drawCH(CHQ)     #Graham凸包

CHQ=BruteForce(x,y)
pl.subplot(132)
drawCH(CHQ)     #BruteForce凸包

CHQ=DivideConquer(x,y)
pl.subplot(133)
drawCH(CHQ)     #DivideConquer凸包
pl.show()

ps:凸包的一个应用是求平面最远点对,源自定理:平面最远的点对一定均在凸包上。

时间: 2024-10-27 19:37:28

凸包算法-GrahamScan+暴力+分治的相关文章

凸包算法

先理解下凸包 说凸包首先要说凸性的定义,简单点说就是平面邻域中任意两点所在的线段上的点都在该邻域中,则该邻域具有凸性.简单推敲一下,就可以发现如果邻域中存在一阶导数不连续的点一定无法被某点集线性表示出来.再往下的内容属于数学分析了,对我们的算法设计帮助不大,暂时先不管. 一般的计算几何问题都是处理的离散点集形成的平面域,所以我们感兴趣的是怎样找一个包含这个点集的面积最小的凸多边形,这就是凸包.作为常识也应该知道凸包上的顶点必然是该点集的子集,所以根据此性质我们就可以设计高效算法. 下面将介绍三种

平面上圆的凸包算法

平面上圆的凸包算法 我们之前探讨过这个有趣的问题: 平面上有若干圆,求包含这些圆的所有凸集的交. 根据之前讨论的结果,直接按圆心排序做扫描线的方法是错误的.我们需要考虑圆上的每一个点是否可以作为凸包上的一部分. 然而圆上的点的数目是无限多的.我们需要借助离散化的思想:因为我们发现凸包一定是由若干圆弧和线段构成的.而其中线段一定是切线段.由此,我们只需要将每两两圆的切点取出来求一个凸包.显然,在圆凸包上出现的切线段一定会在切点凸包中出现:而切点凸包中其余的线段则一定是弧弦. 但是,这个算法需要枚举

暴力+分治+贪心+DP:最大子序列和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 暴力:暴力列举所有可能的连续子数组,算法复杂度O(N^3)算法1: 1 int MaxSubseqSum1(int A[], int N) 2 { 3 int ThisSum, MaxSum = 0; 4 int i,j,k; 5 6 for (i = 0;

算法基础:分治模式,归并排序ΘΘΘΘΘΘ知识小结

1.分治模式在每层递归时都有三个步骤:分解,解决,合并 2.归并排序算法完全遵循分治模式: 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列 解决:使用归并排序递归的排序两个子序列 合并:合并两个已排序的子序列以产生已排序的答案 3.分析分治算法所需要的时间计算: 假设T(n)是规模为n的一个问题的运行时间,若问题足够小,如对某个常量c,n≦c,则直接求解需要常量时将,我们将其写作Θ(1).假设吧原问题分解成a个子问题,每个子问题的规模是原问题的1/b(对归并排序,a和b都为2,然

计算几何-凸包算法 Python实现与Matlab动画演示

凸包算法是计算几何中的最经典问题之一了.给定一个点集,计算其凸包.凸包是什么就不罗嗦了 本文给出了<计算几何——算法与应用>中一书所列凸包算法的Python实现和Matlab实现,并给出了一个Matlab动画演示程序. 啊,实现谁都会实现啦╮(╯▽╰)╭,但是演示就不一定那么好做了. 算法CONVEXHULL(P)  输入:平面点集P  输出:由CH(P)的所有顶点沿顺时针方向组成的一个列表 1.   根据x-坐标,对所有点进行排序,得到序列p1, …, pn 2.   在Lupper中加入p

【从零学习经典算法系列】分治策略实例——二分查找

1.二分查找算法简介 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法.搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束:如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较.如果在某一步骤数组 为空,则代表找不到.这种搜索算法每一次比较都使搜索范围缩小一半.折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn). 二分查找的优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且

Graham Scan凸包算法

获得凸包的算法可以算是计算几何中最基础的算法之一了.寻找凸包的算法有很多种,Graham Scan算法是一种十分简单高效的二维凸包算法,能够在O(nlogn)的时间内找到凸包. 首先介绍一下二维向量的叉积(这里和真正的叉积还是不同的):对于二维向量a=(x1,y2)和b=(x2,y2),a×b定义为x1*y2-y1*x2.而它的几何意义就是|a||b|sin<a,b>.如果a与b夹角小于180度(逆时针),那么这个值就是正值,大于180度就是负值.需要注意的是,左乘和右乘是不同的.如图所示:

(转)五大常用算法之一:分治算法

五大常用算法之一:分治算法 一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…… 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关.问题的规模越小,越容易直接求解,解题所需的计算时间也越少.例如,对于n个元素的排

openlayer的凸包算法实现

最近在要实现一个openlayer的凸多边形,也遇到了不小的坑,就记录一下 1.具体的需求: 通过在界面点击,获取点击是的坐标点,来绘制一个凸多边形. 2.思考过程: 1)首先,我们得先获取点击事件发生时,触发的点的坐标 map.events.register('click', map, function (e) { var pixel = new OpenLayers.Pixel(e.xy.x,e.xy.y); var lonlat = map.getLonLatFromPixel(pixel