10.1 凸包算法 (Convex Hull Algorithms)

1. 理论基础

1.1 凸包的数学定义

定义 10.1 (凸集):对于 d 维欧几里得空间中的点集 S,如果对于任意两点 p, q ∈ S,连接它们的线段完全包含在 S 中,则称 S 是凸集。

定义 10.2 (凸包):给定 d 维空间中的点集 P,P 的凸包 (Convex Hull) 是包含 P 的最小凸集,记作 CH(P)。等价地,凸包可以表示为 P 中点的所有凸组合的集合:

性质

  • 凸包是唯一的
  • 凸包的顶点都是原集合 P 中的点
  • 在二维空间中,凸包是一个凸多边形
  • 在三维空间中,凸包是一个凸多面体

1.2 不同维度的凸包

维度几何对象边界元素复杂度
2D凸多边形边 (线段)O(n log n)
3D凸多面体面 (三角形)O(n log n)
dD凸多胞体(d-1)维单形O(n^{⌊d/2⌋})

2. CGAL 凸包模块架构

2.1 模块组织

CGAL 的凸包功能分布在三个主要模块中:

Convex_hull_2/          # 二维凸包
  include/CGAL/
    convex_hull_2.h           # 主接口
    ch_graham_andrew.h        # Graham-Andrew 算法
    ch_akl_toussaint.h        # AKL-Toussaint 算法
    ch_bykat.h                # Bykat 算法 (QuickHull)
    ch_jarvis.h               # Jarvis 步进算法
    ch_melkman.h              # Melkman 算法 (简单多边形)
    ch_eddy.h                 # Eddy 算法
    convex_hull_traits_2.h    # 2D 几何特征
    Convex_hull_traits_adapter_2.h  # 适配器模式

Convex_hull_3/          # 三维凸包
  include/CGAL/
    Convex_hull_traits_3.h    # 3D 几何特征
    Convex_hull_face_base_2.h # 面基类
    Convex_hull_vertex_base_2.h # 顶点基类

Convex_hull_d/          # 高维凸包
  include/CGAL/
    Convex_hull_d.h           # d 维凸包类
    Convex_hull_d_traits_3.h  # 3D 特征适配器
    Convex_hull_d_to_polyhedron_3.h  # 转换器

2.2 算法选择策略

CGAL 根据迭代器类型自动选择最优算法:

// 来自 convex_hull_2.h
template <class InputIterator, class OutputIterator, class Traits>
inline OutputIterator CGAL_convex_hull_points_2(
    InputIterator first, InputIterator last,
    OutputIterator result, const Traits& ch_traits,
    std::input_iterator_tag)  // 输入迭代器 → Bykat (QuickHull)
{ return ch_bykat(first, last, result, ch_traits); }
 
template <class InputIterator, class OutputIterator, class Traits>
inline OutputIterator CGAL_convex_hull_points_2(
    InputIterator first, InputIterator last,
    OutputIterator result, const Traits& ch_traits,
    std::forward_iterator_tag)  // 前向迭代器 → AKL-Toussaint
{ return ch_akl_toussaint(first, last, result, ch_traits); }
 
template <class InputIterator, class OutputIterator, class Traits>
inline OutputIterator CGAL_convex_hull_points_2(
    InputIterator first, InputIterator last,
    OutputIterator result, const Traits& ch_traits,
    std::random_access_iterator_tag)  // 随机访问迭代器 → AKL-Toussaint
{ return ch_akl_toussaint(first, last, result, ch_traits); }

3. 二维凸包算法详解

3.1 AKL-Toussaint 算法

AKL-Toussaint 算法是一种输出敏感的凸包算法,通过快速排除内部点来提高效率。

算法思想

  1. 找到极值点(x 最小/最大,y 最小/最大)
  2. 构造初始四边形
  3. 排除四边形内部的点
  4. 对剩余点分别处理四个区域

复杂度

  • 最坏情况:O(n log n)
  • 平均情况:O(n)(对于随机分布的点)

3.2 Graham-Andrew 算法 (单调链)

Graham-Andrew 算法是 Graham 扫描的变体,使用字典序排序。

