KDTree模板

这一块知识点也不太好理解。。看了几个小时,大概是懂了,用于范围搜索确实可以提高不少效率

这里有个模板题:https://anoxiacxy.github.io/more/bzoj/p/2648.html

上面那个是二维的,这个算法可以搜索多维的

这里有个讲的还可以https://leileiluoluo.com/posts/kdtree-algorithm-and-implementation.html。。主要理解图,就是关于搜索范围这里可能不太好理解,在代码里面可能有点模糊

下面是上面那个模板题的模板代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN = 500050;
const int inf = 1e9;

inline int read(){

    int x = 0,f = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = (x<<3) + (x<<1) + (ch^'0'); ch = getchar();}
    return x * f;
}

struct node{

    int d[2];   //坐标
    int l,r;    //每个点的左(上)区域边界点和右(下)区域边界点
    int Max[2]; //这个点所维护的矩形区域
    int Min[2];
}t[MAXN];

int op,x,y; //操作类型,坐标
int N,M;    //初始化树的结点个数,操作次数
int root,cmpd;   //kd树根,操作维度(0表示1维,1表示2维)
int ans;    //最近距离

inline bool cmp(node a,node b){

    if(a.d[cmpd] != b.d[cmpd])
        return a.d[cmpd] < b.d[cmpd];
    else
        return a.d[!cmpd] < b.d[!cmpd];
}

void kd_push_up(int p){ //更新维护的区域,之所以要这样,是因为维护的是一个正方形区域,或一个圆形区域

    if(t[p].l){
        
        if(t[p].Max[0] < t[t[p].l].Max[0])
            t[p].Max[0] = t[t[p].l].Max[0];
        if(t[p].Min[0] > t[t[p].l].Min[0])
            t[p].Min[0] = t[t[p].l].Min[0];
        if(t[p].Max[1] < t[t[p].l].Max[1])
            t[p].Max[1] = t[t[p].l].Max[1];
        if(t[p].Min[1] > t[t[p].l].Min[1])
            t[p].Min[1] = t[t[p].l].Min[1];
    }
    if(t[p].r){

        if(t[p].Max[0] < t[t[p].r].Max[0])
            t[p].Max[0] = t[t[p].r].Max[0];
        if(t[p].Min[0] > t[t[p].r].Min[0])
            t[p].Min[0] = t[t[p].r].Min[0];
        if(t[p].Max[1] < t[t[p].r].Max[1])
            t[p].Max[1] = t[t[p].r].Max[1];
        if(t[p].Min[1] > t[t[p].r].Min[1])
            t[p].Min[1] = t[t[p].r].Min[1];    
    }
}

/**
 * kd_tree_build(l,r,d):创建kd树
 */
int kd_tree_build(int l,int r,int d){

    int mid = (l+r) / 2;
    cmpd = d;   //因为cmp函数要用cmpd,所以要更新cmpd

    nth_element(t+l,t+mid,t+r+1,cmp);   //快排

    //这里初始化可以在输入的时候完成
    t[mid].Max[0] = t[mid].Min[0] = t[mid].d[0];
    t[mid].Max[1] = t[mid].Min[1] = t[mid].d[1];

    if(l != mid)
        t[mid].l = kd_tree_build(l,mid-1,!d);
    if(r != mid)
        t[mid].r = kd_tree_build(mid+1,r,!d);
    
    kd_push_up(mid);    //这里要记得更新维护区域
    return mid;
}

/**
 * kd_insert(now):插入
 */
void kd_insert(int now){

    int D = 0,p = root; //D表示维度,p是根,因为根是从第一维开始排序

    while(1){

        //更新区域
        if(t[p].Max[0] < t[now].Max[0]) 
            t[p].Max[0] = t[now].Max[0];
        if(t[p].Min[0] > t[now].Min[0])
            t[p].Min[0] = t[now].Min[0];
        if(t[p].Max[1] < t[now].Max[1])
            t[p].Max[1] = t[now].Max[1];
        if(t[p].Min[1] > t[now].Min[1])
            t[p].Min[1] = t[now].Min[1];
        
        if(t[now].d[D] >= t[p].d[D]){

            if(t[p].r == 0){
                
                t[p].r = now;
                return;
            }else
                p = t[p].r;
        }
        else{

            if(t[p].l == 0){
                t[p].l = now;
                return;
            }
            else
                p = t[p].l;
        }
        D = !D;
    }
}

