背景
在xy坐标轴给定多个顶点,顶点两两之间形成多条直线,在这些直线中,有的重合在了一起,那么如何将这些直线不重复的进行存储起来呢?
为什么不使用斜率和截距
常规思路很简单,只要保存斜率 k 和截距 b 就可以了。但是这里涉及到了两个问题:
垂直直线如何表示? 要知道,垂直的直线是没有斜率的。
精度问题:使用浮点数来表示斜率和截距可能导致精度误差,特别是当斜率非常大或非常小的时候。
思路
由于每条直线都可以通过两点式来确定唯一的直线
想要存储不同的直线,可以使用三元组 来进行存储。其中:
- 和 分别代表两点间的纵坐标差和横坐标差。这种表示方式避免了直接计算斜率,从而克服了垂直直线的问题。要注意的是: 和 为了保证每条直线都有唯一的表示,我们需要将这个斜率化简到最简形式,可以通过除以
dy
和dx
的最大公约数来实现。 - 是一个常数,代表直线的特定位置。这个常数通过直线上的点和 𝑑𝑦,𝑑𝑥 的值计算得出,计算公式为:。(不是截距)
通过这三个参数,我们可以保证一条直线唯一。
通用方程的推导
假设有两个点 和 ,过两点的直线可以用 和 表达斜率,其中:
如果直线的斜率 定义为 ,那么过两点的直线方程为:
将 令为 ,则有:
对于同一条直线来说,当斜率 化为最简形式时, 和截距 的值都是固定的,故同一条直线的 也是固定的。
这样,不论直线是否垂直,都可以使用 这个参数来表示特定的直线。对于垂直直线(即 ),这个形式依然有效,因为此时等式退化为 (常数)。
使用 b 表示截距
在此方程中, 是通过选择的点 计算得出,其计算方式为:
这种表示方式不直接等同于传统的 y-截距 c,但它提供了一种在二维坐标系中唯一确定直线的方法。这样的表达有助于避免处理斜率无穷大的特殊情况,并且使得储存和比较直线变得更加方便。
这种通用方程形式允许我们将所有直线(无论其方向如何)统一地处理,这在编程中特别有用,因为我们可以使用相同的逻辑来处理所有情况,无需为特殊情况编写额外的代码。
因此,我们最终可以使用三元组 进行存储直线,这在进行重复直线判定时特别好用。
当然,本篇博客仅供参考,该存储唯一直线方法是其中一种方法,有其他方法欢迎分享!
练习题目(选看)
最后,贴一道最新的蓝桥杯模拟题目来训练一下吧!
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。给定平面上 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;
}