几何数据结构之四叉树与八叉树

几何数据结构之四叉树与八叉树

四叉树的定义

四叉树(Quadtree):是一颗包含树根(Root)的树,每个节点包含四个子节点。我们常见用于描述四叉树的形式如下图:

它一个节点可以存储四个子节点的数据。当然,我们还可以采用其他形式来描述四叉树。

如下图所示:

树中每个节点有四个子节点,我们可以把每个节点看作一个正方形,最外围的正方形,包含四个第二大的正方形,同样的,第二大的也包含四个第三大正方形。

当然,我们可以以象限来描述它,一个正方形,被十字叉分为了四个象限,一个象限就代表了一个节点,我们可以不断的递归的去取正方形的边长中点,直到正方形中只存在一个点的。

四叉树是一种空间划分数据结构,通常用于存储二维平面中的点。四叉树将空间递归地划分为四个象限(子区域),并将点插入到对应的子区域中。

插入过程:

每个节点表示一个矩形区域,如果该区域可以容纳更多的点,它就会插入点;如果已经达到阈值(例如最多只能容纳 4 个点),则会进行分割,将区域划分为 4 个子区域。

子区域的插入:每当点超出当前节点的边界时,应该递归到下一个合适的子区域中。如果某个点仍然位于当前节点的范围内,则将其插入到当前节点。

子节点的划分:当节点满了(即点数量超过设定阈值),它将自己分割成 4 个子节点,每个子节点管理一个象限的区域。

四叉树深度的计算公式推导

假设:

  1. 初始区域的边长是 ( S )。
  2. 每个节点容纳的最小距离是 ( C ),即空间中任意两个点之间的最小距离为 ( C ),所以每个子区域将尽可能容纳这种最小距离的点。

计算过程:

1. 划分空间:

四叉树的每一层将空间分为四个象限(子区域)。如果在深度为 ( d ) 的层次,空间被划分为 ( 4^d ) 个子区域,那么每个子区域的大小将是:

子区域边长 = S 2 d \text{子区域边长} = \frac{S}{2^d} 子区域边长=2dS

2. 节点容纳的最小距离:

每个子区域容纳的最小点间距是 ( C ),所以在每个子区域中,至少应该有足够的空间容纳这种最小距离的点。为了确保每个子区域中有两个点之间的最小距离不小于 ( C ),我们有以下条件:

S 2 d ≥ C \frac{S}{2^d} \geq C 2dS≥C

这意味着每个子区域的边长必须大于等于最小点距离 ( C ),否则就需要进一步划分。

3. 解出深度:

根据上面的不等式,求得最小深度 ( d ):

S 2 d ≥ C ⇒ 2 d ≤ S C ⇒ d ≤ log ⁡ 2 ( S C ) \frac{S}{2^d} \geq C \quad \Rightarrow \quad 2^d \leq \frac{S}{C} \quad \Rightarrow \quad d \leq \log_2 \left( \frac{S}{C} \right) 2dS≥C⇒2d≤CS⇒d≤log2(CS)

4. 考虑常数项:

实际应用中,可能还需要考虑额外的调整常数项,通常为 3 2 \frac{3}{2} 23,用于补偿一些实现中的空间管理开销或其他实际细节。因此,四叉树的深度可以表示为:

深度 = log ⁡ 2 ( S C ) + 3 2 \text{深度} = \log_2 \left( \frac{S}{C} \right) + \frac{3}{2} 深度=log2(CS)+23

总结:

  • ( S ) 是初始区域的边长。
  • ( C ) 是任意两个点之间的最小距离,即每个子区域内元素间的最小间距。
  • 最终的四叉树深度大致为:

深度 = log ⁡ 2 ( S C ) + 3 2 \text{深度} = \log_2 \left( \frac{S}{C} \right) + \frac{3}{2} 深度=log2(CS)+23

四叉树大量使用与碰撞检测上面。

下面是简单实现四叉树的代码:

cpp 复制代码
#include <iostream>
#include <vector>
#include <memory>

using namespace std;

// 定义一个点(Point)类,表示一个二维空间中的点
class Point
{
public:
    int x, y;  // 点的 x 和 y 坐标

    // 构造函数,初始化 x 和 y 坐标
    Point(int x, int y) : x(x), y(y) {}
};

// 定义一个边界(Boundary)类,表示一个矩形区域
class Boundary
{
public:
    int x, y, width, height;  // 矩形的中心坐标 (x, y),宽度和高度

    // 构造函数,初始化矩形的中心坐标、宽度和高度
    Boundary(int x, int y, int width, int height)
        : x(x), y(y), width(width), height(height) {}

