四叉树是一种空间索引(划分)技术,通过递归地将一整块区域平均划分为4个子区域,每个区域托管肯定数量的二维点,于是任意一个二维点都能够依据它的坐标疾速找到所述的子区域,失去它的邻点。

具体介绍略过,只有会实现二叉树的二分查找,就不难领会四叉树划分的思维。因为查找邻点的速度十分快,所以罕用于游戏中的碰撞检测,GIS的地理位置索引等。

上面是我实现的一种传统四叉树。

节点定义:

#include <vector>#include <cstdlib>#include <ctime>#include <iostream>//二维数据点定义typedef struct Point_2D {    int x;    int y;}Point;//节点定义typedef struct Quad_Tree_Node {    Point start_xy;               //左上角起始点    int width;                    //宽度    int height;                   //高度    int capacity;                 //节点容量    bool is_leaf;                 //叶节点标记    std::vector<Point> points;    //存储的二维点    Quad_Tree_Node* lu_child;     //子节点,左上    Quad_Tree_Node* ru_child;     //子节点,右上    Quad_Tree_Node* lb_child;     //子节点,左下    Quad_Tree_Node* rb_child;     //子节点,右下    int depth;                    //以后节点深度}Quad_Node;

二维数据点构造只有横纵坐标,也能够携带别的信息。四叉树节点中须要保留所属子区域的信息,我应用左上角起始点+宽度+高度来定义一块区域。设置了节点容量来限度一块区域能保留的数据量,如果超过容量,节点就要持续决裂。

创立节点办法:

Quad_Node* Quad_Tree::create_node_(const Point& start, const int& w, const int& h) {    Quad_Node* new_node = new Quad_Node;    new_node->start_xy = start;    new_node->width = w;    new_node->height = h;    new_node->capacity = _set_capacity;    new_node->is_leaf = true;    new_node->lu_child = nullptr;    new_node->lb_child = nullptr;    new_node->ru_child = nullptr;    new_node->rb_child = nullptr;    return new_node;}

创立节点办法须要左上起始点、宽度和高度三个入口参数。

决裂节点办法:

