16.3 空间搜索结构(Spatial Searching)

相关章节

16.3.0 类比解释:智能快递分拣系统

生活场景类比

想象你管理一个大型智能仓库(空间数据集),每天需要处理数百万个快递包裹(数据点)。客户查询多种多样:

查询类型类比

  1. 范围查询:“找出所有发往北京市海淀区的包裹”
  2. 最近邻查询:“距离客户最近的配送站在哪里?”
  3. k近邻查询:“找到离这个地址最近的3个便利店”
  4. 区间刺探:“哪些包裹会经过郑州中转站?”

传统方法的困境

暴力搜索:
for each package in warehouse:
    if package.destination == "Beijing":
        collect package
        
复杂度:O(n) - 对于100万个包裹,需要100万次检查

空间搜索结构的解决方案

智能分拣系统(范围树):
1. 第一层:按省份分拣(省维度)
2. 第二层:按城市分拣(市维度)
3. 第三层:按区县分拣(区维度)

查询"北京海淀":
- 直接定位到北京分支(跳过其他30个省份)
- 在海淀分支中找到所有包裹
- 总检查次数:O(log n) 层次定位 + O(k) 结果收集

关键洞察:通过预先建立层次化的空间索引,将线性搜索转换为对数级搜索,同时保持空间局部性。

为什么需要空间搜索结构?

问题场景

  • 地图应用:查找用户周围的餐厅
  • 游戏开发:查找玩家视野内的敌人
  • 分子模拟:查找距离某个分子最近的其他分子
  • 数据库:多维数据的范围查询

核心挑战

维度诅咒(Curse of Dimensionality):
- 1D数据:二分查找,O(log n)
- 2D数据:四叉树,范围树,O(log² n)
- d维数据:查询复杂度O(log^d n)

随着维度增加,索引效率下降,需要更复杂的数据结构。

16.3.1 理论基础

空间搜索概述

空间搜索是计算几何中的核心问题,涉及在多维数据集中高效地查找满足特定空间条件的数据。CGAL 提供了多种空间搜索结构,包括范围树、线段树和专门的最近邻搜索算法。

主要查询类型

  1. 范围查询(Range Query)

    • 正交范围查询:查找位于轴对齐包围盒内的所有点
    • 窗口查询(Window Query):d维空间中的矩形范围查询
    • 包含查询(Enclosing Query):查找包含查询区域的所有区间
  2. 最近邻搜索(Nearest Neighbor Search)

    • 单最近邻:查找距离查询点最近的点
    • k近邻(k-NN):查找k个最近的点
    • 半径搜索:查找距离在指定半径内的所有点
  3. 区间刺探(Interval Stabbing)

    • 查找包含给定点的所有区间
    • 动态区间集合的维护

范围树(Range Tree)

范围树是一种分层数据结构,用于高效回答正交范围查询。

结构特点

  • 分层组织:d维范围树由d层二叉搜索树组成
  • 级联结构:每个节点关联一个辅助结构(通常是另一维的范围树)
  • 查询分解:将d维查询分解为O(log^d n)个一维查询

范围树结构可视化

2D范围树示例:点集 {(1,3), (2,5), (3,1), (4,7), (5,2), (6,4)}

第一层(X维度):
                    [3: (3,1)]
                   /          \
          [2: (2,5)]          [5: (5,2)]
          /       \            /        \
    [1: (1,3)]  [4: (4,7)] [6: (6,4)]  (null)
    
每个节点关联的Y维度辅助结构(已排序的Y坐标):
    
    根节点 [3]:     左子树 [2]:      右子树 [5]:
    +--------+      +--------+       +--------+
    | Y: 1   |      | Y: 3   |       | Y: 2   |
    |    3   |      |    5   |       |    4   |
    |    5   |      |    1   |       |        |
    |    7   |      |    7   |       |        |
    |    2   |      |        |       |        |
    |    4   |      |        |       |        |
    +--------+      +--------+       +--------+
    