    // 判断一个点是否在矩形区域内
    bool contains(const Point& point) const
    {
        return point.x >= x - width / 2 && point.x <= x + width / 2 &&
            point.y >= y - height / 2 && point.y <= y + height / 2;
    }

    // 判断一个矩形区域是否与当前矩形区域相交
    bool intersects(const Boundary& range) const
    {
        return !(range.x - range.width / 2 > x + width / 2 ||
            range.x + range.width / 2 < x - width / 2 ||
            range.y - range.height / 2 > y + height / 2 ||
            range.y + range.height / 2 < y - height / 2);
    }
};

// 定义四叉树(QuadTree)类,用于存储二维空间中的点
class QuadTree
{
private:
    Boundary boundary;  // 当前节点的矩形区域
    int capacity;  // 当前节点的最大容量(每个节点最多存储多少个点)
    vector<Point> points;  // 当前节点存储的点
    unique_ptr<QuadTree> northeast;  // 东北子节点
    unique_ptr<QuadTree> northwest;  // 西北子节点
    unique_ptr<QuadTree> southeast;  // 东南子节点
    unique_ptr<QuadTree> southwest;  // 西南子节点

public:
    // 构造函数,初始化边界和容量
    QuadTree(const Boundary& boundary, int capacity)
        : boundary(boundary), capacity(capacity) {}

    // 将当前节点划分为四个子节点
    void subdivide()
    {
        int subWidth = boundary.width / 2;  // 子节点的宽度
        int subHeight = boundary.height / 2;  // 子节点的高度
        int x = boundary.x;  // 父节点的 x 坐标
        int y = boundary.y;  // 父节点的 y 坐标

        // 创建四个子节点,分别对应东北、西北、东南、西南
        northeast = make_unique<QuadTree>(Boundary(x + subWidth / 2, y - subHeight / 2, subWidth, subHeight), capacity);
        northwest = make_unique<QuadTree>(Boundary(x - subWidth / 2, y - subHeight / 2, subWidth, subHeight), capacity);
        southeast = make_unique<QuadTree>(Boundary(x + subWidth / 2, y + subHeight / 2, subWidth, subHeight), capacity);
        southwest = make_unique<QuadTree>(Boundary(x - subWidth / 2, y + subHeight / 2, subWidth, subHeight), capacity);
    }

    // 插入一个点到四叉树中
    bool insert(const Point& point)
    {
        // 如果点超出了当前节点的边界,返回 false
        if (!boundary.contains(point))
        {
            cout << "Point (" << point.x << ", " << point.y << ") is out of bounds." << endl;
            return false;
        }

        // 如果当前节点的点数未超出容量,则直接插入点
        if (points.size() < capacity)
        {
            points.push_back(point);  // 将点加入当前节点的点集合中
            cout << "Point (" << point.x << ", " << point.y << ") inserted in current node." << endl;
            return true;
        }

        // 如果当前节点已超出容量,进行分割
        if (northeast == nullptr)
        {
            cout << "Subdividing node at (" << boundary.x << ", " << boundary.y << ")" << endl;
            subdivide();  // 划分子节点
        }

        // 递归地将点插入到四个子节点中
        if (northeast->insert(point)) return true;
        if (northwest->insert(point)) return true;
        if (southeast->insert(point)) return true;
        if (southwest->insert(point)) return true;

        return false;
    }

    // 打印当前节点及其所有子节点中的点
    void printPoints() const
    {
        // 打印当前节点存储的点
        for (const auto& point : points)
        {
            cout << "(" << point.x << ", " << point.y << ")" << endl;
        }

        // 递归地打印四个子节点中的点
        if (northeast != nullptr) northeast->printPoints();
        if (northwest != nullptr) northwest->printPoints();
        if (southeast != nullptr) southeast->printPoints();
        if (southwest != nullptr) southwest->printPoints();
    }
};

int main()
{
    // 扩大根节点的边界为 20x20
    Boundary boundary(0, 0, 20, 20); 

    // 创建一个四叉树,根节点的容量为 4
    QuadTree qt(boundary, 4);

    // 插入一些点到四叉树
    qt.insert(Point(2, 3));
    qt.insert(Point(3, 5));
    qt.insert(Point(6, 7));
    qt.insert(Point(8, 1));
    qt.insert(Point(1, 1));
    qt.insert(Point(4, 4));

    // 打印树中的所有点
    cout << "Points in the tree:" << endl;
    qt.printPoints();

    return 0;
}

八叉树

八叉树原理与四叉树类似,它可以理解为一个下图:

一个立方体被三个平面分成了八块,它的实现与原理与四叉树类似,在此不过多赘述。

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android