手撕算法系列——3. 线段相交问题

本文探讨如何利用向量叉积判断两条线段在平面直角坐标系中是否相交,包括线段相交的两种情况,并提供Python实现代码进行验证。

    这道题也是一道经典的面试题,即分别给出两条线段在平面直角坐标系下的端点坐标,判断这两条线段是否相交(这里的相交定义为有且只有一个公共点)。

    这道题如果用传统的解析几何思维,那就是求出这两条线段所在直线的方程,判断是否平行,若不平行,再算出两直线的交点,最后计算交点是否在线段上。这样做思路比较简单,但是实现起来可能会遇到各种各样的问题,例如若线段所在直线倾角等于或接近90°,在计算过程中会出现除数等于0或非常接近0的情况,这种情况下可能会出现较大的精度损失,不是一种明智的方法。

    对于两条线段,如果相交则有以上两种情况:

    1)每条线段都分别跨越了另一条线段所在直线,即两个端点分别在该直线两侧;

    2)一条线段的端点恰好在另一条线段上。

    这里我们可以用向量叉积来判断跨立条件,如下图所示,如果线段ab跨立了线段cd所在的直线,那么作向量ca、cd和cb,其中ca×cd和cb×cd的正负号应该是不同的。即满足

ca×cd和cb×cd < 0 

考虑到情况2),上式等于0的情况也成立,因此,判断线段ab跨立线段cd所在直线的的条件为

                                                                                 ca×cd和cb×cd <= 0 


    因此,两线段相交的充要条件应该是彼此跨立。

    下面附上Python实现的代码,用Python是因为可以用matplotlib画图检验。

import matplotlib.pyplot as plt


def multiply(v1, v2):
    """
    计算两个向量的叉积
    """
    return v1.x*v2.y - v2.x*v1.y


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __sub__(self, other):
        """
        重载减法运算,计算两点之差,实际上返回的表示该点到另一点的向量
        :param other: 另一个点
        :return: 该点到另一点的向量
        """
        return Point(self.x-other.x, self.y-other.y)


class Segment:
    def __init__(self, point1, point2):
        self.point1 = point1
        self.point2 = point2

    def straddle(self, another_segment):
        """
        :param another_segment: 另一条线段
        :return: 如果另一条线段跨立该线段,返回True;否则返回False
        """
        v1 = another_segment.point1 - self.point1
        v2 = another_segment.point2 - self.point1
        vm = self.point2 - self.point1
        if multiply(v1, vm) * multiply(v2, vm) <= 0:
            return True
        else:
            return False

    def is_cross(self, another_segment):
        """
        :param another_segment: 另一条线段
        :return: 如果两条线段相互跨立,则相交;否则不相交
        """
        if self.straddle(another_segment) and another_segment.straddle(self):
            return True
        else:
            return False

    def cross(self, another_segment):
        if self.is_cross(another_segment):
            print('两线段相交.')
        else:
            print('两线段不相交.')
        plt.plot([self.point1.x, self.point2.x], [self.point1.y, self.point2.y])
        plt.plot([another_segment.point1.x, another_segment.point2.x],
                 [another_segment.point1.y, another_segment.point2.y])
        plt.show()

测试样例1(相交的情况2):

A = Point(0, 0)
B = Point(2, 2)
C = Point(1, 1)
D = Point(0, 2)
AB = Segment(A, B)
CD = Segment(C, D)
AB.cross(CD)

输出结果:

两线段相交.



                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值