查询示例:查找 x∈[2,5], y∈[2,4] 的点

Step 1: 在X树中定位范围 [2,5]
        - 找到分割点 x=3
        - 左子树:x∈[2,3](完全在范围内)
        - 右子树:x∈[5,5](部分在范围内)

Step 2: 处理左子树(完全包含)
        - 在节点[2]的Y结构中查询 y∈[2,4]
        - 找到点:(2,5)的y=5不在范围内,(1,3)的x=1不在X范围内
        - 等等,需要重新理解...
        
正确的查询过程:

查询范围:[2,5] × [2,4]

X树分解:
                    [3]
                   /   \
              [2]       [5]
             /   \     /   \
           [1]   [4] [6]   null
           
标准分解:
- 节点[2]:代表范围 (-∞, 3),与查询[2,5]部分重叠
- 节点[5]:代表范围 [3, +∞),与查询[2,5]部分重叠
- 节点[4]:代表范围 (3, 5),完全在查询内!
- 节点[6]:代表范围 [5, +∞),与查询[2,5]在x=5处接触

实际分解结果:
- 完全覆盖的节点:[4] (包含点(4,7))
- 需要继续搜索的节点:[2], [5]

在节点[4]的Y结构中查询y∈[2,4]:
- 点(4,7)的y=7,不在[2,4]内,不收集

在节点[2]的Y结构中查询y∈[2,4]:
- 点(2,5)的y=5,不在范围内
- 点(1,3)的x=1,不在X范围[2,5]内(但Y结构只包含该子树的点)

等等,我需要更准确地理解范围树的结构...

让我重新绘制:

范围树的标准分解:
给定查询范围 [x1, x2] × [y1, y2]

1. 在X树中找到标准分解(canonical decomposition)
   - 找到a和b,使得路径从a到b覆盖[x1, x2]
   - 收集O(log n)个覆盖整个X范围的节点

2. 对每个收集的节点,在其Y结构中查询[y1, y2]

示例的可视化:

点集:P = {(1,3), (2,5), (3,1), (4,7), (5,2), (6,4)}

构建的2D范围树:

Level 1 (X-tree):
                    [3: (3,1)]
                   /          \
          [2: (2,5)]          [5: (5,2)]
          /       \            /        \
    [1: (1,3)]  [4: (4,7)] [6: (6,4)]  null

每个节点的关联结构(Y-sorted list):
- [3]: {(3,1), (1,3), (2,5), (4,7), (5,2), (6,4)} - 所有点按Y排序
- [2]: {(3,1), (1,3), (2,5)} - 左子树的点
- [5]: {(4,7), (5,2), (6,4)} - 右子树的点
- [1]: {(1,3)}
- [4]: {(4,7)}
- [6]: {(6,4)}

查询 [2,5] × [2,4](x在[2,5],y在[2,4]):

Step 1: 在X树中找到标准分解
- 查询x范围[2,5]
- 从根[3]开始:2≤3≤5,需要两边都查
- 左子树[2]:2≤2,完全在左边,继续
- 右子树[5]:5≥5,完全在右边,继续

在[2]节点:范围是(-∞, 3],与[2,5]的交集是[2,3]
- 不是完全覆盖,继续分解
- [2]的左子[1]:范围(-∞, 2],与[2,3]交集是{2}
  - 点(1,3)的x=1≠2,实际上(1,3)不在范围内
  - 等等,[1]包含点(1,3),x=1不在[2,3]内
  - 但标准分解应该保证节点的范围是连续的

让我重新理解范围树的构建...

实际上,范围树的节点存储的是该子树中所有点的一个子集。
在X树中,每个节点代表一个分割值。

标准分解算法:
给定查询[x1, x2],找到一组节点,使得:
1. 这些节点的子树互不相交
2. 这些子树的并集恰好是[x1, x2]范围内的所有点
3. 节点数量为O(log n)

