回旋镖算法分享
判断回旋镖的存在问题是题库里的一道简单题,它的实现实则是考察我们对数学的掌握程度。而它的实现其实有很多种方式,下面就来介绍其中的4种。
- 一次函数法
- 距离法
- 向量法
- 行列式法
0.题目复现
问题描述
小M正在玩一个几何游戏,给定一个二维平面上的三个点 points,其中每个点用坐标 points[i] = [xi, yi] 表示。如果三点构成一个回旋镖,则返回 true。回旋镖的定义是三点不在一条直线上,并且这三个点互不相同。
请你帮助小M判断这些点是否构成一个回旋镖。
测试样例
样例1:
输入:points = [[1, 1], [2, 3], [3, 2]]
输出:True
样例2:
输入:points = [[1, 1], [2, 2], [3, 3]]
输出:False
样例3:
输入:points = [[0, 0], [1, 1], [1, 0]]
输出:True
实现这个问题的思路很简单:就是使用数学知识去证明任意三点是否共线,若共线返回false,若不共线则返回True。
下面笔者基于Python去写这个问题的算法实现。
法1:一次函数法
由我们初中就学过的数学知识可知,任意两点必定可以构成一条直线,也就是说,任意两点必定可以构成一个一次函数。如果三个点共线,那么它们必定在同一个函数直线上。如果三点不共线,那么其中两点在同一个函数直线上,但第三个点不在这条函数上。
因此,在这个问题里:
- Step 1:我们可以在给定的样例里,任意选取2个点构成一条直线,求取这个函数的参数。(斜率
m和截距b) - Step 2:然后再用第三个点带入这个函数,若第三个点不在这个函数上,则三点不共线,可以构成回旋镖,返回True。若第三个点在这个函数上,则三点共线,构不成回旋镖,返回False。
法1的算法实现
def solution(points: list) -> bool:
# write code here
# 函数法实现
x1,y1=points[0]
x2,y2=points[1]
x3,y3=points[2]
#任意提取两点出来构建一次函数
if(x2!=x1): #排除特殊情况,当x1=x2时,斜率不存在,此时函数为y=b
m=(y2-y1)/(x2-x1)#计算斜率
b=y1-m*x1 #计算截距
if (y3!=x3*m+b):
return True
else:
return False
else:
if(x3!=x1):
return True
else:
return False
if __name__ == '__main__':
print(solution(points=[[1, 1], [2, 3], [3, 2]]) == True) #True
print(solution(points=[[1, 1], [2, 2], [3, 3]]) == False) #True
print(solution(points=[[0, 0], [1, 1], [1, 0]]) == True) #True
这个算法不仅只需要很简单的数学知识,而且用编程语言实现也非常简单,对新手友好。但是笔者认为它有一个麻烦的地方就是需要考虑斜率不存在的时候。
法2:距离法实现
顾名思义,这个算法的实现逻辑就是:计算两点之间的距离来判断三点是否共线。如果任意两段距离之和等于第三段距离,则三点共线;否则,三点不共线。
这个方法实现起来也很简单,而且不用考虑特殊情况。它的核心就是要使用欧几里得距离公式去计算两点距离。
欧几里得距离公式:
算法步骤:
- Step 1:计算任意两点间的距离。
- Step 2:判断任意两点距离之和是否等于第三段距离。
法2的算法实现
import math
def solution(points: list) -> bool:
# 检查三点是否互不相同
if points[0] == points[1] or points[1] == points[2] or points[0] == points[2]:
return False
# 提取坐标
x1, y1 = points[0]
x2, y2 = points[1]
x3, y3 = points[2]
# 计算两点之间的距离
d1 = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
d2 = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2)
d3 = math.sqrt((x3 - x1) ** 2 + (y3 - y1) ** 2)
# 判断三点是否共线
if abs(d1 + d2 - d3) < 1e-9 or abs(d1 + d3 - d2) < 1e-9 or abs(d2 + d3 - d1) < 1e-9:
return False
else:
return True
if __name__ == '__main__':
print(solution(points=[[1, 1], [2, 3], [3, 2]]) == True)
print(solution(points=[[1, 1], [2, 2], [3, 3]]) == False)
print(solution(points=[[0, 0], [1, 1], [1, 0]]) == True)
值得注意的是:在Python中,浮点数的比较可能会出现精度误差,因此我们在判断机制里,使用了一个很小的误差范围1e-9来判断两个浮点数是否相等。
法3:向量法实现
刚刚的距离法实际是把点的坐标转换为了向量,然后再去计算向量的长度。显然,如果我们直接通过计算向量来判断三点共线问题,会让我们的问题解决得更有效率。
因此,在这个算法里,我们可以使用向量的叉乘来判断三点是否共线。如果三个点共线,则它们的向量叉乘结果为0。
而在Python里,我们可以使用NumPy库来实现向量的叉乘,也可以直接使用Python内置的元组数据类型来存储数据。
算法步骤:
- Step 1:提取三个点的坐标,构建任意两个点组成的向量,并使用二维元组来存储向量
v1 = (x2 - x1, y2 - y1)。 - Step 2:计算两个向量的叉乘。
- Step 3:判断叉乘结果是否为0。
法3的算法实现
def solution(points: list) -> bool:
# 检查三点是否互不相同
if points[0] == points[1] or points[1] == points[2] or points[0] == points[2]:
return False
# 提取坐标
x1, y1 = points[0]
x2, y2 = points[1]
x3, y3 = points[2]
# 计算向量 v1 和 v2
v1 = (x2 - x1, y2 - y1)
v2 = (x3 - x1, y3 - y1)
# 计算向量 v1 和 v2 的叉积
cross_product = v1[0] * v2[1] - v1[1] * v2[0]
# 如果叉积为零,则三点共线
if cross_product == 0:
return False
else:
return True
if __name__ == '__main__':
print(solution(points=[[1, 1], [2, 3], [3, 2]]) == True)
print(solution(points=[[1, 1], [2, 2], [3, 3]]) == False)
print(solution(points=[[0, 0], [1, 1], [1, 0]]) == True)
显然,在这个算法里,我们使用了元组来存储向量,而元组是不可变的,因此它的计算速度将大大提高。
法4:行列式法实现
这个方法的实现思路和向量法是一样的,都是通过计算向量的叉乘来判断三点是否共线。通过对高等代数等相关课程的学习,我们可以知道:如果行列式的值为零,则三点共线;否则,三点不共线。
- Step 1:提取三个点的坐标。
- Step 2:计算行列式.
- Step 3:判断行列式的值是否为零。
法4的算法实现
def solution(points: list) -> bool:
# 检查三点是否互不相同
if points[0] == points[1] or points[1] == points[2] or points[0] == points[2]:
return False
# 提取坐标
x1, y1 = points[0]
x2, y2 = points[1]
x3, y3 = points[2]
# 计算行列式
determinant = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)
# 如果行列式为零,则三点共线
if determinant == 0:
return False
else:
return True
if __name__ == '__main__':
print(solution(points=[[1, 1], [2, 3], [3, 2]]) == True)
print(solution(points=[[1, 1], [2, 2], [3, 3]]) == False)
print(solution(points=[[0, 0], [1, 1], [1, 0]]) == True)
5.总结与延伸
在这个问题里,我们使用了4种不同的算法来实现回旋镖的判断。其中,向量法和行列式法的实现是最为高效的,它们的时间复杂度都为O(1)。而距离法和一次函数法的实现则相对较为简单,它们的时间复杂度都为O(1)。
另外,笔者在这个问题里还使用了一些Python的技巧,例如元组的使用、浮点数的比较等。这些技巧可以帮助我们更加高效地实现算法。
以及,其实除了上述4种方法,笔者还想到一些别的算法思路去实现该问题:
比如说,我们是否可从图论的角度去构建算法?笔者从七桥问题受到启发,如果三点不共线,那么可视为它们在一个图里,它们必然构成一个环。反之则不能。于是乎,只要我们通过分析图的连通性,就能判断三点是否构成一个回旋镖。
但由于时间关系,并没有将它们用计算机语言实现。但有空的话我觉得还是很值得敲一敲的。