这道题真的花了我超多的时间,想过不少思路,也有很多弯路,以及各种逻辑的错误。改了好久的BUG终于是把他做出来了。最后写出来的代码不难,挺容易理解的,就是有时候可能想的不会那么周到。每一次的思路都会漏掉一些东西,花了很多时间完善和改BUG。
而且一道基础题写了这么多代码,应该也是一个不怎么好的算法。比赛现场花这么多时间做成这样的话觉得很不划算。
等我做完之后我去搜了搜别人的代码,是我太菜了。
1 题目
题目:矩形面积交
问题描述
平面上有两个矩形,它们的边平行于直角坐标系的X轴或Y轴。对于每个矩形,我们给出它的一对相对顶点的坐标,请你编程算出两个矩形的交的面积。
输入格式
输入仅包含两行,每行描述一个矩形。
在每行中,给出矩形的一对相对顶点的坐标,每个点的坐标都用两个绝对值不超过10^7的实数表示。
输出格式
输出仅包含一个实数,为交的面积,保留到小数后两位。
样例输入
1 1 3 3
2 2 4 4
样例输出
1.00
2 解题过程
2.1 分析
输入的两个顶点分别是对角的两个点的xy坐标,这种表示法让我想起了游戏引擎碰撞检测里面的轴对齐包围盒。
本来想一下觉得其实很简单,就是算一个小矩形的面积。但是仔细想想发现相交的情况至有很多种。用相交的点的数量来分可以分为0个、1个、2个、4个四种类型。 (我这里相交只算两条垂直线段交点)
- 没有交点:
- 一个交点
- 两个交点
- 四个交点
⑨是重合的意思。
同时以左下角为0点,逆时针顺序标记的方式作为四个顶点表示矩形的方式。
顶点在边上算作交点,不算作在矩形内。
可以想到,只要知道黄色矩形对角的两个顶点坐标即可计算面积。而且四个点都知道的时候,即便不知道哪两个点是对角的,只要任意的组合两个找到一个不为零的解即可。因为矩形四个点中任意两个点当作对角点计算,只有0和面积两种结果。
所以我打算先找出相交的所有点,若是四个交点时,从这四个交点中找出面积。若是两个交点时,判断是不是④情况,如果是的话因为只知道两个相邻的点无法计算面积,就需要找到另外两个矩形内的点结合交点计算面积,⑤⑥直接计算得出多少就是多少就好了。如果是一个交点就直接计算,或者可以直接返回0。
如果没有交点,就判断是不是①情况,是就直接通过矩形内的四个点计算面积,不是就是没有相交,面积为0;
2.2 顶点类
两个对角的点就足以表示一个矩形,但是为了方便起见我还是把另外两个顶点也求出来。因为我打算使用一个数组存储一个矩形,所以上面画图我也是从0开始的索引。顺便定义一个Point类方便后续的实现。
Point类
现在判断是否在矩形内的方法只适用于按左下角为0点,逆时针旋转记录顶点位置的方式。 大部分的函数都在这里实现,比如判断在矩形内,判断相交直线的交点,计算面积以及其他辅助性的函数。这里分开来说明好了。
关键的内容,一个x坐标一个y坐标。是double类型,float类型好像会不够用。
class Point{
public double x;
public double y;
}
找出两个矩形的交点。
PointsInCollided:矩形的每一条边与另一矩形的所有边作碰撞比较,把碰撞点都存起来,去除重复点,然后输出一个List。
LineCollided:计算两条线段的交点,s1、e1表示第一条线的起点和终点,s2、e2为第二条线的。因为这里的线段只有水平的和垂直的,所以还是比较好计算的。首先起点和终点的x相同代表线段垂直,y相同代表水平。然后判断两天线段是否平行,平行没有交点(我这里重合也不算交点)。然后垂直的直线必然有交点,交点的x坐标为垂直线段的x,y坐标为水平线段的y。最后判断直线的交点在不在线段中,不在说明线段没有交点,在的话就返回交点位置。
// 找出两个矩形相交的所有点
public static List<Point> PointsInCollided(Point[] rect1, Point[] rect2){
List<Point> points = new ArrayList<Point>();
Point temp;
for(int i=0;i<4;i++) {
for (int j=0;j<4;j++) {
// 对4求余是在数到4号顶点时让他转换为实际的索引0
temp = LineCollided(rect1[i], rect1[(i+1)%4], rect2[j], rect2[(j+1)%4]);
if (temp != null) {
points.add(temp);
}
}
}
// 利用hashset去除重复元素
HashSet<Point> set = new HashSet<Point>(points);
points.clear();
points.addAll(set);
return points;
}
// 找出两条线段的碰撞点
private static Point LineCollided(Point s1, Point e1, Point s2, Point e2) {
Point p = new Point();
// 直线平行
if((s1.x == e1.x && s2.x == e2.x) || (s1.y == e1.y && s2.y == e2.y))
return null;
// 直线相交
if(s1.y == e1.y) {
// 1是水平 2是垂直
p.x = s2.x;
p.y = s1.y;
// 判断交点是否在线段上
if (p.x < Math.min(s1.x, e1.x) || p.x > Math.max(s1.x, e1.x))
return null;
else if (p.y < Math.min(s2.y, e2.y) || p.y > Math.max(s2.y, e2.y))
return null;
}
else {
// 1是垂直 2是水平
p.x = s1.x;
p.y = s2.y;
// 判断交点是否在线段上
if (p.x < Math.min(s2.x, e2.x) || p.x > Math.max(s2.x, e2.x))
return null;
else if (p.y < Math.min(s1.y, e1.y) || p.y > Math.max(s1.y, e1.y))
return null;
}
return p;
}
找到一个矩形所有在另一个矩形中的顶点(顶点在边上不算)。
PointsInRect:矩形中每一个点都判断一边在不在另一个矩形中,把结果存成list输出。
IsInRect:判断点是否在矩形中,简单的使用x、y的大小判断,但顶点数组的表示矩形的形式须满足按左下角为0点,逆时针旋转递增。
// 找出所有Rect1在Rect2中的点
public static List<Point> PointsInRect(Point[] rect1, Point[] rect2) {
List<Point> points = new ArrayList<Point>();
for(int i=0;i<rect1.length;i++) {
if(rect1[i].IsInRect(rect2)) {
points.add(rect1[i]);
}
}
return points;
}
// 判断是否在某个矩形内
// 按左下角为0点,逆时针旋转
public boolean IsInRect(Point[] rect) {
if(x > rect[0].x && x < rect[1].x && y > rect[1].y && y< rect[2].y) {
return true;
}else {
return false;
}
}
其他的一些功能。
CaculateArea:通过两个点计算面积(这里两个点当作是对角点)。
用于保证顶点数组表示矩形的形式。
isSmallerThan:判断是不是比另一个点小。
ExChange:交换两个顶点的内容。
用于HashSet的比较。
重写hashCode:生成哈希码
重写equals:比较是否相等
// 根据对角两个点计算面积
public static double CaculateArea(Point p1, Point p2) {
return Math.abs((p1.x - p2.x) * (p1.y - p2.y));
}
// 判断这个点是不是比自己小(越往左下方越小)
public boolean isSmallerThan(Point p) {
if(x < p.x && y <p.y) {
return true;
}
return false;
}
// 交换变量
public static void ExChange(Point p1,Point p2) {
double tx,ty;
tx = p1.x;
ty = p1.y;
p1.x = p2.x;
p1.y = p2.y;
p2.x = tx;
p2.y = ty;
}
// ========== 用于hashSet ==========
@Override
public int hashCode() {
return (int)(x *x*11 +y * 30);
}
@Override
public boolean equals(Object o) {
Point p = (Point)o;
if(p.x == x && p.y ==y)
return true;
else
return false;
}
2.3 主函数
2.3.1 获取数据
定义两个矩形,然后获取输入数据。因为测试过之后发现他给的数据不一定会把左下角当作第一个数据,有时候会右上角,所以先判断一下,必要的时候交换数据(这里没有考虑右下角和左上角)。
然后再计算出其他点的数据。
Point a[] = new Point[4]; // 矩形A
Point b[] = new Point[4]; // 矩形B
// 初始化
for (int i=0;i<a.length;i++)
a[i] = new Point();
for (int i=0;i<b.length;i++)
b[i] = new Point();
// 输入数据
Scanner scanner = new Scanner(System.in);
a[0].x = scanner.nextDouble();
a[0].y = scanner.nextDouble();
a[2].x = scanner.nextDouble();
a[2].y = scanner.nextDouble();
b[0].x = scanner.nextDouble();
b[0].y = scanner.nextDouble();
b[2].x = scanner.nextDouble();
b[2].y = scanner.nextDouble();
scanner.close();
// 确认左下角才是0号数据
if(a[2].isSmallerThan(a[0]))
Point.ExChange(a[0], a[2]);
if(b[2].isSmallerThan(b[0]))
Point.ExChange(b[0], b[2]);
// 计算余下的四个点的位置
a[3].x = a[0].x;
a[3].y = a[2].y;
a[1].x = a[2].x;
a[1].y = a[0].y;
b[3].x = b[0].x;
b[3].y = b[2].y;
b[1].x = b[2].x;
b[1].y = b[0].y;
2.3.2 判断情况计算
// 计算A在B中的顶点
List<Point> pointsInB = Point.PointsInRect(a, b);
// 计算B在A中的顶点
List<Point> pointsInA = Point.PointsInRect(b, a);
// 结果
double resultArea = 0;
// ============= 判断情况类型计算 =============
List<Point> pointsCol = Point.PointsInCollided(a, b);
// 如果有交点
if(pointsCol.size() > 0) {
// 判断是不是情况四
if(pointsInB.size() == 2) {
for(int i=0;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsInB.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}else if(pointsInA.size() == 2) {
for(int i=0;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsInA.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}
// 其他的情况
else
{
for(int i=1;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsCol.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}
}
// 判断是不是情况一
else if(pointsInB.size() == 4) {
resultArea = Point.CaculateArea(pointsInB.get(0), pointsInB.get(2));
}else if(pointsInA.size() == 4) {
resultArea = Point.CaculateArea(pointsInA.get(0), pointsInA.get(2));
}
// 输出结果
System.out.println(String.format("%.2f", resultArea));
2.4 完整代码
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Point a[] = new Point[4]; // 矩形A
Point b[] = new Point[4]; // 矩形B
// 初始化
for (int i=0;i<a.length;i++)
a[i] = new Point();
for (int i=0;i<b.length;i++)
b[i] = new Point();
// 输入数据
Scanner scanner = new Scanner(System.in);
a[0].x = scanner.nextDouble();
a[0].y = scanner.nextDouble();
a[2].x = scanner.nextDouble();
a[2].y = scanner.nextDouble();
b[0].x = scanner.nextDouble();
b[0].y = scanner.nextDouble();
b[2].x = scanner.nextDouble();
b[2].y = scanner.nextDouble();
scanner.close();
// 确认左下角才是0号数据
if(a[2].isSmallerThan(a[0]))
Point.ExChange(a[0], a[2]);
if(b[2].isSmallerThan(b[0]))
Point.ExChange(b[0], b[2]);
// 计算余下的四个点的位置
a[3].x = a[0].x;
a[3].y = a[2].y;
a[1].x = a[2].x;
a[1].y = a[0].y;
b[3].x = b[0].x;
b[3].y = b[2].y;
b[1].x = b[2].x;
b[1].y = b[0].y;
// 计算A在B中的顶点
List<Point> pointsInB = Point.PointsInRect(a, b);
// 计算B在A中的顶点
List<Point> pointsInA = Point.PointsInRect(b, a);
// 结果
double resultArea = 0;
// ============= 判断情况类型计算 =============
List<Point> pointsCol = Point.PointsInCollided(a, b);
// 如果有交点
if(pointsCol.size() > 0) {
// 判断是不是情况四
if(pointsInB.size() == 2) {
for(int i=0;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsInB.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}else if(pointsInA.size() == 2) {
for(int i=0;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsInA.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}
// 其他的情况
else
{
for(int i=1;i<pointsCol.size();i++) {
resultArea = Point.CaculateArea(pointsCol.get(0), pointsCol.get(i));
if(resultArea != 0) break;
}
}
}
// 判断是不是情况一
else if(pointsInB.size() == 4) {
resultArea = Point.CaculateArea(pointsInB.get(0), pointsInB.get(2));
}else if(pointsInA.size() == 4) {
resultArea = Point.CaculateArea(pointsInA.get(0), pointsInA.get(2));
}
// 输出结果
System.out.println(String.format("%.2f", resultArea));
}
}
class Point{
public double x;
public double y;
// 找出所有Rect1在Rect2中的点
public static List<Point> PointsInRect(Point[] rect1, Point[] rect2) {
List<Point> points = new ArrayList<Point>();
for(int i=0;i<rect1.length;i++) {
if(rect1[i].IsInRect(rect2)) {
points.add(rect1[i]);
}
}
return points;
}
// 找出两个矩形相交的所有点
public static List<Point> PointsInCollided(Point[] rect1, Point[] rect2){
List<Point> points = new ArrayList<Point>();
Point temp;
for(int i=0;i<4;i++) {
for (int j=0;j<4;j++) {
temp = LineCollided(rect1[i], rect1[(i+1)%4], rect2[j], rect2[(j+1)%4]);
if (temp != null) {
points.add(temp);
}
}
}
// 利用hashset去除重复元素
HashSet<Point> set = new HashSet<Point>(points);
points.clear();
points.addAll(set);
return points;
}
// 找出两条线段的碰撞点
private static Point LineCollided(Point s1, Point e1, Point s2, Point e2) {
Point p = new Point();
// 直线平行
if((s1.x == e1.x && s2.x == e2.x) || (s1.y == e1.y && s2.y == e2.y))
return null;
// 直线相交
if(s1.y == e1.y) {
// 1是水平 2是垂直
p.x = s2.x;
p.y = s1.y;
// 判断交点是否在线段上
if (p.x < Math.min(s1.x, e1.x) || p.x > Math.max(s1.x, e1.x))
return null;
else if (p.y < Math.min(s2.y, e2.y) || p.y > Math.max(s2.y, e2.y))
return null;
}
else {
// 1是垂直 2是水平
p.x = s1.x;
p.y = s2.y;
// 判断交点是否在线段上
if (p.x < Math.min(s2.x, e2.x) || p.x > Math.max(s2.x, e2.x))
return null;
else if (p.y < Math.min(s1.y, e1.y) || p.y > Math.max(s1.y, e1.y))
return null;
}
return p;
}
// 判断是否在某个矩形内
// 按左下角为0点,逆时针旋转
public boolean IsInRect(Point[] rect) {
if(x > rect[0].x && x < rect[1].x && y > rect[1].y && y< rect[2].y) {
return true;
}else {
return false;
}
}
// 根据对角两个点计算面积
public static double CaculateArea(Point p1, Point p2) {
return Math.abs((p1.x - p2.x) * (p1.y - p2.y));
}
// 判断这个点是不是比自己小(越往左下方越小)
public boolean isSmallerThan(Point p) {
if(x < p.x && y <p.y) {
return true;
}
return false;
}
// 交换变量
public static void ExChange(Point p1,Point p2) {
double tx,ty;
tx = p1.x;
ty = p1.y;
p1.x = p2.x;
p1.y = p2.y;
p2.x = tx;
p2.y = ty;
}
// ========== 用于hashSet ==========
@Override
public int hashCode() {
return (int)(x *x*11 +y * 30);
}
@Override
public boolean equals(Object o) {
Point p = (Point)o;
if(p.x == x && p.y ==y)
return true;
else
return false;
}
}
也还勉强吧。