对于查询[2,5]:
- 找到最左边的x≥2的节点:从根开始,2<3,向左到[2],2≥2,找到
- 找到最右边的x≤5的节点:从根开始,5>3,向右到[5],5≤5,找到
- 从[2]到[5]的路径上的某些节点构成标准分解

路径[2]到根到[5]:
[2] -> [3] -> [5]

标准分解节点:
- [2]的右子树:[4](包含点(4,7))
- [5]的左子树:null
- 可能还包括[2]和[5]本身

实际上,对于范围[2,5]:
- 点(2,5):x=2,在范围内
- 点(3,1):x=3,在范围内
- 点(4,7):x=4,在范围内
- 点(5,2):x=5,在范围内

这些点分布在:
- (2,5):节点[2]
- (3,1):节点[3]
- (4,7):节点[4]
- (5,2):节点[5]

标准分解应该给出O(log n)个节点,覆盖所有这些点。
对于2D范围树,查询时间复杂度为O(log²n + k),其中k是结果数量。

让我简化可视化,关注查询过程:

查询动画描述:

Frame 1 (初始状态):
点集:A(1,3), B(2,5), C(3,1), D(4,7), E(5,2), F(6,4)
查询窗口:[2,5] × [2,4](X:2-5, Y:2-4)

    Y
    7 |       D
    6 |
    5 |   B
    4 |           F
    3 | A
    2 |       E
    1 |     C
    0 +-----------------
      1   2   3   4   5   6  X

查询窗口(虚线框):
    +-----------+
    |           |
    |   [2,5]   | Y:[2,4]
    |   ×[2,4]  |
    +-----------+

Frame 2 (X维度过滤):
在X树中定位到范围[2,5]
- 找到相关节点:[2], [3], [4], [5]
- 排除:A(1,3)的x=1<2,F(6,4)的x=6>5

剩余候选点:B(2,5), C(3,1), D(4,7), E(5,2)

Frame 3 (Y维度过滤 - 节点[2]):
检查节点[2]的Y结构(包含B)
B(2,5)的y=5 > 4,不在范围内 ❌

Frame 4 (Y维度过滤 - 节点[3]):
检查节点[3]的Y结构(包含C)
C(3,1)的y=1 < 2,不在范围内 ❌

Frame 5 (Y维度过滤 - 节点[4]):
检查节点[4]的Y结构(包含D)
D(4,7)的y=7 > 4,不在范围内 ❌

Frame 6 (Y维度过滤 - 节点[5]):
检查节点[5]的Y结构(包含E)
E(5,2)的y=2 ∈[2,4],在范围内 ✅

Frame 7 (结果):
找到1个点:E(5,2)

等等,看起来结果不对。让我重新检查点集...
实际上,在[2,5]×[2,4]范围内的点:
- B(2,5):y=5>4,不在
- C(3,1):y=1<2,不在
- D(4,7):y=7>4,不在
- E(5,2):y=2≥2且y=2≤4,在范围内!
- F(6,4):x=6>5,不在

所以确实只有E(5,2)在查询范围内。

让我换一个查询:[1,4] × [2,6]

在范围内的点:
- A(1,3):x=1∈[1,4], y=3∈[2,6] ✅
- B(2,5):x=2∈[1,4], y=5∈[2,6] ✅
- C(3,1):y=1<2,不在
- D(4,7):y=7>6,不在

结果:A(1,3), B(2,5)

好的,现在让我正式写出范围树的查询过程可视化。

**范围树查询过程可视化**:

2D范围树查询动画:查找 x∈[1,4], y∈[2,6] 的点

场景:6个点 A(1,3), B(2,5), C(3,1), D(4,7), E(5,2), F(6,4)

