多边形

多边形

问题描述

多边形处理平面上由多条线段围成的封闭图形,包括面积计算、点位置判断等。常用于区域判断和几何构造问题。

基本操作

算法思想

  1. 使用点序列表示多边形
  2. 基于向量和叉积计算
  3. 适用于面积和位置判断
  4. 时间复杂度

代码实现

class Polygon {
public:
    vector<Point> points;  // 按顺时针或逆时针顺序存储顶点
    
    Polygon(vector<Point> p = {}) : points(p) {}
    
    // 计算多边形面积
    double area() const {
        double result = 0;
        int n = points.size();
        for (int i = 0; i < n; i++) {
            result += points[i].cross(points[(i + 1) % n]);
        }
        return abs(result) / 2;
    }
    
    // 判断点是否在多边形内部
    // 返回值: -1外部 0边上 1内部
    int containsPoint(const Point& p) const {
        int n = points.size();
        bool on_edge = false;
        int wn = 0;  // winding number
        
        for (int i = 0; i < n; i++) {
            const Point& p1 = points[i];
            const Point& p2 = points[(i + 1) % n];
            
            // 检查点是否在边上
            if (Line(p1, p2).onSegment(p)) {
                return 0;
            }
            
            // 检查射线相交
            if (p1.y <= p.y) {
                if (p2.y > p.y && 
                    (p2 - p1).cross(p - p1) > 0) {
                    wn++;
                }
            } else {
                if (p2.y <= p.y && 
                    (p2 - p1).cross(p - p1) < 0) {
                    wn--;
                }
            }
        }
        
        return wn != 0 ? 1 : -1;
    }
    
    // 计算多边形周长
    double perimeter() const {
        double result = 0;
        int n = points.size();
        for (int i = 0; i < n; i++) {
            result += points[i].distance(points[(i + 1) % n]);
        }
        return result;
    }
};
class Polygon {
    List<Point> points;  // 按顺时针或逆时针顺序存储顶点
    
    Polygon(List<Point> p) {
        this.points = p;
    }
    
    // 计算多边形面积
    double area() {
        double result = 0;
        int n = points.size();
        for (int i = 0; i < n; i++) {
            result += points.get(i).cross(points.get((i + 1) % n));
        }
        return Math.abs(result) / 2;
    }
    
    // 判断点是否在多边形内部
    // 返回值: -1外部 0边上 1内部
    int containsPoint(Point p) {
        int n = points.size();
        boolean onEdge = false;
        int wn = 0;  // winding number
        
        for (int i = 0; i < n; i++) {
            Point p1 = points.get(i);
            Point p2 = points.get((i + 1) % n);
            
            // 检查点是否在边上
            if (new Line(p1, p2).onSegment(p)) {
                return 0;
            }
            
            // 检查射线相交
            if (p1.y <= p.y) {
                if (p2.y > p.y && 
                    p2.subtract(p1).cross(p.subtract(p1)) > 0) {
                    wn++;
                }
            } else {
                if (p2.y <= p.y && 
                    p2.subtract(p1).cross(p.subtract(p1)) < 0) {
                    wn--;
                }
            }
        }
        
        return wn != 0 ? 1 : -1;
    }
    
    // 计算多边形周长
    double perimeter() {
        double result = 0;
        int n = points.size();
        for (int i = 0; i < n; i++) {
            result += points.get(i).distance(points.get((i + 1) % n));
        }
        return result;
    }
}
class Polygon:
    def __init__(self, points: List[Point] = None):
        self.points = points or []  # 按顺时针或逆时针顺序存储顶点
    
    # 计算多边形面积
    def area(self) -> float:
        result = 0
        n = len(self.points)
        for i in range(n):
            result += self.points[i].cross(self.points[(i + 1) % n])
        return abs(result) / 2
    
    # 判断点是否在多边形内部
    # 返回值: -1外部 0边上 1内部
    def contains_point(self, p: Point) -> int:
        n = len(self.points)
        on_edge = False
        wn = 0  # winding number
        
        for i in range(n):
            p1 = self.points[i]
            p2 = self.points[(i + 1) % n]
            
            # 检查点是否在边上
            if Line(p1, p2).on_segment(p):
                return 0
            
            # 检查射线相交
            if p1.y <= p.y:
                if p2.y > p.y and (p2 - p1).cross(p - p1) > 0:
                    wn += 1
            else:
                if p2.y <= p.y and (p2 - p1).cross(p - p1) < 0:
                    wn -= 1
        
        return 1 if wn != 0 else -1
    
    # 计算多边形周长
    def perimeter(self) -> float:
        result = 0
        n = len(self.points)
        for i in range(n):
            result += self.points[i].distance(self.points[(i + 1) % n])
        return result

