使用 C++ 进行 2D 段/四叉树解释 [关闭]

Posted

技术标签:

【中文标题】使用 C++ 进行 2D 段/四叉树解释 [关闭]【英文标题】:2D Segment/Quad Tree Explanation with C++ [closed] 【发布时间】:2014-09-27 02:46:26 【问题描述】:

附:这可能不是重复的。我搜索了 SO 并确保我没有得到我想要的东西。

我是 ACM 问题解决者,最近我学习了线性数组的分段树和延迟传播的分段树。但是我遇到了一些需要二维线段树(在某处被称为四叉树)的问题。但我找不到任何好的教程。我搜索了 SO,找到了一个链接 http://e-maxx.ru/algo/segment_tree,这是一个俄语教程。

我需要对 2D 段树的源代码(最好是 C++)进行一些很好的解释。需要注意的是,我非常了解典型的段树。

【问题讨论】:

你能告诉我们一些你迄今为止尝试过的例子吗? 另外,谷歌快速搜索“quadtree”给了我这个:gamedevelopment.tutsplus.com/tutorials/… 四叉树是不同的。查找“范围树”。 2D 范围树基本上只是一棵(在 x 上)段树(在 y 上)。 在这里查看实现github.com/hissain/advance-data-structures-algorithms/blob/… 【参考方案1】:

嗯,正如你所说,我希望你足够了解分段树(又名统计树)。我在多维线段树背后给出了一些直觉。


假设你有一个二维的 N * N(对于一个相当大的 N,大到不能被蛮力处理)整数值网格并且你被要求执行操作 - 找到最小/最大值或计算网格特定部分的所有项目的总和,更新任何网格索引值等。看起来,这个问题与典型的线段树问题没有什么不同,不像数据的维度容器。这里可以选择的是构建一个 2D Segment Tree。

2D 段树的概念不过是Quad Tree - 一种树数据结构,其中每个外部节点正好有四个子节点。四叉树最常用于通过递归地将二维空间细分为四个象限或区域来划分二维空间。这些区域可以是正方形或矩形,或者可以具有任意形状。该数据结构在 1974 年被 Raphael Frinkel 和 J. L. Bentley 命名为四叉树。类似的分区也称为 Q-tree

树的根包含通过[ (0, 0), (N - 1, N - 1) ] 的完整段。对于每个段[ (a1, b1), (a2, b2) ],我们将它们拆分为[ (a1, b1), ( (a1 + a2) / 2, (b1 + b2) / 2 ) ) ][ ( (a1 + a2) / 2 + 1, b1 ), ( a2, (b1 + b2) / 2 ) ][ ( a1, (b1 + b2) / 2 + 1 ), ( (a1 + a2) / 2, b2 ) ][ ( (a1 + a2) / 2 + 1, (b1 + b2) / 2 + 1 ), ( a2, b2 ) ],直到a1 = b1a2 = b2。构建分段树的成本是O(nlogn),并且可以在O(logn) 中完成准备回答RMQ(范围最大/最小查询)的分段树。

假设,您有一个 N = 4 的网格。那么对应的树就是-

如您所见,4 * 4 数组 [ (0, 0), (3, 3) ] 被分割成 4 个子数组 - [ (0, 0), (1, 1) ][ (2, 0), (3, 1) ][ (2, 0), (1, 3) ][ (2, 2), (3, 3) ]。此外,每四个块被分割成四个更小的单元;例如,段 [ (2, 2), (3, 3) ] 将是 [ (2, 2), (2, 2) ][ (3, 2), (3, 2) ][ (2, 3), (2, 3) ][ (3, 3), (3, 3) ]。这些段是最小的单位,所以不再进一步划分。

实施

编码部分与分割部分不同,与分割树非常相似。 这里给出的代码对编程竞赛很友好(没有指针、内存分配/释放的东西和 OOP 结构),我在竞赛中多次使用这个 sn-p。

#include <bits/stdc++.h>
 
using namespace std;
 
#define Max 501
#define INF (1 << 30)
int P[Max][Max]; // container for 2D grid
 