Step 1: 数据结构概览 +------------------------------------------------+ | X-树结构 | 几何分布 | | | | | [3:C] | Y | | / \ | 7 | D | | [2:B] [5:E] | 6 | | | / \ / \ | 5 | B | | [1:A][4:D][6:F] null | 4 | F | | | 3 | A | | 每个节点的Y-结构: | 2 | E | | [3]: C,A,B,D,E,F (全按Y) | 1 | C | | [2]: C,A,B | 0 +-------------| | [5]: D,E,F | 1 2 3 4 5 6 X | [1]: A | | | [4]: D | 查询窗口:[1,4]×[2,6] | | [6]: F | +-------+ | | | | | | +------------------------------------------------+

Step 2: X维度范围查询 [1,4] 在X-树中找到标准分解(canonical nodes)

查询路径:

  • 根[3]:x=3∈[1,4],需要两边都查
  • 左[2]:x=2∈[1,4],继续向左
  • 左-左[1]:x=1∈[1,4],完全覆盖
  • 左-右[4]:x=4∈[1,4],完全覆盖

标准分解节点:[1], [4], [5]的左子树

可视化: [3:C] ← 根,分割点 /
[2:B] [5:E] / \ /
✓→ [1:A] [4:D] [6:F] null ←✗ (x=6>4) ↑ ↑ 完全覆盖 完全覆盖 (x≤1) (x≥4但在范围内)

实际上,标准分解算法确保我们找到O(log n)个节点, 这些节点的子树恰好覆盖查询范围且不重叠。

对于范围[1,4],分解为:

  • 节点[1]:覆盖x∈(-∞, 2)的点,即A(1,3)
  • 节点[4]:覆盖x∈[3, 5)的点,即D(4,7)
  • 还需要覆盖[2,3)的点,即B(2,5)和C(3,1)

实际上,标准分解应该是:

  • 从x=1开始向上到根,收集右子树
  • 从x=4开始向上到根,收集左子树

让我简化,直接给出查询过程…

Step 3: 在标准分解节点上执行Y查询

节点[1](包含点A):

  • A(1,3)的y=3∈[2,6]?是!✓
  • 结果:收集A

节点[2](包含点B,C):

  • 在Y结构中查询y∈[2,6]
  • B(2,5)的y=5∈[2,6]?是!✓
  • C(3,1)的y=1∉[2,6]?否!✗
  • 结果:收集B

节点[4](包含点D):

  • D(4,7)的y=7∉[2,6]?否!✗
  • 结果:无

Step 4: 合并结果 最终结果:{A(1,3), B(2,5)} 查询时间:O(log²n + k) = O(4 + 2) = O(6)


**查询复杂度**:
- 构建:O(n log^{d-1} n)
- 查询:O(log^d n + k),k为结果数量
- 空间:O(n log^{d-1} n)

### 线段树(Segment Tree)

线段树用于存储区间并支持区间刺探查询。

**结构特点**:
- **静态结构**:通常针对静态区间集合构建
- **区间存储**:每个区间存储在O(log n)个节点中
- **完全二叉树**:基于坐标值的范围构建

**线段树结构可视化**:

场景:存储区间 [1,4], [2,5], [3,6], [0,7]

坐标范围:[0, 7]

构建的线段树:

                [0-7]
               /     \
          [0-3]       [4-7]
         /     \      /     \
      [0-1]   [2-3] [4-5]   [6-7]
      /  \    /  \   /  \    /  \
   [0,0][1,1][2,2][3,3][4,4][5,5][6,6][7,7]

区间存储(区间存储在覆盖它的节点中):

[0-3]: [1,4], [2,5](与左半部分重叠) [4-7]: [3,6](与右半部分重叠) [0-1]: [1,4](与[0-1]相交) [2-3]: [1,4], [2,5], [3,6](都与[2-3]相交) [4-5]: [2,5], [3,6](都与[4-5]相交) [6-7]: [3,6](与[6-7]相交)

实际上,线段树的存储规则是: 一个区间存储在节点中,当且仅当:

  1. 节点的范围完全包含在区间内,且
  2. 节点的父节点的范围不完全包含在区间内

重新计算存储位置:

区间[1,4]:

  • 0-7:不完全包含(0∉[1,4])
  • [0-3]:不完全包含(0∉[1,4])
  • [2-3]:完全包含!存储在这里
  • 还需要存储[1,1]部分

