前言:
本文主要记录自己在学习过程中遇到的一些几何问题以及其对应的C++实现方法,以作备忘,欢迎交流。
1.判断点到直线距离(平面上):
给定直线上两点、以及平面上一点,利用点到直线距离公式求出结果,需要先求出直线的一般式方程,再代入公式求解。
输入: N组的三个点坐标、、
输出: 点到直线的距离
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
//计算点到直线距离首先要算出直线方程,直线方程此处采用一般式: Ax+By+C=0
//假设直线上两点(x1,y1) (x2,y2)
//A=y2-y1 B=x1-x2 C=x2*y1-x1*y2
//求出直线方程后直接带入点到直线距离公式:
//d=|Ax0+By0+C|/sqrt(A^2+B^2)
float getDistanceFromPoint(vector<int>& p0, vector<int>& p1, vector<int>& p2) {
if (p1[0] == p2[0] && p1[1] == p2[1]) {
//两点重合,直接按点之间距离计算
return sqrt((p0[0] - p1[0]) * (p0[0] - p1[0]) + (p0[1] - p1[1]) * (p0[1] - p1[1]));
}
//点P0到P1 P2构成直线的距离
float A = p2[1] - p1[1];
float B = p1[0] - p2[0];
float C = p1[1] * p2[0] - p1[0] * p2[1];
float distance = abs(A * p0[0] + B * p0[1] + C) / sqrt(A * A + B * B);
return distance;
}
int main()
{
int n;//多少组数据
std::cin >> n;
int p0_x, p0_y, p1_x, p1_y, p2_x, p2_y;
vector<int> p0, p1, p2;
float dis = 0.0;
for (int i = 0; i < n; i++) {
std::cin >> p0_x >> p0_y;
std::cin >> p1_x >> p1_y;
std::cin >> p2_x >> p2_y;
p0 = { p0_x, p0_y };
p1 = { p1_x, p1_y };
p2 = { p2_x, p2_y };
dis = getDistanceFromPoint(p0, p1, p2);
cout << dis << endl;
}
}
2.判断点与三角形的关系(平面上):
给定一个点和三角形三个顶点、、,判断点是否在三角形内。
2.1 同向法
主要考虑使用同向法(思路参考自此处,原文是三维情况,这里我只考虑平面)进行计算:假设点位于三角形内会有这样的一个规律,当沿着的方向在三条边上行走时,会发现点始终位于边,和的右侧。但是如何判断一个点在线段的左侧还是右侧呢?当选定线段时,点位于的右侧,同理选定时,点位于的右侧,最后选定时,点位于的右侧,所以当选择某一条边时,我们只需验证点与该边所对的点在同一侧即可。如何判断两个点在某条线段的同一侧呢?可以通过叉积来实现,连接,将和做叉积,再将和做叉积,如果两个叉积的结果方向一致,那么两个点在同一测。判断两个向量的是否同向可以用点积实现,如果点积大于0,则两向量夹角是锐角,否则是钝角。
输入: N组的四个点坐标、、、
输出: 点与三角形的关系
代码如下:
#include <iostream>
using namespace std;
class Vector {
public:
float x, y;
Vector(int _x, int _y) : x(_x), y(_y) {}
Vector operator - (Vector v) {
return Vector(x - v.x, y - v.y);
}
float Dot(Vector& v) {
return x * v.x + y * v.y;
}
int Cross(Vector v) {
return x * v.y - y * v.x;//二维向量叉积等于两向量构成的平行四边形面积
}
};
bool OnPoint(Vector& A, Vector& B, Vector& C, Vector& P) {
//判断点P是否在三个顶点上
if (A.x == P.x && A.y == P.y) {
return true;//点P与点A重合
}
if (B.x == P.x && B.y == P.y) {
return true;//P与B重合
}
if (C.x == P.x && C.y == P.y) {
return true;//P与C重合
}
return false;
}
bool OnEdge(Vector& A, Vector& B, Vector& C, Vector& P) {
Vector PA = P - A;
Vector PB = P - B;
Vector PC = P - C;
if (PA.Cross(PB) == 0 && min(A.x, B.x) <= P.x && min(A.y, B.y) <= P.y && max(A.x, B.x) >= P.x && max(A.y, B.y) >= P.y) {
return true;//P在AB上
}
if (PB.Cross(PC) == 0 && min(B.x, C.x) <= P.x && min(B.y, C.y) <= P.y && max(B.x, C.x) >= P.x && max(B.y, C.y) >= P.y) {
return true;//P在BC上
}
if (PA.Cross(PC) == 0 && min(A.x, C.x) <= P.x && min(A.y, C.y) <= P.y && max(A.x, C.x) >= P.x && max(A.y, C.y) >= P.y) {
return true;//P在AC上
}
return false;//不在边上
}
bool SameSide(Vector& A, Vector& B, Vector& C, Vector& P) {
//判断点P是否与点C都在线段AB的同侧
Vector AB = A - B;
Vector AP = A - P;
Vector AC = A - C;
float v1, v2;
v1 = AB.Cross(AP);
v2 = AB.Cross(AC);
return v1 * v2 > 0;//大于0说明AP与AC在AB的同一侧
}
bool InTriangle(Vector& A, Vector& B, Vector& C, Vector& P) {
return SameSide(A, B, C, P) && SameSide(B, C, A, P) && SameSide(C, A, B, P);
}
int main()
{
// 输入格式:n为需要判断的个数
// 接下来每4行一组分别为三角形的三个点和目标点的横纵坐标
FILE* stream;
freopen_s(&stream, "input.txt", "r", stdin);
int n = 0;
float ax, ay, bx, by, cx, cy, px, py;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> ax >> ay;
cin >> bx >> by;
cin >> cx >> cy;
cin >> px >> py;
Vector A(ax, ay);
Vector B(bx, by);
Vector C(cx, cy);
Vector P(px, py);
if (OnPoint(A, B, C, P)) {
cout << "点在顶点" << endl;
continue;
}
if (OnEdge(A, B, C, P)) {
cout << "点在边上" << endl;
continue;
}
if (InTriangle(A, B, C, P)) {
cout << "点在三角形内" << endl;
continue;
}
else {
cout << "点在三角形外" << endl;
}
}
return 0;
}
此处需要读输入文件input.txt,格式如下:
5
0 3
4 0
0 0
2 2
0 0
0 3
4 0
2 2
0 3
0 0
4 0
2 2
0 3
4 0
0 0
0 2
0 3
4 0
0 0
2 1
2.2 简洁版
#include <iostream>;
using namespace std;
class Vector2D{
public:
Vector2D(float x, float y) {
mx = x;
my = y;
}
Vector2D operator - (Vector2D v) {
return Vector2D(mx - v.mx, my - v.my);
}
float cross(Vector2D v) {
return mx * v.my - v.mx * my;
}
private:
float mx;
float my;
};
bool isInTriangle(float Ax, float Ay, float Bx, float By, float Cx, float Cy, float Px, float Py) {
Vector2D A = Vector2D(Ax, Ay);
Vector2D B = Vector2D(Bx, By);
Vector2D C = Vector2D(Cx, Cy);
Vector2D P = Vector2D(Px, Py);
Vector2D PA = A - P;
Vector2D PB = B - P;
Vector2D PC = C - P;
float t1 = PA.cross(PB);
float t2 = PB.cross(PC);
float t3 = PC.cross(PA);
return t1 * t2 >= 0 && t1 * t3 >= 0;
}
int main() {
bool res = isInTriangle(0, 0, 0, 3, 4, 0, 2, 2);
cout << res << endl;
}
3.判断点和多边形的关系(平面):
给定一个点和一组多边形的各顶点,判断该点与多边形的关系。
主要考虑射线法(参考处)时间复杂度:O(n)。以被测点P为端点,向任意方向作射线(一般水平向右作射线),统计该射线与多边形的交点数。如果为奇数,P在多边形内;如果为偶数,P在多边形外。特殊情况可以参考链接。
输入: 多边形的N个点坐标
输出: 点与多边形的关系
代码如下:
#include <iostream>
#include <vector>
using namespace std;
//射线法判断点是否在多边形内
const double eps = 1e-6;//浮点数判断阈值
class Point {
public:
double x, y;
Point() : x(0), y(0) {}
Point(double _x, double _y) : x(_x), y(_y) {}
Point operator +(Point v) {//向量加法
return Point(x + v.x, y + v.y);
}
Point operator -(Point v) {//向量减法
return Point(x - v.x, y - v.y);
}
double operator *(Point v) {//点积
return x * v.x + y * v.y;
}
double operator ^(Point v) {//叉积
return x * v.y - v.x * y;
}
};
int dcmp(double x) {
//三态函数,判断两个double在eps精度下的大小关系
if (fabs(x) < eps) return 0;//认为两者相等
else {
return x < 0 ? -1 : 1;
}
}
bool inSegment(Point& q, Point& p1, Point& p2) {
//判断Q是否在p1和p2的线段上
//第一个利用向量QP1和QP2叉积为零表明Q与P1 P2共线
//第二个利用向量点积为负表明Q在两者之间
return dcmp((p1 - q) ^ (p2 - q)) == 0 && dcmp((p1 - q) * (p2 - q)) <= 0;
}
bool inPolygon(vector<Point>& polygon, Point p) {
//判断点P在多边形内
bool flag = false;//相当于记录交点数
for (int i = 0; i < polygon.size(); i++) {
Point p1 = polygon[i];
Point p2 = polygon[(i + 1) % polygon.size()];
if (inSegment(p, p1, p2)) return true;//点在多边形边上,也认为在多边形内
//前一个判断: min(P1.y,P2.y) < P.y <= max(P1.y,P2.y)
//后一个判断被测点p在射线与边交点的左边
if ((dcmp(p1.y - p.y) > 0 != dcmp(p2.y - p.y) > 0) && dcmp(p.x - (p.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) - p1.x) < 0) {
flag = !flag;
}
}
return flag;
}
int main()
{
//测试用例输入:
// 4
// 10 10
// 20 10
// 20 5
// 10 5
// 15 8--测试点(25 8)
//输出:
// Inside (Outside)
int n;
cin >> n;//顶点个数
vector<Point> polygon(n);
for (int i = 0; i < n; i++) {
cin >> polygon[i].x >> polygon[i].y;//一行输入一个点的坐标x y
}
Point p;//测试点
cin >> p.x >> p.y;
if (inPolygon(polygon, p)) {
cout << "Inside" << endl;
}
else {
cout << "Outside" << endl;
}
}
4.判断两个矩形是否相交(平面):
4.1不考虑矩形旋转
即矩形上下边平行于X轴,左右边平行与Y轴,可以参考 力扣836矩形重叠 问题,但注意,力扣题认为如果相交的面积为正,则称两矩形重叠,只在角或边接触的两个矩形不构成重叠。但对于判断相交的情况应该是算做相交的。
以下为官方题解思路:
我们尝试分析在什么情况下,矩形 rec1 和 rec2 没有重叠。如果矩形 rec1 和 rec2 中至少有一个矩形的面积为 0,则一定没有重叠。当矩形 rec1 和 rec2 的面积都大于 0 时,如果我们在平面中放置一个固定的矩形 rec2,那么矩形 rec1 必须要出现在 rec2 的「四周」,也就是说,矩形 rec1 需要满足以下四种情况中的至少一种:
- 矩形 rec1 在矩形 rec2 的左侧;
- 矩形 rec1 在矩形 rec2 的右侧;
- 矩形 rec1 在矩形 rec2 的上方;
- 矩形 rec1 在矩形 rec2 的下方。
何为「左侧」?如果矩形 rec1 在矩形 rec2 的左侧,那就表示我们可以找到一条竖直的线(可以与矩形的边重合),使得矩形 rec1 和 rec2 被分在这条竖线的两侧。对于「右侧」、「上方」以及「下方」,它们的定义与「左侧」是类似的。
算法:
首先判断矩形 rec1 和 rec2 的面积是否为 0。
- 对于矩形 rec1 而言,其面积为 0 当且仅当 rec1[0] == rec1[2] 或 rec1[1] == rec1[3];
- 对于矩形 rec2 而言,其面积为 0 当且仅当 rec2[0] == rec2[2] 或 rec2[1] == rec2[3]。
如果至少有一个矩形的面积为 0,则一定没有重叠。如果矩形 rec1 和 rec2 的面积都大于 0,则考虑两个矩形的位置。我们将上述四种情况翻译成代码。具体地,我们用 (rec[0], rec[1]) 表示矩形的左下角,(rec[2], rec[3]) 表示矩形的右上角,与题目描述一致。对于「左侧」,即矩形 rec1 在 x 轴上的最大值不能大于矩形 rec2 在 x 轴上最小值。对于「右侧」、「上方」以及「下方」同理。因此代码如下:
class Solution {
public:
bool isRectangleOverlap(vector<int>& rec1, vector<int>& rec2) {
if (rec1[0] == rec1[2] || rec1[1] == rec1[3] || rec2[0] == rec2[2] || rec2[1] == rec2[3]) {
return false;
}
return !(rec1[2] <= rec2[0] || // left
rec1[3] <= rec2[1] || // bottom
rec1[0] >= rec2[2] || // right
rec1[1] >= rec2[3]); // top
}
};
4.2考虑矩形旋转的情况
第一种不考虑矩形选择的情况,判断方法比较简单,网上也有比较多的解释,我这里主要考虑矩形旋转时判断是否相交。主要使用分离轴定律(参考)
输入: 两个矩形的4个点坐标
输出: 两个矩形是否相交
代码如下:
#include <iostream>
#include <vector>
using namespace std;
//求解方法:分离轴定律
//两个凸多边形物体,如果我们能找到一个轴,使得两个在物体在该轴上的投影互不重叠,
//则这两个物体之间没有碰撞发生,该轴为Separating Axis。
//也就是说两个多边形在所有轴上的投影都发生重叠,则判定为碰撞;否则,没有发生碰撞。
//矩形有4条边,那么就有4条轴,由于矩形的对边是平行的,所以有两条轴是重复的,
//我们仅需要检查相邻的两个轴,那么两个矩形就需要检查4个轴。
//
//检查投影的方法有两种:
// 1.把每个矩形的4个顶点投影到一个轴上,这样算出4个顶点最长的连线距离,然后同样对待第二个矩形,最后判断2个矩形投影距离是否重叠(程序使用)
// 2.把2个矩形的半径距离投影到轴上,以后把2个矩形的中心点连线投影到轴上,以后判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小
//
class Vector2D {
public:
double x, y;
Vector2D(double _x, double _y) {
x = _x;
y = _y;
}
Vector2D operator -(Vector2D& v) {
return Vector2D(x - v.x, y - v.y);
}
double dot(Vector2D& v) {//与向量v点乘
return x * v.x + y * v.y;
}
double norm() {//向量自身模的平方
return x * x + y * y;
}
Vector2D project(Vector2D& v) {
// 顶点u向量向轴v投影,返回投影后的新向量
Vector2D u = Vector2D(x, y);
double newX = u.dot(v) * v.x / v.norm();
double newY = u.dot(v) * v.y / v.norm();
return Vector2D(newX, newY);
}
};
//矩形定义为按顺时针或逆时针输入的四个点坐标
class Rectangle {
public:
vector<Vector2D> point;//矩形的四个顶点
Rectangle(double _x1, double _y1, double _x2, double _y2, double _x3, double _y3, double _x4, double _y4) {
point.emplace_back(Vector2D(_x1, _y1));
point.emplace_back(Vector2D(_x2, _y2));
point.emplace_back(Vector2D(_x3, _y3));
point.emplace_back(Vector2D(_x4, _y4));
}
Vector2D getSideVec() {//获取(x1,y1)和(x2,y2)所构成的边的向量, 方向(x1,y1)-->(x2,y2)
return Vector2D(point[1].x - point[0].x, point[1].y - point[0].y);
}
Vector2D getNeighborVec() {//获取相邻边即(x2,y2)和(x3,y3)边对应的向量,方向(x3,y3)-->(x2,y2)
return Vector2D(point[1].x - point[2].x, point[1].y - point[2].y);
}
};
bool isIntersect(Rectangle& r1, Rectangle& r2) {
bool res = false;
// 1.将每个矩形相邻两边的向量作为投影基准
vector<Vector2D> axis;//基准轴集合
axis.push_back(r1.getSideVec());
axis.push_back(r1.getNeighborVec());
axis.push_back(r2.getSideVec());
axis.push_back(r2.getNeighborVec());
// 2.将两个矩形每个顶点分别向投影轴进行投影,
// 如果所有轴显示重叠,则存在碰撞,如果有其中一个轴显示没有重叠,则没有碰撞可以之间结束判断
for (int i = 0; i < axis.size(); i++) {
//将矩形1的四个点(Point[j])投影到轴(axis[i])上
double r1_max = DBL_MIN;
double r1_min = DBL_MAX;
for (int j = 0; j < 4; j++) {
Vector2D newPoint = r1.point[j].project(axis[i]);
// 3.计算标量值以识别矩形在轴上的最大和最小投影向量
// 虽然使用向量的范数(长度)似乎很自然,但这不起作用因为具有负值的坐标将返回正标量值。
// 因此,采用每个向量和轴的点积,虽然是毫无意义的标量值,但是该值将指示向量在轴上的位置。
double value = newPoint.dot(axis[i]);
r1_max = max(r1_max, value);
r1_min = min(r1_min, value);
}
//将矩形2的四个点投影到轴上
double r2_max = DBL_MIN;
double r2_min = DBL_MAX;
for (int j = 0; j < 4; j++) {
Vector2D newPoint = r2.point[j].project(axis[i]);
double value = newPoint.dot(axis[i]);
r2_max = max(r2_max, value);
r2_min = min(r2_min, value);
}
// 4.如果B的最小标量值小于或等于A的最大标量值
// 并且B的最大标量值是大于或等于A的最小标量值,则两个矩形在投影到该轴时重叠
if (r2_min <= r1_max && r2_max >= r1_min) {
res = true;
}
else {
res = false;
return res;
}
}
return res;
}
int main()
{
//初始化两个矩形的类
Rectangle r1 = Rectangle(0.0, 0.0, 3.2, 2.4, 5.0, 0.0, 1.8, -2.4);
Rectangle r2 = Rectangle(0.0, 0.0, -3.2, 2.4, -5.0, 0.0, -1.8, -2.4);
bool res = isIntersect(r1, r2);
if (res) {
cout << "相交" << endl;
}
else {
cout << "不相交" << endl;
}
}
或者检查投影时把2个矩形的半径距离投影到轴上,然后把2个矩形的中心点连线投影到轴上,判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小。
输入: 两个矩形的左上角顶点、长、宽、旋转角度
输出: 两个矩形是否相交
代码如下:
#include <iostream>
#include <vector>
using namespace std;
//求解方法:分离轴定律
//两个凸多边形物体,如果我们能找到一个轴,使得两个在物体在该轴上的投影互不重叠,
//则这两个物体之间没有碰撞发生,该轴为Separating Axis。
//也就是说两个多边形在所有轴上的投影都发生重叠,则判定为碰撞;否则,没有发生碰撞。
//矩形有4条边,那么就有4条轴,由于矩形的对边是平行的,所以有两条轴是重复的,
//我们仅需要检查相邻的两个轴,那么两个矩形就需要检查4个轴。
//
//检查投影的方法有两种:
// 1.把每个矩形的4个顶点投影到一个轴上,这样算出4个顶点最长的连线距离,然后同样对待第二个矩形,最后判断2个矩形投影距离是否重叠
// 2.把2个矩形的半径距离投影到轴上,以后把2个矩形的中心点连线投影到轴上,以后判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小
//
class Vector2D {
public:
double x, y;
Vector2D(double _x, double _y) {
x = _x;
y = _y;
}
Vector2D operator -(Vector2D& v) {
return Vector2D(x - v.x, y - v.y);
}
double dot(Vector2D& v) {//向量点乘
return x * v.x + y * v.y;
}
double norm(Vector2D& v) {//向量模
return sqrt(v.x * v.x + v.y * v.y);
}
double project(Vector2D& v) {
// u * v = |u| |v| cos
// |u| cos = u * v / |v|
// u向v投影
Vector2D u = Vector2D(x, y);
return u.dot(v) / norm(v);
}
};
// (x,y) x+width
// ---------------
// | |
// | |
// | |
// | |
// ---------------
// x+length
class Rectangle {
public:
double x, y, length, width, rotation;
Rectangle(double _x, double _y, double _length, double _width, double _rotation) {//定义矩形
x = _x;//x和y为左上角顶点
y = _y;
length = _length; // y 长为沿y方向的边的长度
width = _width; // x 宽为沿x方向的边的长度
rotation = _rotation;
}
Vector2D getWidthVec() {//获取宽边对应的向量
return Vector2D(cos(rotation), sin(rotation));
}
Vector2D getLengthVec() {//获取长边对应的向量,因为是从顶点出发所以纵坐标反向
return Vector2D(sin(rotation), -cos(rotation));
}
Vector2D getCenterPoint() {//获取中心点对应的向量
double centerX = x + (length * cos(rotation) - width * sin(rotation)) / 2.0;
double centerY = y + (length * sin(rotation) + width * cos(rotation)) / 2.0;
return Vector2D(centerX, centerY);
}
Vector2D getHalfDiagonalVec() {//获取对角线一半的向量
return Vector2D((length * cos(rotation) - width * sin(rotation)) / 2.0,
(length * sin(rotation) + width * cos(rotation)) / 2.0);
}
};
bool isIntersect(Rectangle& r1, Rectangle& r2) {
// 1. 以任意一条矩形的边向量作为投影基准
vector<Vector2D> v;
v.push_back(r1.getWidthVec());
v.push_back(r1.getLengthVec());
v.push_back(r2.getWidthVec());
v.push_back(r2.getLengthVec());
// 2. 获得两个矩形的对角线半个向量
Vector2D d1 = r1.getHalfDiagonalVec();
Vector2D d2 = r2.getHalfDiagonalVec();
// 3. 获得两个矩形的中心点向量
Vector2D c1 = r1.getCenterPoint();
Vector2D c2 = r2.getCenterPoint();
cout << c1.x << " " << c1.y << "," << c2.x << " " << c2.y << endl;
Vector2D c = c2 - c1;
for (int i = 0; i < 4; i++) {
// 4. 每次选择一个基准向量,将两个矩形的对角线向量和中心线向量投影
double p1 = d1.project(v[i]);
double p2 = d2.project(v[i]);
double p = c.project(v[i]);
// 5. 投影和的距离关系
// cout << p1 << " " << p2 << " " << p << endl;
if (abs(p1 + p2) < abs(p)) return false;
}
return true;
}
int main()
{
// 初始化两个矩形的类
Rectangle r1 = Rectangle(1, 0, sqrt(2), sqrt(2), 3.14 / 4);
Rectangle r2 = Rectangle(3, 0, sqrt(2), sqrt(2), 3.14 / 4);
bool res = isIntersect(r1, r2);
if (res) cout << "相交" << endl;
else cout << "不相交" << endl;
}
5.判断点在线段上的投影点是否在线段内:
输入: N组的线段两端点坐标、点P坐标
输出: 投影点在线段左、右、上。
代码如下:
#include <iostream>;
using namespace std;
class Vector {
public:
// position: (x, y) --> (v.x, v.y)
float x;
float y;
Vector(float _x, float _y) {
x = _x;
y = _y;
}
Vector operator - (Vector& v) {
return Vector(x - v.x, y - v.y);
}
float dot(Vector& v) {
return x * v.x + y * v.y;
}
};
int PointRelations(Vector A, Vector B, Vector P) {
Vector AB = B - A;
Vector AP = P - A;
float v1 = AB.dot(AP);
float v2 = AB.dot(AB);
if (v1 < 0) return 0;
else {
if (v1 < v2) return 1;
else return 2;
}
return -1;
}
int main() {
// input:
// n
// ax, ay
// bx, by
// px, py
// output: left/right/middle
FILE* stream;
freopen_s(&stream, "input.txt", "r", stdin);
int n = 0;
float ax, ay, bx, by, px, py;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> ax >> ay;
cin >> bx >> by;
cin >> px >> py;
Vector A(ax, ay);
Vector B(bx, by);
Vector P(px, py);
int res = PointRelations(A, B, P);
if (res == 0) {
cout << "left" << endl;
}
else if (res == 1) {
cout << "middle" << endl;
}
else if (res == 2) {
cout << "right" << endl;
}
else {
cout << "Value Error: Please check the dimension of input parameters." << endl;
}
}
}
6.点与直线的关系:
判断点在直线左侧还是右侧。考虑向量叉乘实现。
输入: N组的直线上两点A、B坐标、点P坐标
输出: 点在直线左、右侧。
代码如下:
#include <iostream>;
using namespace std;
class Vector {
public:
// position: (x, y) --> (v.x, v.y)
float x;
float y;
Vector(float _x, float _y) {
x = _x;
y = _y;
}
Vector operator - (Vector& v) {
return Vector(x - v.x, y - v.y);
}
float cross(Vector& v) {
return x * v.y - y * v.x;
}
};
bool SameSide(Vector A, Vector B, Vector P) {
Vector AB = B - A;
Vector AP = P - A;
float v1 = AB.cross(AP);
return v1 > 0;//大于0说明P在AB的逆时针方向
}
int main() {
// input:
// n
// ax, ay
// bx, by
// px, py
// output: left/right
FILE* stream;
freopen_s(&stream, "input.txt", "r", stdin);
int n = 0;
float ax, ay, bx, by, px, py;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> ax >> ay;
cin >> bx >> by;
cin >> px >> py;
Vector A(ax, ay);
Vector B(bx, by);
Vector P(px, py);
bool res = SameSide(A, B, P);
if (res) {
cout << "left" << endl;
}
else {
cout << "right" << endl;
};
}
}
7.折线段等分
给定一组折线段,同时给一个整数k,要用k个点将这组折线段等分,求这k个点的坐标。
输入: 一组折线段(按点的形式给出,点与点之间认为是一条线段)
输出: k个点的坐标
思路:先求出这组折线段的总长度,用k个点等分,那就是分成k+1段,得到每段等分的长度。再对每条线段从起点开始,计算等距离的分离点,但需要注意细节(长度如果超出当前线段,需要从下一条线段上接着算)。
代码如下:
#include<iostream>
#include<vector>
using namespace std;
double eps = 1e-8;
double getDistance(vector<double>& point1, vector<double>& point2) {
// 计算两点间距离
return sqrt((point1[0] - point2[0]) * (point1[0] - point2[0]) + (point1[1] - point2[1]) * (point1[1] - point2[1]));
}
vector<vector<double>> getSeperatePoints(vector<vector<double>>& points, int k) {
// k表示要将这组折线段分成k+1段
vector<vector<double>> res;//最终的k个点坐标
// 1. 计算总的折线长度,并且计算出每一段的平均长度
double totalLen = 0;
for (int i = 1; i < points.size(); i++) {
totalLen += getDistance(points[i - 1], points[i]);
}
double avgLen = totalLen / (k + 1.0); // 等分的长度
//cout << "avgLen = " << avgLen << endl;
double curLen = avgLen; // 记录距离当前线段起点的距离
double x1 = points[0][0];// 取出第一个点作为当前线段起点
double y1 = points[0][1];
int count = 0; // 统计累计点的个数
// 2. 计算每个等分点
for (int j = 1; j < points.size(); j++) {
double x2 = points[j][0];
double y2 = points[j][1];
double line_distance = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)); // 计算每一个单独线段的长度
//cout << "line_distance: " << line_distance << endl;
while (curLen <= line_distance) {
//当前长度还没有超出整个线段,继续在当前线段上计算分离点坐标
double x = x1 + (curLen / line_distance) * (x2 - x1);
double y = y1 + (curLen / line_distance) * (y2 - y1);
//cout << "x = " << x << " y = " << y << endl;
if (abs(x - x2) < eps && abs(y - y2) < eps && j == points.size() - 1) {
// 此时第k+1个等分点和这组线段的最后一个点重合,不应放在结果中
break;
}
res.push_back({ x, y });//保存分离点
curLen += avgLen;//加上等分距离方便进行下一个点的计算
}
//此时已经超出线段长度,需要从下一段开始计算
curLen -= line_distance;//减掉线段长度得到超出部分的长度
x1 = x2;//当前线段起点更新
y1 = y2;
}
return res;
}
int main() {
//vector<vector<double>> points = { {0,0},{1,0},{2,0},{4,0} };
//double k = 2;//k个等分点,把一组折线段分成k+1段
vector<vector<double>> points = { {0, 0}, {-1, 1}, {-2, 2}, {-3, 3}, {-6, 0} };
int k = 5;
vector<vector<double>> res = getSeperatePoints(points, k);
for (int i = 0; i < res.size(); i++) {
cout << res[i][0] << " " << res[i][1] << endl;
}
return 0;
}
8.最小覆盖圆(洛谷P1742):
给出N个点,让你画一个最小的包含所有点的圆。
采用随即增量法,期望复杂度为,需要先将所有点随机打乱。
输入: 点数N,后N行为各点坐标
输出: 圆心坐标,以及半径
代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//最小覆盖圆:
//给出N个点,画一个最小的包含所有点的圆
//输入:点数N,再一行给各点坐标
//输出:圆的半径、圆心坐标
double eps = 1e-8;
class Node {
public:
double x, y;
Node() : x(0), y(0) {}
Node(double _x, double _y) : x(_x), y(_y) {}
};
int sgn(double x) {
if (abs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
double getDistance(Node A, Node B) {//求两点距离
return sqrt(pow(A.x - B.x, 2) + pow(A.y - B.y, 2));
}
Node circle_center(Node a, Node b, Node c) {
//求三角形外接圆圆心,两条边的垂直平分线交点就是
double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1 * a1 + b1 * b1) / 2.0;//第一条边的中垂线
double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2 * a2 + b2 * b2) / 2.0;
double d = a1 * b2 - a2 * b1;
double x = a.x + (c1 * b2 - c2 * b1) / d;//圆心坐标
double y = a.y + (a1 * c2 - a2 * c1) / d;
Node center = Node(x, y);
return center;
}
Node min_cover_circle(vector<Node>& points) {
random_shuffle(points.begin(), points.end());//打乱所有点
double r = 0.0;//半径
Node center = points[0];//圆心
for (int i = 1; i < points.size(); i++) {//剩下所有点
if (sgn(getDistance(points[i], center) - r) > 0) {//pi在园外
center = points[i];
r = 0.0;
for (int j = 0; j < i; j++) {//重新检查前面的点
if (sgn(getDistance(points[j], center) - r) > 0) {
//两点定圆
center.x = (points[i].x + points[j].x) / 2.0;
center.y = (points[i].y + points[j].y) / 2.0;
r = getDistance(points[j], center);
for (int k = 0; k < j; k++) {//检查第三个点
if (sgn(getDistance(points[k], center) - r) > 0) {
center = circle_center(points[i], points[j], points[k]);
r = getDistance(points[i], center);
}
}
}
}
}
}
cout << "Radius is: " << r << endl;
return center;
}
int main()
{
int n;
cin >> n;
vector<Node> points;
for (int i = 0; i < n; i++) {
Node node = Node();
cin >> node.x >> node.y;
points.push_back(node);
}
Node res = min_cover_circle(points);
cout << "x = " << res.x << " y = " << res.y;
return 0;
}
9.圆(洛谷P1652)
给出个圆,保证任意两个圆都不相交且不相切。然后给出两个点,保证均不在某个圆上。现在要从画条曲线,问这条曲线最少穿过多少次圆的边界?
输入: 第一行为一个整数n,表示圆的个数;第二行是n个整数,表示n个圆的x坐标;第三行是n个整数,表示n个圆的y坐标;第四行是n个整数,表示n个圆的半径;第五行四个整数
输出: 仅一个整数,表示最少要穿过多少次圆的边界。
思路:答案就是所有把两个点分隔的圆的个数,也就是其中一个在圆内,另一个在圆外的圆的个数。判断一个在圆内,另一个在圆外时,可以用异或,看两点到圆心的距离是否小于(不能等于)半径。
代码如下:
#include <iostream>
#include <vector>
using namespace std;
double getDistance(int x1, int y1, int x2, int y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
int main()
{
//输入:
//7
//1 -3 2 5 -4 12 12
//1 -1 2 5 5 1 1
//8 1 2 1 1 1 2
//-5 1 12 1
//输出:3
int n;//n个圆
cin >> n;
vector<int> x(n);
vector<int> y(n);
vector<int> r(n);
int x1, y1, x2, y2;
for (int i = 0; i < n; i++) {
cin >> x[i];
}
for (int i = 0; i < n; i++) {
cin >> y[i];
}
for (int i = 0; i < n; i++) {
cin >> r[i];
}
cin >> x1 >> y1 >> x2 >> y2;
int res = 0;
for (int i = 0; i < n; i++) {
if ((getDistance(x1, y1, x[i], y[i]) < r[i]) ^ (getDistance(x2, y2, x[i], y[i]) < r[i])) {
res++;
}
}
cout << res << endl;
return 0;
}
10.移动的圆(炼码21)
题目将给出两个圆和的圆心坐标和半径,现给你一个点,使圆圆心沿直线运动至点。请问圆A在运动过程中是否会与圆B相交?(运动过程包括起点和终点),相交返回1,否则返回-1。
输入: 一组点:
输出: 是否相交
代码如下:
class Solution {
public:
/**
* @param position: the position of circle A,B and point P.
* @return: if two circle intersect return 1, otherwise -1.
*/
int ifIntersect(vector<double> &position) {
// 这里可以将连线轨迹形成一个矩形,判断矩形和B是否相交。然后在起点和终点特殊判断
double xA = position[0];//圆A横坐标
double yA = position[1];//圆A纵坐标
double rA = position[2];
double xB = position[3];
double yB = position[4];
double rB = position[5];
double xP = position[6];
double yP = position[7];
double disAB = sqrt((xA - xB) * (xA - xB) + (yA - yB) * (yA - yB));//移动前A B圆心的距离
double disPB = sqrt((xP - xB) * (xP - xB) + (yP - yB) * (yP - yB));//移动后两圆的圆心距离
// 两个圆相交的充要条件是两圆圆心距大于两半径差的绝对值,小于两半径和,相切就加上等于。
double minDis = abs(rA - rB);
double maxDis = rA + rB;
// 先判断起点和终点是否相交
if ((minDis <= disAB && disAB <= maxDis) || (minDis <= disPB && disPB <= maxDis)) {
return 1;
}
// 求B到AP的垂足
// 先求出AP直线方程
double a = yP - yA;
double b = xA - xP;
double c = xP * yA - xA * yP;
// B到AP的距离
double dist = abs(a * xB + b * yB + c) / sqrt(a * a + b * b);
// 求垂足坐标
double x0 = (b * b * xB - a * b * yB - a * c) / (a * a + b * b);
double y0 = (a * a * yB - a * b * xB - b * c) / (a * a + b * b);
if (minDis <= dist && dist <= maxDis) {
if (inLine(xA, yA, xP, yP, x0, y0)) {//经过垂足点
return 1;
}
}
return -1;
}
bool inLine(double xA, double yA, double xP, double yP, double x0, double y0) {
double maxX, minX, maxY, minY;
maxX = max(xA, xP);
minX = min(xA, xP);
maxY = max(yA, yP);
minY = min(yA, yP);
if (((x0 - xA) * (yP - yA) - (y0 - yA) * (xP - xA) == 0) &&
(minX <= x0 && x0 <= maxX) &&
(minY <= y0 && y0 <= maxY)
) {
return true;
}
return false;
}
};
11.凸多边形(炼码886)
给定一组点的数组,当一个多边形按顺序连接时,发现这个多边形是凸多边形。
输入: 一组点的坐标
输出: 是否为凸多边形
思路:依次求多边形定点连线的向量(不要忘了最后一个定点指向第一个定点的向量),依次计算并判断向量叉乘值的符号是否一致。当时,b在a的顺时针方向,当时,两者共线,当时,b在a的逆时针方向。但一开始并不知道顺时针还是逆时针方向,所以只用判断依次连成的边向量是否叉乘符号一致,一致就说明点依次顺时针或逆时针连接,即为凸多边形。
代码如下:
class Vector {
public:
int x, y;
Vector() : x(0), y(0) {}
Vector(int _x, int _y) : x(_x), y(_y) {}
};
class Solution {
public:
/**
* @param point: a list of two-tuples
* @return: a boolean, denote whether the polygon is convex
*/
bool isConvex(vector<vector<int>> &point) {
// write your code here
// 判断顺序的N条边是否叉积同号
vector<Vector> edges(point.size());//定义边数组
for (int i = 0; i < point.size(); i++) {
edges[i].x = point[(i + 1) % point.size()][0] - point[i][0];
edges[i].y = point[(i + 1) % point.size()][1] - point[i][1];
}
int pre = 0;//上一组边的叉积
int cur = 0;//当前叉积
for (int i = 0; i < edges.size(); i++) {
// 依次判断各相邻边向量叉积是否同号
cur = edges[i].x * edges[(i + 1) % edges.size()].y - edges[(i + 1) % edges.size()].x * edges[i].y;
if (cur != 0) {
if (cur * pre < 0) {
// 上一次叉积结果与此次异号,无法构成多边形
return false;
}
pre = cur;
}
}
return true;
}
};