关于如何存储不重复直线的探讨

 背景

在xy坐标轴给定多个顶点,顶点两两之间形成多条直线,在这些直线中,有的重合在了一起,那么如何将这些直线不重复的进行存储起来呢?

为什么不使用斜率和截距

常规思路很简单,只要保存斜率 k 和截距 b 就可以了。但是这里涉及到了两个问题:

垂直直线如何表示? 要知道,垂直的直线是没有斜率的。

精度问题:使用浮点数来表示斜率和截距可能导致精度误差,特别是当斜率非常大或非常小的时候。

思路

由于每条直线都可以通过两点式来确定唯一的直线

想要存储不同的直线,可以使用三元组 (dy, dx, b) 来进行存储。其中:

  •  dy 和 dx 分别代表两点间的纵坐标差\Delta y和横坐标差\Delta x。这种表示方式避免了直接计算斜率,从而克服了垂直直线的问题。要注意的是:dy 和 dx 为了保证每条直线都有唯一的表示,我们需要将这个斜率化简到最简形式,可以通过除以 dydx 的最大公约数来实现。
  • b 是一个常数,代表直线的特定位置。这个常数通过直线上的点和 𝑑𝑦,𝑑𝑥 的值计算得出,计算公式为:b = \Delta y \cdot x_1 - \Delta x \cdot y_1。(不是截距)

通过这三个参数,我们可以保证一条直线唯一。

通用方程的推导

假设有两个点 (x_1, y_1)(x_2, y_2),过两点的直线可以用 \Delta y 和 \Delta x 表达斜率,其中:

  • \Delta y = y_2 - y_1
  • \Delta x = x_2 - x_1

如果直线的斜率 k 定义为 \frac{\Delta y}{\Delta x},那么过两点的直线方程为:

y = \frac{\Delta y}{\Delta x} x + c

\Delta y \cdot x - \Delta x \cdot y = \Delta x \cdot c

\Delta x \cdot c 令为 b,则有:

\Delta y \cdot x - \Delta x \cdot y = \Delta x \cdot c = b

对于同一条直线来说,当斜率 k = \Delta y / \Delta x 化为最简形式时,\Delta x 和截距 c 的值都是固定的,故同一条直线的 b 也是固定的。

这样,不论直线是否垂直,都可以使用 b 这个参数来表示特定的直线。对于垂直直线(即 \Delta x = 0),这个形式依然有效,因为此时等式退化为 x = \text{constant}(常数)。

使用 b 表示截距

在此方程中,b 是通过选择的点 (x_1, y_1)  计算得出,其计算方式为:

b = \Delta y \cdot x_1 - \Delta x \cdot y_1

这种表示方式不直接等同于传统的 y-截距 c,但它提供了一种在二维坐标系中唯一确定直线的方法。这样的表达有助于避免处理斜率无穷大的特殊情况,并且使得储存和比较直线变得更加方便。

这种通用方程形式允许我们将所有直线(无论其方向如何)统一地处理,这在编程中特别有用,因为我们可以使用相同的逻辑来处理所有情况,无需为特殊情况编写额外的代码。

因此,我们最终可以使用三元组 (dy, dx, b) 进行存储直线,这在进行重复直线判定时特别好用。

当然,本篇博客仅供参考,该存储唯一直线方法是其中一种方法,有其他方法欢迎分享!

练习题目(选看)

最后,贴一道最新的蓝桥杯模拟题目来训练一下吧!

在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,
那么这些点中任意两点确定的直线是同一条。
给定平面上 2 × 3 个整点 { ( x , y ) | 0 x < 2 , 0 y < 3 , x Z , y Z } ,即横坐标
0 1 ( 包含 0 1 ) 之间的整数、纵坐标是 0 2 ( 包含 0 2 ) 之间的整数
的点。这些点一共确定了 11 条不同的直线。
给定平面上 20 × 21 个整点 { ( x , y ) | 0 x < 20 , 0 y < 21 , x Z , y Z } ,即横
坐标是 0 19 ( 包含 0 19 ) 之间的整数、纵坐标是 0 20 ( 包含 0 20 )
间的整数的点。请问这些点一共确定了多少条不同的直线。

简单来说,就是横坐标0-19取整数,纵坐标0-20取整数,能够构成(0,0),(0,1),(0,2)......(19,19),(19,20)这些点,判断这些点总共构成了多少条直线。

参考代码:

#include <bits/stdc++.h>
using namespace std;

// 最大公约数函数
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

int main() {
    set<tuple<int, int, int>> lines;
    int width = 20, height = 21;

    for (int x1 = 0; x1 < width; x1++) {
        for (int y1 = 0; y1 < height; y1++) {
            for (int x2 = 0; x2 < width; x2++) {
                for (int y2 = 0; y2 < height; y2++) {
                    if (x1 == x2 && y1 == y2) continue; // 同一个点的情况跳过

                    int dx = x2 - x1;
                    int dy = y2 - y1;
                    if (dx == 0) {
                        // 垂直线
                        lines.insert({1, 0, x1}); // 使用 (1,0,x) 表示垂直线 x = x1
                    } else {
                        if (dx < 0) {
                            dx = -dx;
                            dy = -dy;
                        }
                        int g = gcd(abs(dx), abs(dy));
                        dx /= g;
                        dy /= g;
                        // 要注意,这里的 b=Δx*c,并不是截距,但是为了方便记忆,可以记b为截距
                        int b = dy * x1 - dx * y1;
                        lines.insert({dy, dx, b});
                    }
                }
            }
        }
    }
    cout << "直线数量为: " << lines.size() << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值