算法步骤

  1. 按 x 坐标排序(x 相同则按 y)
  2. 分别构造下凸壳和上凸壳
  3. 合并两个凸壳
// 核心扫描过程 (ch_graham_andrew_impl.h)
template <class BidirectionalIterator, class OutputIterator, class Traits>
OutputIterator ch_graham_andrew_scan(
    BidirectionalIterator first,
    BidirectionalIterator last,
    OutputIterator result,
    const Traits& ch_traits)
{
    typedef typename Traits::Left_turn_2 Left_turn_2;
    Left_turn_2 left_turn = ch_traits.left_turn_2_object();
    
    // 使用栈维护凸包顶点
    std::vector<Point> hull;
    for (auto it = first; it \!= last; ++it) {
        while (hull.size() >= 2 && 
               \!left_turn(hull[hull.size()-2], hull.back(), *it)) {
            hull.pop_back();  // 移除非左转的点
        }
        hull.push_back(*it);
    }
    // ...
}

3.3 QuickHull (Bykat) 算法

QuickHull 是一种分治算法,类似于快速排序。

算法步骤

  1. 找到距离最远的两点(凸包直径)
  2. 将点集分为两个子集
  3. 对每个子集递归构造凸包
  4. 合并结果
// ch_bykat.h - 递归分割策略
template <class InputIterator, class OutputIterator, class Traits>
OutputIterator ch_bykat(
    InputIterator first, InputIterator last,
    OutputIterator result,
    const Traits& ch_traits)
{
    // 找到极值点
    auto [min_pt, max_pt] = find_extreme_points(first, last);
    
    // 分割点集
    auto [left_set, right_set] = partition_points(
        first, last, min_pt, max_pt, ch_traits);
    
    // 递归处理
    quick_hull_recursive(left_set, result, ch_traits);
    quick_hull_recursive(right_set, result, ch_traits);
    
    return result;
}

3.4 Jarvis 步进算法 (Gift Wrapping)

Jarvis 算法模拟包装礼物的过程,沿凸包边界逐步前进。

特点

  • 输出敏感:O(nh),其中 h 是凸包顶点数
  • 适合凸包顶点数较少的情况
  • 简单直观

4. 三维凸包实现

4.1 核心类设计

// Convex_hull_traits_3.h
// 3D 凸包使用 Point_triple 作为平面表示
template <class R_>
class Point_triple {
protected:
    typedef typename R_::FT FT;
    typedef typename R_::Point_3 Point_3;
    Point_3 p_, q_, r_;  // 平面上不共线的三点
public:
    const Point_3& p() const { return p_; }
    const Point_3& q() const { return q_; }
    const Point_3& r() const { return r_; }
};
 
// Convex_hull_traits_3 类
template <class R_, class Polyhedron = Default, 
          class Has_filtered_predicates_tag = ...>
class Convex_hull_traits_3 {
public:
    typedef R_ R;
    typedef typename R::Point_3 Point_3;
    typedef Point_triple<R> Plane_3;  // 用三点表示平面
    
    // 关键谓词
    typedef typename R::Orientation_3 Orientation_3;
    typedef Point_triple_has_on_positive_side_3<R> Has_on_positive_side_3;
    typedef Point_triple_less_signed_distance_to_plane_3<R> 
            Less_signed_distance_to_plane_3;
    
    // 退化情况处理:投影到 2D
    typedef CGAL::Projection_traits_xy_3<R> Traits_xy_3;
    typedef CGAL::Projection_traits_yz_3<R> Traits_yz_3;
    typedef CGAL::Projection_traits_xz_3<R> Traits_xz_3;
};

4.2 增量式算法

三维凸包使用增量式构造算法:

  1. 初始化:构造初始四面体
  2. 增量插入:逐个插入点,更新凸包
  3. 可见性判断:确定哪些面被新点”看到”
  4. 面片更新:移除可见面,添加新面
// 可见性判断核心逻辑
bool is_visible(const Plane_3& plane, const Point_3& point) {
    // 点在平面的正侧
    return orientation(plane.p(), plane.q(), plane.r(), point) == POSITIVE;
}
 