inline int dist(int p,int px,int py){

    int dis = 0;
    if(px < t[p].Min[0])
        dis += t[p].Min[0] - px;
    if(px > t[p].Max[0])
        dis += px - t[p].Max[0];
    if(py < t[p].Min[1])
        dis += t[p].Min[1] - py;
    if(py > t[p].Max[1])
        dis += py - t[p].Max[1];
    
    return dis;
}

void kd_query(int p){

    int dl,dr,d0;
    d0 = abs(t[p].d[0] - x) + abs(t[p].d[1] - y);
    if(d0 < ans)
        ans = d0;
    
    if(t[p].l)
        dl = dist(t[p].l,x,y);
    else
        dl = inf;
    if(t[p].r)
        dr = dist(t[p].r,x,y);
    else 
        dr = inf;

    if(dl < dr){
        
        if(dl < ans)
            kd_query(t[p].l);
        if(dr < ans)
            kd_query(t[p].r);
    }else{

        if(dr < ans)
            kd_query(t[p].r);
        if(dl < ans)
            kd_query(t[p].l);
    }
}

int main(){

    N = read(),M = read();

    for(int i = 1;i<=N;i++){
        t[i].d[0] = read();
        t[i].d[1] = read();
    }

    root = kd_tree_build(1,N,0);

    while(M--){

        op = read(),x = read(),y = read();
        if(op == 1){
            ++N;
            t[N].Max[0] = t[N].Min[0] = t[N].d[0] = x;
            t[N].Max[1] = t[N].Min[1] = t[N].d[1] = y;
            kd_insert(N);
        }else{
            ans = inf;
            kd_query(root);
            printf("%d\n", ans);
        }
    }

    system("pause");
    return 0;
}

当使用模板类函数来实现KD树时,可以按照以下方式编写代码: ```cpp #include <iostream> #include <vector> template <typename T, size_t K> struct KDNode { std::array<T, K> point; KDNode<T, K>* left; KDNode<T, K>* right; KDNode(const std::array<T, K>& p) : point(p), left(nullptr), right(nullptr) {} }; template <typename T, size_t K> class KDTree { private: KDNode<T, K>* root; KDNode<T, K>* buildTree(std::vector<std::array<T, K>>& points, size_t depth) { if (points.empty()) { return nullptr; } size_t axis = depth % K; auto cmp = [axis](const std::array<T, K>& a, const std::array<T, K>& b) { return a[axis] < b[axis]; }; std::sort(points.begin(), points.end(), cmp); size_t median = points.size() / 2; KDNode<T, K>* node = new KDNode<T, K>(points[median]); node->left = buildTree(std::vector<std::array<T, K>>(points.begin(), points.begin() + median), depth + 1); node->right = buildTree(std::vector<std::array<T, K>>(points.begin() + median + 1, points.end()), depth + 1); return node; } public: KDTree() : root(nullptr) {} void build(std::vector<std::array<T, K>>& points) { root = buildTree(points, 0); } }; ``` 使用示例代码: ```cpp int main() { std::vector<std::array<int, 2>> points{{3, 6}, {17, 15}, {13, 15}, {6, 12}, {9, 1}, {2, 7}, {10, 19}}; KDTree<int, 2> tree; tree.build(points); return 0; } ``` 以上代码实现了一个简单的KD树,其中`T`表示点的坐标数据类型,`K`表示点的维度。`KDNode`结构体表示KD树的节点,`KDTree`类则表示整个KD树的数据结构。`buildTree`函数用于递归地构建KD树,`build`函数用于构建整个KD树。 注意:以上代码只是一个简单的示例,实际应用中可能需要根据具体情况进行更多的优化和功能扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值