KD树


kd树:

计算时采用线性扫描的方式O(n^2),效率奇低。采用平衡二叉树的方法存储各个点,用中位数做分割,划分左右区间,并进行以x-y轴为中心进行交替选择。

(2,4.5).png
(2.1,3.1).png

算法复杂度:

构建:O(log2n)
插入:O(log n)
删除:O(log n)
查询:平均情况下 O(log n) ,最好情况 O(1),最坏情况O(n)


  1. 构建kd树:

(1)按y排序,抽取其中的中位数(向上取整)对应的点,axis代表的维自增。每个node保留一个指针指向父节点。

  • 怎么确定split域的首先该取的值(先划分x轴还是y轴)?

分别计算x,y方向上数据的方差得知x方向上的方差最大

  1. 搜寻确定查询点最小范围的点:

(1)先以y分割判断点A>Sy,向左子树查。
(2)再以x分割判断B<Sy,向右子树搜索。

  1. while查找二维空间里的最近点。

如果点非空而且栈非空(在根节点退到root->f即空节点而且栈为空(叶节点情况下一般栈非空))退出。
(1)如果 minr < r(当前点,搜索点),则无需查找另外一侧的子节点。r=r->f
(2)如果minr > r(当前点,搜索点),则去另一侧的子节点找找看。r=r->l/r(看你搜索点在线的哪一侧(根据x/y相对大小),取反方向即可),同时,stack记录r。
(3)当r==NULL,触底回退到stack为空(保留stack[0]),然后r=r->f(会不会重新陷回r->l/r ?不会左边的minr可能值都遍历了,所以会使r指向另一侧,等栈pop回到原点时又毫不留情r=r->f)

#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
int splits=0;
// 维数
int now_axis=0;
// 当前所在维数
class kd_tree{
    public:
    vector<float>point;
    // (x,y,z...)
    float range;
    // x<range and x>range
    int split;
    // x,y,z....轴标记
    kd_tree*l;
    kd_tree*r;
    kd_tree*f;
    kd_tree(){
        l=NULL;
        r=NULL;
        f=NULL;
        range=0;
        split=0;
        # split用于判断用哪一轴作为切割依据
    }
};
kd_tree* insert(kd_tree*root,vector<float>v,int split){
    if(root==NULL){
        root=new kd_tree();
        root->point.assign(v.begin(),v.end());
        root->split=split;
        root->range=v[split];
        root->point.assign(v.begin(),v.end());
    }
    else{
        if(v[root->split]>root->range){
            root->r=insert(root->r,v,split);
            root->r->f=root;
        }
        else if(v[root->split]<root->range){
            root->l=insert(root->l,v,split);
            root->l->f=root;
        }
    }
    return root;
}
// 排序用
bool cmp(vector<float>a,vector<float>b){
    return a[now_axis]<b[now_axis];
}
// 初始化必须集齐所有数据
void init(kd_tree*&root,vector<vector<float>>v,int left,int right,int split){
    if(left>right){
        return;
    }
    now_axis=split%splits;
    
    sort(v.begin()+left,v.begin()+right+1,cmp);

    int middle=(left+right+1)/2;
    // +1是向上取整,不加是向下取整
    root=insert(root,v[middle],now_axis);
    init(root,v,left,middle-1,split+1);
    init(root,v,middle+1,right,split+1);
    return;
}
int choose_split(vector<vector<float>>v){
    int i,j;
    vector<float>ave(splits);
    for(i=0;i<splits;i++){
        float sum=0;
        for(j=0;j<v.size();j++){
            sum+=v[j][i];
        }
        ave[i]=sum/float(v.size());
    }
    int ans=0;
    float maxd=0;
    for(i=0;i<splits;i++){
        float sumd=0;
        for(j=0;j<v.size();j++){
            sumd+=(v[j][i]-ave[i])*(v[j][i]-ave[i]);
        }
        if(sumd>maxd){
            ans=i;
            maxd=sumd;
        }
    }
    return ans;
}
kd_tree* find_range(kd_tree*root,vector<float>&v){
    if(root->point[root->split]>v[root->split]){
        if(root->l==NULL){
            return root;
        }
        else{
            find_range(root->l,v);
        }
    }
    else if(root->point[root->split]<v[root->split]){
        if(root->r==NULL){
            return root;
        }
        else{
            find_range(root->r,v);
        }
    }
    else{
        return root;
    }
}
void preorder(kd_tree*root){
    if(root==NULL){
        return;
    }
    cout<<root->point[0]<<","<<root->point[1]<<endl;
    cout<<(root->split==0?"x":"y")<<":"<<root->range<<endl;
    preorder(root->l);
    preorder(root->r);
}
// 最小半径的平方
float minr=0x7fffffff;
kd_tree* find_nearest_node(kd_tree*root,vector<float>&v){
    if(root==NULL){
        return NULL;
    }
    kd_tree*ans=root;
    vector<kd_tree*>list;
    while(!(root==NULL)||!list.empty()){
        // if(root){
        //     打印路径
        //     cout<<root->point[0]<<" "<<root->point[1]<<endl;
        // }
        if(root==NULL){                
            root=list[0];
            while(!list.empty()){
                list.pop_back();
            }
            root=root->f;
        }
        else{
            int i=0;
            float r_now=0;
            // calc the distance^2

            for(i=0;i<splits;i++){
                r_now+=(root->point[i]-v[i])*(root->point[i]-v[i]);
            }
            if(r_now<minr){
                // current point is much more near
                minr=r_now;
                ans=root;
            }
            if((root->point[root->split]-v[root->split])*(root->point[root->split]-v[root->split])>=minr){
                // 回溯确保最优
                // if the cirle which based on the radius between current point and the point to be searched
                // doesn't intersect with the straight line(x=...,y=...), then search the father side; 
                root=root->f;
                if(!list.empty()){
                    list.pop_back();
                }
            }
            else{
                list.push_back(root);
                // or turn to the other side of 
                if(v[root->split]<root->point[root->split]){
                    root=root->r;
                }
                else{
                    root=root->l;
                }
            }
        }
    }
    return ans;
}
int main()
{
    freopen("inputs.txt","r+",stdin);
    int length;
    int i,j,x;
    vector<vector<float>>v;
    kd_tree* root=NULL;
    cin>>length;
    cin>>splits;
    for(i=0;i<length;i++){
        vector<float>vv;
        for(j=0;j<splits;j++){
            cin>>x;
            vv.push_back(x);
        }
        v.push_back(vv);
    }
    int left=0,right=v.size()-1,split;

    split=choose_split(v);
    // choose the largest D(..) to define x/y/z as the start axis.

    init(root,v,left,right,split);
    // init the b-tree.

    if(root==NULL){
        cout<<"failed"<<endl;
    }
    vector<float>vvv={2,4.5};
    // point to be search.

    kd_tree*r=find_range(root,vvv);
    // return the range define node.
    
    r=find_nearest_node(r,vvv);
    // return the answer node

    cout<<(split==0?"x":"y")<<" as the firat axis."<<endl;
    cout<<"\nr^2: "<<minr<<endl;
    cout<<"\nanswer node: ("<<r->point[0]<<","<<r->point[1]<<")"<<endl;

    return 0;
}

/*

6
2
2 3
4 7
5 4
7 2
8 1
9 6

(5,4)
*/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353