时间复杂度分析

操作 时间复杂度 空间复杂度
面积计算
点位置判断
周长计算
凸包构造

应用场景

  1. 区域判断
  2. 面积计算
  3. 几何构造
  4. 凸包问题
  5. 碰撞检测

注意事项

  1. 点的顺序
  2. 边界情况
  3. 凸凹性质
  4. 精度问题
  5. 特殊形状

常见变形

  1. 多边形凸包
class Solution {
public:
    // Graham扫描法求凸包
    vector<Point> convexHull(vector<Point>& points) {
        int n = points.size();
        if (n <= 3) return points;
        
        // 找到最下方的点
        int bottom = 0;
        for (int i = 1; i < n; i++) {
            if (points[i].y < points[bottom].y ||
                (points[i].y == points[bottom].y && 
                 points[i].x < points[bottom].x)) {
                bottom = i;
            }
        }
        swap(points[0], points[bottom]);
        
        // 按极角排序
        Point p0 = points[0];
        sort(points.begin() + 1, points.end(),
            [&p0](const Point& a, const Point& b) {
                double cross = (a - p0).cross(b - p0);
                if (abs(cross) < EPS) {
                    return p0.distance(a) < p0.distance(b);
                }
                return cross > 0;
            });
        
        // Graham扫描
        vector<Point> hull = {points[0], points[1]};
        for (int i = 2; i < n; i++) {
            while (hull.size() >= 2 && 
                   (hull.back() - hull[hull.size()-2])
                   .cross(points[i] - hull.back()) <= 0) {
                hull.pop_back();
            }
            hull.push_back(points[i]);
        }
        
        return hull;
    }
};
class Solution {
    // Graham扫描法求凸包
    public List<Point> convexHull(List<Point> points) {
        int n = points.size();
        if (n <= 3) return points;
        
        // 找到最下方的点
        int bottom = 0;
        for (int i = 1; i < n; i++) {
            if (points.get(i).y < points.get(bottom).y ||
                (points.get(i).y == points.get(bottom).y && 
                 points.get(i).x < points.get(bottom).x)) {
                bottom = i;
            }
        }
        Collections.swap(points, 0, bottom);
        
        // 按极角排序
        final Point p0 = points.get(0);
        points.subList(1, n).sort((a, b) -> {
            double cross = a.subtract(p0).cross(b.subtract(p0));
            if (Math.abs(cross) < EPS) {
                return Double.compare(p0.distance(a), p0.distance(b));
            }
            return cross > 0 ? -1 : 1;
        });
        
        // Graham扫描
        List<Point> hull = new ArrayList<>();
        hull.add(points.get(0));
        hull.add(points.get(1));
        
        for (int i = 2; i < n; i++) {
            while (hull.size() >= 2 && 
                   hull.get(hull.size()-1).subtract(
                       hull.get(hull.size()-2))
                   .cross(points.get(i).subtract(
                       hull.get(hull.size()-1))) <= 0) {
                hull.remove(hull.size()-1);
            }
            hull.add(points.get(i));
        }
        
        return hull;
    }
}
class Solution:
    # Graham扫描法求凸包
    def convex_hull(self, points: List[Point]) -> List[Point]:
        n = len(points)
        if n <= 3:
            return points
        
        # 找到最下方的点
        bottom = min(range(n), 
                    key=lambda i: (points[i].y, points[i].x))
        points[0], points[bottom] = points[bottom], points[0]
        
        # 按极角排序
        p0 = points[0]
        points[1:] = sorted(
            points[1:],
            key=lambda p: (
                -(p - p0).cross(Point(1, 0)),
                p0.distance(p)
            )
        )
        
        # Graham扫描
        hull = [points[0], points[1]]
        
        for i in range(2, n):
            while len(hull) >= 2 and \
                  (hull[-1] - hull[-2]).cross(
                      points[i] - hull[-1]) <= 0:
                hull.pop()
            hull.append(points[i])
        
        return hull
  1. 多边形切割