int Quad_Tree::split_node_(Quad_Node* node) {    node->is_leaf = false;    int sub_w = node->width / 2;    int sub_h = node->height / 2;    //确定每个子节点的起始地位    Point lu_start = node->start_xy;    Point lb_start;    lb_start.x = node->start_xy.x;    lb_start.y = node->start_xy.y + sub_h;    Point ru_start;    ru_start.x = node->start_xy.x + sub_w;    ru_start.y = node->start_xy.y;    Point rb_start;    rb_start.x = node->start_xy.x + sub_w;    rb_start.y = node->start_xy.y + sub_h;    //创立子节点    node->lu_child = create_node_(lu_start, sub_w, sub_h);    node->lb_child = create_node_(lb_start, sub_w, sub_h);    node->ru_child = create_node_(ru_start, sub_w, sub_h);    node->rb_child = create_node_(rb_start, sub_w, sub_h);    //将以后节点保留的数据点插入到下一层    for (int i = 0, count = node->points.size(); i < count; ++i) {        insert_node_(node, node->points[i]);    }    node->points.clear();    return 0;}

决裂节点留神须要把以后节点保留的所有数据点插入到下一层,即从新划分到子节点的区域中。因而,不难看出只有叶节点上会保留数据点。

插入节点办法:

int Quad_Tree::insert_node_(Quad_Node* node, const Point& point){    if (node->is_leaf) {    //数据点都插入到叶节点        int count = node->points.size();        if (count + 1 > node->capacity) {    //超过容量就要决裂节点,再插入            split_node_(node);            insert_node_(node, point);        }        else {            node->points.push_back(point);    //没超过就失常插入        }        return 0;    }    //以后节点不是叶节点,找到它所属的子区域    Region in_region = find_region(node, point);    if (in_region == Region::LEFT_UP) {        insert_node_(node->lu_child, point);    //插入到左上子节点    }    else if (in_region == Region::LEFT_BOTTOM) {        insert_node_(node->lb_child, point);    //插入到左下子节点    }    else if (in_region == Region::RIGHT_UP) {        insert_node_(node->ru_child, point);    //插入到右上子节点    }    else if (in_region == Region::RIGHT_BOTTOM) {        insert_node_(node->rb_child, point);    //插入到右下子节点    }    else {        return -1;    //不属于任何子区域    }    return 0;}

插入节点时判断以后节点是不是叶节点,不是叶节点的话须要找到确定所属子区域,插入到对应的子节点中。确定子区域的过程我独自实现为一个函数:

Region Quad_Tree::find_region(Quad_Node* node, const Point& point){    Region found = Region::OUT_OF_RANGE;    int x = point.x;    int y = point.y;    int sub_w = node->width / 2;    int sub_h = node->height / 2;    if (x > node->start_xy.x && x < node->start_xy.x + sub_w) {        if (y > node->start_xy.y && y < node->start_xy.y + sub_h) {            found = Region::LEFT_UP;        }        else if (y > node->start_xy.y + sub_h && y < node->start_xy.y + node->height) {            found = Region::LEFT_BOTTOM;        }        else {        }    }    else if (x > node->start_xy.x + sub_w && x < node->start_xy.x + node->width) {        if (y > node->start_xy.y && y < node->start_xy.y + sub_h) {            found = Region::RIGHT_UP;        }        else if (y > node->start_xy.y + sub_h && y < node->start_xy.y + node->height) {            found = Region::RIGHT_BOTTOM;        }        else {        }    }    else {        found = Region::OUT_OF_RANGE;    }    return found;}

依据下面的实现,我对正好位于边界线的数据点是没有解决的。能够把它们保留到父节点上。

区域的定义用了枚举类:

enum class Region {    LEFT_UP = 0,    LEFT_BOTTOM,    RIGHT_UP,    RIGHT_BOTTOM,    OUT_OF_RANGE};

实现一个遍历删除办法:

void Quad_Tree::traverse(Quad_Node* node) {    if (node->is_leaf) {        //拜访操作        node->points.clear();        delete node;        return;    }    traverse(node->lu_child);    traverse(node->lb_child);    traverse(node->ru_child);    traverse(node->rb_child);    node->points.clear();    delete node;}//遍历输入void Quad_Tree::show() {    traverse(root_);}

决裂到指定深度的办法:

int max_depth = 8;void Quad_Tree::init_split(){    split_to_depth(root_, 0);}int Quad_Tree::split_to_depth(Quad_Node* node, const int& depth){    if (depth == max_depth) {        node->is_leaf = true;        return 1;    }    else {        node->is_leaf = false;        node->depth = depth;        int sub_w = node->width / 2;        int sub_h = node->height / 2;        Point lu_start = node->start_xy;        Point lb_start;        lb_start.x = node->start_xy.x;        lb_start.y = node->start_xy.y + sub_h;        Point ru_start;        ru_start.x = node->start_xy.x + sub_w;        ru_start.y = node->start_xy.y;        Point rb_start;        rb_start.x = node->start_xy.x + sub_w;        rb_start.y = node->start_xy.y + sub_h;        node->lu_child = create_node_(lu_start, sub_w, sub_h);        node->lb_child = create_node_(lb_start, sub_w, sub_h);        node->ru_child = create_node_(ru_start, sub_w, sub_h);        node->rb_child = create_node_(rb_start, sub_w, sub_h);    }    split_to_depth(node->lu_child, depth + 1);    split_to_depth(node->lb_child, depth + 1);    split_to_depth(node->ru_child, depth + 1);    split_to_depth(node->rb_child, depth + 1);}

Quad_Tree类定义:

class Quad_Tree {private:    Quad_Node* root_;    //根节点    Quad_Node* create_node_(const Point& start, const int& w, const int& h);        int split_node_(Quad_Node* node);    int insert_node_(Quad_Node* node, const Point& point);    Region find_region(Quad_Node* node, const Point& point);    void traverse(Quad_Node* node);    int split_to_depth(Quad_Node* node, const int& depth);    public:    Quad_Tree(const int& start_x, const int& start_y, const int& witdh, const int& height);    ~Quad_Tree();    void insert_point_(const Point& point);    void show();    void init_split();};

main函数,测试一下划分10000个数据点的工夫。

#include "quadTree.h"void test_time() {    //设置工夫种子    srand(time(0));    clock_t startTime, endTime;    double in_sec = 0, tra_sec = 0;    Quad_Tree quad_tree(0, 0, 1920, 1536);    //左上起始地位(0,0),宽1920,高1536    const int rand_size = 10000;    Point* rand_points = new Point[rand_size];    //数据点插入    startTime = clock();    for (int i = 0; i < rand_size; ++i) {    //划分随机数据点        rand_points[i].x = rand() % 1920;        rand_points[i].y = rand() % 1536;        quad_tree.insert_point_(rand_points[i]);    }    endTime = clock();    in_sec = (double)(endTime - startTime);    //数据点遍历    startTime = clock();    quad_tree.show();    endTime = clock();    tra_sec = (double)(endTime - startTime);    std::cout << "\n";    std::cout << "insert " << rand_size << " points takes " << in_sec << "ms.\n";    std::cout << "traverse " << rand_size << " points takes " << tra_sec << "ms.\n";}int main() {    //Quad_Tree quad_tree(0, 0, 1920, 1536);    //quad_tree.init_split();    test_time();}

最初失去1万个数据点建树约9~12ms,遍历约2~3ms。5000个数据点建树约5~6ms,遍历1~2ms,可见查找效率还是十分高的,数据量只有几千时能十分快地找到邻点。