标准线段树区间插入算法:

insert_interval(node, interval):
    if node.range completely inside interval:
        store interval at node
        return
    if node is leaf:
        return
    if interval overlaps left child:
        insert_interval(left child, interval)
    if interval overlaps right child:
        insert_interval(right child, interval)

让我重新计算:

区间[1,4]:

  • 0-7:不完全包含,继续
  • 左[0-3]:不完全包含(0∉[1,4]),继续
  • 左-左[0-1]:不完全包含(0∉[1,4]),但相交
  • 左-左-右[1,1]:完全包含!存储
  • 左-右[2-3]:完全包含!存储
  • 右[4-7]:不完全包含(4∈[1,4]但7∉[1,4]),继续
  • 右-左[4-5]:不完全包含(5∉[1,4]),但相交
  • 右-左-左[4,4]:完全包含!存储

所以[1,4]存储在:[1,1], [2,3], [4,4]

类似地: [2,5]存储在:[2,2], [3,3], [4,5] [3,6]存储在:[3,3], [4,5], [6,6] [0,7]存储在:0-7(根节点)

查询示例:刺探查询 x=3.5

搜索路径:

  • 0-7:包含3.5,收集[0,7]
  • 左[0-3]:3.5>3,不进入
  • 右[4-7]:3.5<4,不进入
  • 等等,3.5不在[0-3]也不在[4-7]?

实际上,3.5应该在[4-7]?不,3.5<4。

让我重新理解线段树的结构…

标准线段树将范围划分为不相交的单元。 对于范围[0,7],划分为:[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7]

内部节点的范围是子节点的并集。

点x=3.5不属于任何整数点,但在线段树中,我们找到包含它的最小范围。

实际上,对于刺探查询,我们找到包含查询点的叶节点,然后收集该叶节点到根路径上存储的所有区间。

对于x=3.5:

  • 找到包含3.5的叶节点:实际上,3.5在[3,3]和[4,4]之间

等等,线段树通常处理离散点或连续范围。 对于连续范围,线段树的叶节点代表基本区间。

让我简化,使用整数坐标和基本区间。

重新设计:基本区间为[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]

树结构: 0-7 /
[0-3] [4-7] / \ /
[0-1] [2-3] [4-5] [6-7] / \ / \ / \ /
[0,1][1,2][2,3][3,4][4,5][5,6][6,7]

等等,这不对。让我重新构建…

对于范围[0,7],划分为:

  • 第一层:[0-3], [4-7]
  • 第二层:[0-1], [2-3], [4-5], [6-7]
  • 第三层:[0,0],[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7]

实际上,对于连续坐标,线段树的叶节点对应点,内部节点对应区间。

对于刺探查询x=3.5:

  • 从根0-7开始
  • 3.5∈0-7,进入
  • 3.5∈[0-3]?3.5>3,否
  • 3.5∈[4-7]?3.5<4,否

这不对。让我重新定义范围…

如果[0-3]表示[0,4),[4-7]表示[4,8):

  • 3.5∈[0,4),进入左子树
  • 3.5∈[0,2)?否
  • 3.5∈[2,4)?是,进入[2-3](表示[2,4))
  • 3.5∈[2,3)?否
  • 3.5∈[3,4)?是,到达叶节点

所以包含3.5的叶节点是[3,4)。

从根到该叶节点的路径:0-7 -> [0-3] -> [2-3] -> [3,4)

收集路径上存储的区间:

  • 0-7:存储[0,7]
  • [0-3]:存储[1,4], [2,5]
  • [2-3]:存储[1,4], [2,5], [3,6]
  • [3,4):存储[1,4], [3,6]

合并结果:[0,7], [1,4], [2,5], [3,6]

验证:x=3.5确实在所有这些区间内!


**查询复杂度**:
- 构建:O(n log n)
- 刺探查询:O(log n + k)
- 空间:O(n log n)