// 处理新插入的点
void insert_point(const Point_3& new_point) {
    std::vector<Face_handle> visible_faces;
    
    // 收集所有可见面
    for (auto face : hull_faces) {
        if (is_visible(face.plane(), new_point)) {
            visible_faces.push_back(face);
        }
    }
    
    // 找到可见区域的边界边
    auto boundary_edges = find_boundary_edges(visible_faces);
    
    // 移除可见面,添加新面
    for (auto face : visible_faces) remove_face(face);
    for (auto edge : boundary_edges) {
        add_face(edge, new_point);
    }
}

5. 高维凸包 (Convex_hull_d)

5.1 单纯复形表示

Convex_hull_d 使用单纯复形 (Simplicial Complex) 表示高维凸包:

// Convex_hull_d.h - 核心数据结构
template <class R_>
class Convex_hull_d : public Regular_complex_d<R_> {
    // 继承自 Regular_complex_d,维护单纯复形结构
    
    // 核心成员
    Point_list all_pnts_;           // 所有插入的点
    Vector_d quasi_center_;         // 原点单形的顶点之和
    Simplex_handle origin_simplex_; // 原点单形
    Facet_handle start_facet_;      // 起始面(用于遍历)
    Vertex_handle anti_origin_;     // 反原点(用于表示无限单形)
    
    // 统计信息
    std::size_t num_of_bounded_simplices;   // 有界单形数
    std::size_t num_of_unbounded_simplices; // 无界单形数
    std::size_t num_of_visibility_tests;    // 可见性测试次数
};

5.2 QuickHull 算法实现

高维 QuickHull 的核心是递归地处理面片:

// 初始化过程
template <class Forward_iterator>
void initialize(Forward_iterator first, Forward_iterator last) {
    // 1. 找到初始单形(d+1 个仿射无关点)
    // 2. 构建初始单形和其对偶的无限单形
    // 3. 将剩余点分配给各个面片
    // 4. 递归处理每个面片
}
 
// 面片处理
void process_facet(Facet_handle f, std::list<Point_d>& points) {
    if (points.empty()) return;
    
    // 找到距离面片最远的点
    Point_d furthest = find_furthest_point(f, points);
    
    // 收集所有可见面
    std::list<Facet_handle> visible_facets;
    visible_facets_search(f, furthest, visible_facets, ...);
    
    // 创建新的单形
    for (auto vf : visible_facets) {
        for (int k = 1; k <= dcur; ++k) {
            if (\!is_base_facet_visible(opposite_simplex(vf, k), furthest)) {
                // 创建新单形
                Simplex_handle T = new_simplex();
                // 设置顶点...
                compute_equation_of_base_facet(T);
            }
        }
    }
}

6. 使用示例

6.1 二维凸包基本用法

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_2.h>
#include <vector>
 
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point_2;
 
int main() {
    std::vector<Point_2> points = {
        Point_2(0, 0), Point_2(10, 0), 
        Point_2(10, 10), Point_2(6, 5), Point_2(4, 1)
    };
    
    std::vector<Point_2> hull;
    CGAL::convex_hull_2(points.begin(), points.end(), 
                        std::back_inserter(hull));
    
    std::cout << hull.size() << " points on the convex hull" << std::endl;
    // 输出: 3 points on the convex hull (0,0), (10,0), (10,10)
    
    return 0;
}

6.2 使用 Traits 适配器

#include <CGAL/Convex_hull_traits_adapter_2.h>
#include <CGAL/property_map.h>
 
// 自定义点类型
struct MyPoint {
    double x, y;
    int id;
};
 
// 创建 property map
struct MyPointMap {
    typedef MyPoint key_type;
    typedef Point_2 value_type;
    typedef Point_2 reference;
    typedef boost::readable_property_map_tag category;
};
 
Point_2 get(MyPointMap, const MyPoint& p) {
    return Point_2(p.x, p.y);
}
 