class Solution {
public:
    // 直线切割多边形
    pair<Polygon, Polygon> cutPolygon(const Polygon& poly, 
                                    const Line& line) {
        vector<Point> left, right;
        int n = poly.points.size();
        
        for (int i = 0; i < n; i++) {
            Point cur = poly.points[i];
            Point next = poly.points[(i + 1) % n];
            
            // 当前点的位置
            double cross1 = (line.p2 - line.p1).cross(cur - line.p1);
            
            if (abs(cross1) < EPS) {
                // 点在直线上
                left.push_back(cur);
                right.push_back(cur);
            } else if (cross1 > 0) {
                // 点在直线左侧
                left.push_back(cur);
            } else {
                // 点在直线右侧
                right.push_back(cur);
            }
            
            // 检查边是否与直线相交
            double cross2 = (line.p2 - line.p1).cross(next - line.p1);
            if (cross1 * cross2 < 0) {
                // 边与直线相交
                Point intersection = lineIntersection(
                    Line(cur, next), line
                );
                left.push_back(intersection);
                right.push_back(intersection);
            }
        }
        
        return {Polygon(left), Polygon(right)};
    }
};
class Solution {
    // 直线切割多边形
    public Pair<Polygon, Polygon> cutPolygon(Polygon poly, Line line) {
        List<Point> left = new ArrayList<>();
        List<Point> right = new ArrayList<>();
        int n = poly.points.size();
        
        for (int i = 0; i < n; i++) {
            Point cur = poly.points.get(i);
            Point next = poly.points.get((i + 1) % n);
            
            // 当前点的位置
            double cross1 = line.p2.subtract(line.p1)
                           .cross(cur.subtract(line.p1));
            
            if (Math.abs(cross1) < EPS) {
                // 点在直线上
                left.add(cur);
                right.add(cur);
            } else if (cross1 > 0) {
                // 点在直线左侧
                left.add(cur);
            } else {
                // 点在直线右侧
                right.add(cur);
            }
            
            // 检查边是否与直线相交
            double cross2 = line.p2.subtract(line.p1)
                           .cross(next.subtract(line.p1));
            if (cross1 * cross2 < 0) {
                // 边与直线相交
                Point intersection = lineIntersection(
                    new Line(cur, next), line
                );
                left.add(intersection);
                right.add(intersection);
            }
        }
        
        return new Pair<>(new Polygon(left), new Polygon(right));
    }
}
class Solution:
    # 直线切割多边形
    def cut_polygon(self, poly: Polygon, line: Line) -> Tuple[Polygon, Polygon]:
        left, right = [], []
        n = len(poly.points)
        
        for i in range(n):
            cur = poly.points[i]
            next = poly.points[(i + 1) % n]
            
            # 当前点的位置
            cross1 = (line.p2 - line.p1).cross(cur - line.p1)
            
            if abs(cross1) < self.EPS:
                # 点在直线上
                left.append(cur)
                right.append(cur)
            elif cross1 > 0:
                # 点在直线左侧
                left.append(cur)
            else:
                # 点在直线右侧
                right.append(cur)
            
            # 检查边是否与直线相交
            cross2 = (line.p2 - line.p1).cross(next - line.p1)
            if cross1 * cross2 < 0:
                # 边与直线相交
                intersection = self.line_intersection(
                    Line(cur, next), line
                )
                left.append(intersection)
                right.append(intersection)
        
        return Polygon(left), Polygon(right)
全部评论

相关推荐

点赞 评论 收藏
分享
你背过凌晨4点的八股文么:简历挂了的话会是流程终止,像我一样
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务