P99.5 PolygonConcavityIndex
Check whether a given polygon in a 2D plane is convex; if not, return the index of a vertex that doesn't belong to the convex hull.
给出二维平面上点的集合A。这些点可组成一个多边形:每两个连续的点的连线构成多边形的边,并且有一个边是通过连接集合中的最后一个点和第一个点构成的。
二维平面上的一组点,其边界是一条直线,称为半平面。更准确地说,具有形式{(x, y): ax + by ≥ c}的直线都是半平面。半平面包含其边界。
当且仅当多边形的边界上的任意两点之间的线段没有超出多边形时,此多边形称为凸多边形。
例如,在笛卡尔直角坐标系中,由下面4个顶点:(-1,3)(3,1)(0,-1)(-2,1)构成的多边形是凸多边形
二维平面上有限点集的凸包是包含该集中所有点的最小凸多边形。例如,笛卡尔直角坐标系中有7个点:
(-1,3)(1,2)(1,1)(3,1)(0,-1)(-2,1)(-1,2)
包含上面七个点的凸包就是一个含有五个顶点的多边形。其顶点为:
(-1,3)(1,2)(3,1)(0,-1)(-2,1)
如果一个多边形是凹形的(也就是说,它不是凸形的),它至少有一个不在它的凸包边界上的顶点。现在就是找到这样一个顶点。 假设给出以下声明:
class Point2D(object):
x = 0
y = 0
编写函数:
def solution(A)
非空数组A由N个点组成,如果多边形是凸的,则返回−1。否则,函数应返回不在凸包边界上的任何一个点的索引。 注意:多边形的边可以共线(即多边形的某个内角可能为180度)。
要获得第K个点的坐标(其中0 ≤ K < N),请使用以下规则:
A[K].x获得X轴坐标,
A[K].y获得Y轴坐标。
例如,给定数组A:
A[0].X=-1,A[0].Y=3,A[1].X=1,A[1].Y=2,A[2].X=3,A[2].Y=1,
A[3].X=0,A[3].Y=-1,A[4].X=-2,A[4].Y=1
函数应返回−1。
但是,给定数组A:
A[0].X=-1,A[0].Y=3,A[1].X=1,A[1].Y=2,A[2].X=1,A[2].Y=1,
A[3].X=3,A[3].Y=1,A[4].X=0,A[4].Y=-1,A[5].X=-2,A[5].Y=1,A[6].X=-1,A[6].Y=2
函数应返回2或6。这些都是不在凸包边界上的点的索引。
假定:
- N是区间[3,10000]内的整数;
- 数组A中每个点的坐标都是[−1,000,000,000,1,000,000,000]内的整数;
- 多边形A的两条边只能在端点处相交;
- 数组A不包含重复的点。
根据Graham Scan算法,在计算凸包的过程中,对于不是凸包上的点,返回其索引。如果遍历完点,没有返回,则说明是凸多边形,返回-1即可。
- Graham Scan算法:
-
获得参考点T0:参考点就是所有点中,纵坐标最小的点,如果这样的点有多个,则把这些点中横坐标最小的点作为参考点。可知这个参考点肯定在凸包上。
-
点逆时针排序:
-
首先将参考点T0移动到原点P0,其他的点也按照上述规则移动到相应的点;
-
计算原点与其他的点构成的向量与x轴正向构成的夹角,按照夹角从小到大对点进行排序。对于夹角为0的,按照横坐标的升序排列;其他夹角相同的,按照纵坐标的值降序排列。
-
最终获得的点的序列就是按照逆时针排列的;
-
-
判断相邻的3个点是否是凸包上的点
假设相邻的三个点分别是P0,P1,P2:
# -*- coding:utf-8 -*-
# &Author AnFany
# Lesson 99:Future training
# P 99.5 PolygonConcavityIndex
def solution(A):
"""
判断A中点组成的多边形是否是凸多边形,是的话返回-1,不是的话返回在凸包内部的任意一点的索引
利用Graham扫描算法得到凸包
:param A: 顶点的坐标
:return: -1或者凸包内部任意点的索引
"""
if len(A) == 3:
return -1
# 所有点的集合
point_set = []
for i in range(len(A)):
point_set.append([i, [A[i].x, A[i].y]]) # 题目中设定的数据结构
# point_set.append([i, [A[i][0], A[i][1]]])
# 选出所有点中纵坐标最小的点,纵坐标相同的选择横坐标最小的点
point_set.sort(key=lambda s: [s[1][1], s[1][0]])
min_y_point = point_set[0] # 参考点P0
# 将上面选择的点,转移到原点,计算其他所有点转移后和原点构成的向量和x轴正向的夹角
# 按照夹角从小到大排列,相同角度的, 按照y的降序排列
more_equal_than_zero = [] # 大于等于0的升序排列,相同值的按照y的降序排列
less_than_zero = [] # 小于0的升序排列,相同值的按照y的降序排列
positive_infinity = [] # 等于正无穷的按照y值的降序排列
for p in point_set[1:]:
point = [p[1][0] - min_y_point[1][0], p[1][1] - min_y_point[1][1]]
if point[0] != 0:
tan = point[1] / point[0]
if tan >= 0:
more_equal_than_zero.append([p[0], [tan, point[1]], point])
else:
less_than_zero.append([p[0], [tan, point[1]], point])
else:
positive_infinity.append([p[0], [0, point[1]], point])
more_equal_than_zero.sort(key=lambda m: [m[1][0], -m[1][1]])
less_than_zero.sort(key=lambda m: [m[1][0], -m[1][1]])
positive_infinity.sort(key=lambda m: -m[1][1])
# 合并后,所有点是按着夹角逆时针排列的
trans_point_angle = more_equal_than_zero + positive_infinity + less_than_zero
# 开始位置添加第一个点P0
trans_point_angle.insert(0, [point_set[0][0], [0, 0], [0, 0]])
# 结尾位置添加第一个点P0
trans_point_angle.append(trans_point_angle[0])
# 把前2个点p0, p1放入栈中,把p1后面的点p2作为评判点,如果向量的叉积V_p0p2*V_p0p1<0,说明p2在p0p1的逆时针方向,是对的,如果为0,
# 说明三点共线;如果大于0,说明p2在p0p1的顺时针方向,说明P1点是凹进去的
current = [trans_point_angle[0], trans_point_angle[1]]
for index in trans_point_angle[2:]:
p0, p1, p2 = [current[0], current[1], index]
p0_p = p0[2]
p1_p = p1[2]
p2_p = p2[2]
p0p2 = p2_p[0] - p0_p[0], p2_p[1] - p0_p[1]
p0p1 = p1_p[0] - p0_p[0], p1_p[1] - p0_p[1]
product = p0p2[0] * p0p1[1] - p0p2[1] * p0p1[0]
if product < 0:
current = [current[1], index]
elif product == 0:
current = [current[0], index]
else:
return current[1][0]
return -1