---

## 16.3.2 架构分析

### SearchStructures 模块类设计

CGAL 的 SearchStructures 模块提供了预定义维度的范围树和线段树:

Range_tree_1<C_Traits_1> … Range_tree_4<C_Traits_4> Segment_tree_1<C_Traits_1> … Segment_tree_4<C_Traits_4> Range_tree_d<Key, Interval, Traits> Segment_tree_d<Interval, Value, Traits> Tree_anchor<Key, Interval>


#### 范围树类(位于 `/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Range_tree_k.h`)

```cpp
// 一维范围树
template <class C_Traits_1>
class Range_tree_1 {
public:
    typedef C_Traits_1 Traits;
    typedef typename C_Traits_1::Key Key;
    typedef typename C_Traits_1::Interval Interval;
    
    Range_tree_1();
    
    template <class T>
    Range_tree_1(const T& first, const T& last);
    
    template <class T>
    bool make_tree(const T& first, const T& last);
    
    // 窗口查询:查找位于窗口内的所有点
    template <class T>
    T window_query(Interval const &win, const T& result);
    
private:
    typedef tree_point_traits<Key, Interval, Key_1, key_1, low_1, high_1, compare_1> I1;
    typedef Tree_anchor<Key, Interval> Tree_anchor_type;
    typedef Range_tree_d<Key, Interval, I1> Range_tree_1_type;
    
    Tree_anchor_type *anchor;
    Range_tree_1_type *range_tree_1;
};

// 三维范围树
template <class C_Traits_3>
class Range_tree_3 {
public:
    typedef C_Traits_3 Traits;
    typedef typename C_Traits_3::Key Key;
    typedef typename C_Traits_3::Interval Interval;
    
    template <class T>
    T window_query(Interval const &win, const T& result);
    
private:
    // 三层嵌套结构
    typedef Range_tree_d<Key, Interval, I3> Range_tree_1_type;  // 第3维
    typedef Range_tree_d<Key, Interval, I2> Range_tree_2_type;  // 第2维
    typedef Range_tree_d<Key, Interval, I1> Range_tree_3_type;  // 第1维
    
    Tree_anchor_type *anchor;
    Range_tree_1_type *range_tree_1;
    Range_tree_2_type *range_tree_2;
    Range_tree_3_type *range_tree_3;
};

线段树类(位于 /home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Segment_tree_k.h

// 二维线段树
template <class C_Traits_2>
class Segment_tree_2 {
public:
    typedef C_Traits_2 Traits;
    typedef typename C_Traits_2::Interval Interval;
    
    template <class T>
    T window_query(Interval const &win, const T& result);
    
    template <class T>
    T enclosing_query(Interval const &win, const T& result);
    
private:
    // 两层嵌套结构
    typedef Segment_tree_d<Interval, Interval, I2> Segment_tree_1_type;
    typedef Segment_tree_d<Interval, Interval, I1> Segment_tree_2_type;
    
    Tree_anchor_type *anchor;
    Segment_tree_1_type *segment_tree_1;
    Segment_tree_2_type *segment_tree_2;
};

16.3.5 复杂度分析

范围树复杂度

维度构建时间查询时间空间
d=1O(n log n)O(log n + k)O(n)
d=2O(n log n)O(log² n + k)O(n log n)
d=3O(n log² n)O(log³ n + k)O(n log² n)
dO(n log^{d-1} n)O(log^d n + k)O(n log^{d-1} n)

线段树复杂度

操作时间复杂度空间复杂度
构建O(n log n)O(n log n)
刺探查询O(log n + k)-
区间插入O(log n)-
区间删除O(log n)-

最近邻搜索复杂度

算法预处理查询时间空间
线性扫描O(1)O(n)O(1)
k-d 树O(n log n)O(log n) 平均O(n)
正交k近邻O(n log n)O(log n + k) 平均O(n)
增量搜索O(n log n)O(1) 每点O(n)

16.3.6 实用指导

调试技巧

技巧1:可视化k-d树结构

template<typename Tree>
void visualize_kd_tree(const Tree& tree, std::size_t max_depth = 5)
{
    struct Visualizer {
        std::size_t depth;
        
        void visit_node(const auto& node) {
            std::string indent(depth * 2, ' ');
            
            if(node.is_leaf()) {
                std::cout << indent << "[Leaf] Points: " << node.size() << std::endl;
            } else {
                auto bbox = node.bounding_box();
                std::cout << indent << "[Node] Depth: " << depth
                         << " Split_dim: " << node.split_dimension()
                         << " Split_val: " << node.split_value()
                         << std::endl;
                
                ++depth;
                visit_node(node.left());
                visit_node(node.right());
                --depth;
            }
        }
    };
    
    Visualizer vis{0};
    vis.visit_node(tree.root());
}

技巧2:验证查询结果正确性

// 与暴力搜索对比验证
template<typename Tree, typename Query>
void verify_query_results(const Tree& tree, const Query& query, 
                         const std::vector<Point>& points)
{
    // 树查询结果
    auto tree_results = tree.search(query);
    
    // 暴力搜索结果
    std::vector<Point> brute_results;
    for(const auto& p : points) {
        if(query.contains(p)) {
            brute_results.push_back(p);
        }
    }
    
    // 比较
    std::sort(tree_results.begin(), tree_results.end());
    std::sort(brute_results.begin(), brute_results.end());
    
    if(tree_results \!= brute_results) {
        std::cerr << "Query mismatch\!" << std::endl;
        std::cerr << "Tree results: " << tree_results.size() << std::endl;
        std::cerr << "Brute results: " << brute_results.size() << std::endl;
    }
}

常见错误FAQ

Q1: 范围树查询返回了错误的结果?

A: 常见原因:

  • Traits定义不正确,特别是坐标访问函数
  • 窗口定义错误(min/max坐标顺序颠倒)
  • 维数不匹配(2D树查询3D点)
// 错误:坐标顺序颠倒
Interval win(Key(6.0, 2.0), Key(2.0, 4.0));  // max < min!
 
// 正确:min先,max后
Interval win(Key(2.0, 2.0), Key(6.0, 4.0));

Q2: k-d树查询性能不如预期?

A: 优化建议:

  • 使用accelerate_distance_queries()启用距离查询加速
  • 对于高维数据(d>10),k-d树效果下降,考虑使用近似方法
  • 确保树的平衡(使用好的分割策略)
// 启用所有优化
tree.build();
tree.accelerate_distance_queries();
tree.accelerate_range_queries();  // 如果可用

Q3: 如何处理动态数据(插入/删除)?

A: CGAL的空间搜索结构主要是静态的。动态数据策略:

  • 批量重建:积累足够修改后重建整棵树
  • 使用多个树:一个主树(静态)+ 一个辅助树(动态,较小)
  • 定期合并:将辅助树合并回主树

16.3.7 关键文件位置

SearchStructures 模块

文件说明
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Range_tree_k.h1-4维范围树
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Range_tree_d.hd维范围树实现
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Segment_tree_k.h1-4维线段树
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Segment_tree_d.hd维线段树实现
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Tree_base.h树基类
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Tree_traits.hTree traits
/home/chunibyo/workspace/osc/cgal/SearchStructures/include/CGAL/Range_segment_tree_traits.h范围/线段树 traits

Spatial_searching 模块

文件说明
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Orthogonal_k_neighbor_search.h正交k近邻搜索
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Incremental_neighbor_search.h增量最近邻搜索
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/K_neighbor_search.hk近邻搜索基类
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Euclidean_distance.h欧氏距离
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Manhattan_distance_iso_box_point.h曼哈顿距离
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Fuzzy_sphere.h模糊球查询
/home/chunibyo/workspace/osc/cgal/Spatial_searching/include/CGAL/Fuzzy_iso_box.h模糊包围盒查询