/* 2D Segment Tree node */
struct Point 
    int x, y, mx;
    Point() 
    Point(int x, int y, int mx) : x(x), y(y), mx(mx) 
 
    bool operator < (const Point& other) const 
        return mx < other.mx;
    
;
 
struct Segtree2d 

    // I didn't calculate the exact size needed in terms of 2D container size.
    // If anyone, please edit the answer.
    // It's just a safe size to store nodes for MAX * MAX 2D grids which won't cause stack overflow :)
    Point T[500000]; // TODO: calculate the accurate space needed
    
    int n, m;
 
    // initialize and construct segment tree
    void init(int n, int m) 
        this -> n = n;
        this -> m = m;
        build(1, 1, 1, n, m);
    
 
    // build a 2D segment tree from data [ (a1, b1), (a2, b2) ]
    // Time: O(n logn)
    Point build(int node, int a1, int b1, int a2, int b2) 
        // out of range
        if (a1 > a2 or b1 > b2)
            return def();
 
        // if it is only a single index, assign value to node
        if (a1 == a2 and b1 == b2)
            return T[node] = Point(a1, b1, P[a1][b1]);
 
        // split the tree into four segments
        T[node] = def();
        T[node] = maxNode(T[node], build(4 * node - 2, a1, b1, (a1 + a2) / 2, (b1 + b2) / 2 ) );
        T[node] = maxNode(T[node], build(4 * node - 1, (a1 + a2) / 2 + 1, b1, a2, (b1 + b2) / 2 ));
        T[node] = maxNode(T[node], build(4 * node + 0, a1, (b1 + b2) / 2 + 1, (a1 + a2) / 2, b2) );
        T[node] = maxNode(T[node], build(4 * node + 1, (a1 + a2) / 2 + 1, (b1 + b2) / 2 + 1, a2, b2) );
        return T[node];
    
 
    // helper function for query(int, int, int, int);
    Point query(int node, int a1, int b1, int a2, int b2, int x1, int y1, int x2, int y2) 
        // if we out of range, return dummy
        if (x1 > a2 or y1 > b2 or x2 < a1 or y2 < b1 or a1 > a2 or b1 > b2)
            return def();
 
        // if it is within range, return the node
        if (x1 <= a1 and y1 <= b1 and a2 <= x2 and b2 <= y2)
            return T[node];
 
        // split into four segments
        Point mx = def();
        mx = maxNode(mx, query(4 * node - 2, a1, b1, (a1 + a2) / 2, (b1 + b2) / 2, x1, y1, x2, y2) );
        mx = maxNode(mx, query(4 * node - 1, (a1 + a2) / 2 + 1, b1, a2, (b1 + b2) / 2, x1, y1, x2, y2) );
        mx = maxNode(mx, query(4 * node + 0, a1, (b1 + b2) / 2 + 1, (a1 + a2) / 2, b2, x1, y1, x2, y2) );
        mx = maxNode(mx, query(4 * node + 1, (a1 + a2) / 2 + 1, (b1 + b2) / 2 + 1, a2, b2, x1, y1, x2, y2));
 
        // return the maximum value
        return mx;
    
 
    // query from range [ (x1, y1), (x2, y2) ]
    // Time: O(logn)
    Point query(int x1, int y1, int x2, int y2) 
        return query(1, 1, 1, n, m, x1, y1, x2, y2);
    
 
    // helper function for update(int, int, int);
    Point update(int node, int a1, int b1, int a2, int b2, int x, int y, int value) 
        if (a1 > a2 or b1 > b2)
            return def();
 
        if (x > a2 or y > b2 or x < a1 or y < b1)
            return T[node];
 
        if (x == a1 and y == b1 and x == a2 and y == b2)
            return T[node] = Point(x, y, value);
 
        Point mx = def();
        mx = maxNode(mx, update(4 * node - 2, a1, b1, (a1 + a2) / 2, (b1 + b2) / 2, x, y, value) );
        mx = maxNode(mx, update(4 * node - 1, (a1 + a2) / 2 + 1, b1, a2, (b1 + b2) / 2, x, y, value));
        mx = maxNode(mx, update(4 * node + 0, a1, (b1 + b2) / 2 + 1, (a1 + a2) / 2, b2, x, y, value));
        mx = maxNode(mx, update(4 * node + 1, (a1 + a2) / 2 + 1, (b1 + b2) / 2 + 1, a2, b2, x, y, value) );
        return T[node] = mx;
    
 
    // update the value of (x, y) index to 'value'
    // Time: O(logn)
    Point update(int x, int y, int value) 
        return update(1, 1, 1, n, m, x, y, value);
    
 
    // utility functions; these functions are virtual because they will be overridden in child class
    virtual Point maxNode(Point a, Point b) 
        return max(a, b);
    
 
    // dummy node
    virtual Point def() 
        return Point(0, 0, -INF);
    