int main() {
    std::vector<MyPoint> my_points = {...};
    
    // 使用适配器
    typedef CGAL::Convex_hull_traits_adapter_2<K, MyPointMap> Traits;
    Traits traits(MyPointMap());
    
    std::vector<MyPoint> hull;
    CGAL::convex_hull_2(my_points.begin(), my_points.end(), 
                        std::back_inserter(hull), traits);
    
    return 0;
}

6.3 三维凸包

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/convex_hull_3.h>
#include <CGAL/Polyhedron_3.h>
#include <list>
 
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_3 Point_3;
typedef CGAL::Polyhedron_3<K> Polyhedron_3;
 
int main() {
    std::list<Point_3> points = {
        Point_3(0, 0, 0), Point_3(1, 0, 0), Point_3(0, 1, 0),
        Point_3(0, 0, 1), Point_3(0.5, 0.5, 0.5)
    };
    
    Polyhedron_3 poly;
    CGAL::convex_hull_3(points.begin(), points.end(), poly);
    
    std::cout << "Convex hull has " << poly.size_of_vertices() 
              << " vertices and " << poly.size_of_facets() << " facets"
              << std::endl;
    
    return 0;
}

6.4 高维凸包

#include <CGAL/Convex_hull_d.h>
#include <CGAL/Homogeneous_d.h>
 
typedef CGAL::Homogeneous_d<double> Kernel;
typedef CGAL::Convex_hull_d<Kernel> Convex_hull_d;
typedef Convex_hull_d::Point_d Point_d;
 
int main() {
    const int dim = 4;  // 4维空间
    Convex_hull_d ch(dim);
    
    // 插入点
    std::vector<double> coords(dim);
    for (int i = 0; i < 100; ++i) {
        for (int d = 0; d < dim; ++d) {
            coords[d] = rand() / (double)RAND_MAX;
        }
        ch.insert(Point_d(dim, coords.begin(), coords.end()));
    }
    
    std::cout << "Hull has " << ch.number_of_vertices() << " vertices"
              << std::endl;
    
    // 遍历凸包面片
    for (auto fit = ch.facets_begin(); fit \!= ch.facets_end(); ++fit) {
        // 处理每个面片
    }
    
    return 0;
}

7. 复杂度分析

7.1 时间复杂度

算法维度最坏情况平均情况输出敏感
Graham-Andrew2DO(n log n)O(n log n)
AKL-Toussaint2DO(n log n)O(n)
QuickHull2DO(n log n)O(n log n)
Jarvis March2DO(nh)O(nh)
增量式3DO(n log n)O(n log n)
QuickHulldDO(n log n + n^{⌊d/2⌋})-

7.2 空间复杂度

  • 2D 凸包:O(n),用于存储输入点和凸包顶点
  • 3D 凸包:O(n),使用 DCEL 数据结构
  • dD 凸包:O(n^{⌊d/2⌋}),最坏情况下凸包面片数

8. 关键文件位置

文件路径说明
/home/chunibyo/workspace/osc/cgal/Convex_hull_2/include/CGAL/convex_hull_2.h2D 凸包主接口
/home/chunibyo/workspace/osc/cgal/Convex_hull_2/include/CGAL/ch_graham_andrew.hGraham-Andrew 算法
/home/chunibyo/workspace/osc/cgal/Convex_hull_2/include/CGAL/ch_akl_toussaint.hAKL-Toussaint 算法
/home/chunibyo/workspace/osc/cgal/Convex_hull_2/include/CGAL/ch_bykat.hQuickHull 算法
/home/chunibyo/workspace/osc/cgal/Convex_hull_2/include/CGAL/Convex_hull_traits_adapter_2.hTraits 适配器
/home/chunibyo/workspace/osc/cgal/Convex_hull_3/include/CGAL/Convex_hull_traits_3.h3D 凸包 Traits
/home/chunibyo/workspace/osc/cgal/Convex_hull_d/include/CGAL/Convex_hull_d.hd 维凸包类

9. 注意事项

  1. 数值稳定性:建议使用 Exact_predicates_inexact_constructions_kernel (Epick)
  2. 退化情况:处理共线点、共面点时需要特别注意
  3. 输出格式:凸包顶点按逆时针顺序输出(2D)
  4. 内存管理:高维凸包可能消耗大量内存