;
 
/* 2D Segment Tree for range minimum query; a override of Segtree2d class */
struct Segtree2dMin : Segtree2d 
    // overload maxNode() function to return minimum value
    Point maxNode(Point a, Point b) 
        return min(a, b);
    
 
    Point def() 
        return Point(0, 0, INF);
    
;
 
// initialize class objects
Segtree2d Tmax;
Segtree2dMin Tmin;
 
 
// Driver program.
int main(void) 
    int n, m;
    // input
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &P[i][j]);
 
    // initialize
    Tmax.init(n, m);
    Tmin.init(n, m);
 
    // query
    int x1, y1, x2, y2;
    scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
 
    Tmax.query(x1, y1, x2, y2).mx;
    Tmin.query(x1, y1, x2, y2).mx;
 
    // update
    int x, y, v;
    scanf("%d %d %d", &x, &y, &v);
    Tmax.update(x, y, v);
    Tmin.update(x, y, v);
 
    return 0;

3D 分割

给您一个 3D 网格并要求您执行类似于 2D 线段树的类似操作并非不可能。在这种情况下,我们可以像构建 2D 网格一样构建 3D Segment 树。

我们将网格分成 8 个较小的段,并递归地进行细分,直到出现最小的单元。下图展示了这种分割思路。

对于一维段树,我们将数组分成 2 (2^1) 段,这为特定操作产生了log2(n) 复杂度。同样对于 2D 段树,我们将每一步中的 2D 网格分成 4 (2^2) 个段,以确保每个操作的成本为log2(n)。所以以类似的方式,我们通过将网格细分为 8 (2^3) 段来扩展这个 3D 树。

P.S.:3D段树是我自己的想象;我不知道有没有这样的事情。可能有更好的方法来处理 2D 和 3D 段树,但我认为这些代码足以用于编程竞赛,因为我已经使用了很多次。


编辑

3D 分割的想法不过是Octree - 一个树形数据结构,其中每个内部节点正好有八个子节点。八叉树最常用于通过递归地将其细分为八个八分圆来划分三维空间。八叉树是四叉树的三维模拟。

八叉树通常用于 3D 图形和 3D 游戏引擎。它还有很多其他应用,例如空间索引、最近邻搜索、三维高效碰撞检测和so many。

希望对你有帮助!

【讨论】:

这是不对的。 3D分割有很多共同的用途。在许多图形应用程序中,八叉树用于表示实体。给定平面中的一组圆和一个可区分的圆 (P1,r1),找到集合 (P2,r2) 中的圆,最小化 dist(P1,P2)-r1-r2。 @Gene 谢谢。我不知道Octree。感谢您提供此信息,我正在编辑我的答案 能否在二维线段树中实现查询函数的非递归实现?这个递归函数在大型二维数组上运行非常缓慢。 这个查询函数的预期时间复杂度是多少? @lavee_singh 查询和更新都是对数的。你的问题解决了吗?

以上是关于使用 C++ 进行 2D 段/四叉树解释 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

2D空间中使用Quadtree四叉树进行碰撞检测优化

用于二维碰撞检测的四叉树

2d 游戏引擎裁剪算法

Mark四叉树与八叉树

四叉树lod结合灯塔AOI

四叉树lod结合灯塔AOI