• kd-树笔记


    以下内容均为本人近几天学习笔记,个人理解,并非完美答案,请抱着怀疑眼光阅读,如有错误请告知,感谢!

    1.kd-树简介

    1.1 特征:在任何情况下,kd-树都是一棵递归定义的平衡二叉搜索树

    1.2 用途:常用于范围查询,高效解决多维范围查询。例如:快速在校友数据库中找到1970-2000年毕业并且身高在170-190cm且性别为男的校友。

    2.kd-树的实现

    2.1 一维kd-树:一维kd-树本质上就是平衡二叉搜索树,也可以看成线段树,一维的范围查找问题完全可以用线段树解决。这样便于推广到二维乃至k维。

    2.2 kd-树的建树:

    2.2.1 构造算法:

    • kd-树的建树应该将每个维度分为两个部分,并以中位点作为中点进行划分。
    • 增加一个属性深度(deep),那么kd-树维度维k,当深度为deep时,该对deep%k维进行划分。
    • k维kd-树本质上仍是平衡二叉搜索树,只是在每一层对不同维度进行划分,使左右节点数量相等,从而维持树高。

    2.2.2 伪代码:(指针更方便,但更容易出错)

    void BuildTree( int l , int r , int root , int deep){ //l是该维度的数据的左边界,r是右边界

            if(l > r)        return;//不存在数据

            isExist[root] = 1;//标记root存在数据

            isExist[ls] = isExist[rs] = -1;//(左右儿子初始化为不存在)

            int idx = deep%k;//找出划分哪个维度

            找中位点mid,同时使mid左面所有节点小于mid,右边所有节点大于mid;

            BuildTree(l , mid-1 , ls , deep+1);

            BuildTree(mid+1 , r , rs , deep+1);

    }

    2.3 kd-树的查询:

    2.3.1 当前节点范围查询的三种情况:

    • A:该范围完全包含于该节点的左子树或右子树
    • B:该范围一部分在左子树,一部分在右子树
    • C:该范围既不在左子树也不在右子树

    2.4 代码实例:HDU4347

    #include <iostream>
    #include <string.h>
    #include <algorithm>
    #include <stdio.h>
    #include <math.h>
    #include <queue>
     
    using namespace std;
     
    #define N 50005
     
    #define lson rt << 1
    #define rson rt << 1 | 1
    #define Pair pair<double, Node>
    #define Sqrt2(x) (x) * (x)
     
    int n, k, idx;
     
    struct Node
    {
        int feature[5];     //定义属性数组
        bool operator < (const Node &u) const
        {
            return feature[idx] < u.feature[idx];
        }
    }_data[N];   //_data[]数组代表输入的数据
     
    priority_queue<Pair> Q;     //队列Q用于存放离p最近的m个数据
     
    class KDTree{
     
        public:
            void Build(int, int, int, int);     //建树
            void Query(Node, int, int, int);    //查询
     
        private:
            Node data[4 * N];    //data[]数组代表K-D树的所有节点数据
            int flag[4 * N];      //用于标记某个节点是否存在,1表示存在,-1表示不存在
    }kd;
     
    //建树步骤,参数dept代表树的深度
    void KDTree::Build(int l, int r, int rt, int dept)
    {
        if(l > r) return;
        flag[rt] = 1;                   //表示编号为rt的节点存在
        flag[lson] = flag[rson] = -1;   //当前节点的孩子暂时标记不存在
        idx = dept % k;                 //按照编号为idx的属性进行划分
        int mid = (l + r) >> 1;
        nth_element(_data + l, _data + mid, _data + r + 1);   //nth_element()为STL中的函数
        data[rt] = _data[mid];
        Build(l, mid - 1, lson, dept + 1);  //递归左子树
        Build(mid + 1, r, rson, dept + 1);  //递归右子树
    }
     
    //查询函数,寻找离p最近的m个特征属性
    void KDTree::Query(Node p, int m, int rt, int dept)
    {
        if(flag[rt] == -1) return;   //不存在的节点不遍历
        Pair cur(0, data[rt]);       //获取当前节点的数据和到p的距离
        for(int i = 0; i < k; i++)
            cur.first += Sqrt2(cur.second.feature[i] - p.feature[i]);
        int dim = dept % k;          //跟建树一样,这样能保证相同节点的dim值不变
        bool fg = 0;                 //用于标记是否需要遍历右子树
        int x = lson;
        int y = rson;
        if(p.feature[dim] >= data[rt].feature[dim]) //数据p的第dim个特征值大于等于当前的数据,则需要进入右子树
            swap(x, y);
        if(~flag[x]) Query(p, m, x, dept + 1);      //如果节点x存在,则进入子树继续遍历
     
        //以下是回溯过程,维护一个优先队列
        if(Q.size() < m)   //如果队列没有满,则继续放入
        {
            Q.push(cur);
            fg = 1;
        }
        else
        {
            if(cur.first < Q.top().first)  //如果找到更小的距离,则用于替换队列Q中最大的距离的数据
            {
                Q.pop();
                Q.push(cur);
            }
            if(Sqrt2(p.feature[dim] - data[rt].feature[dim]) < Q.top().first)
            {
                fg = 1;
            }
        }
        if(~flag[y] && fg) 
            Query(p, m, y, dept + 1);
    }
     
    //输出结果
    void Print(Node data)
    {
        for(int i = 0; i < k; i++)
            printf("%d%c", data.feature[i], i == k - 1 ? '
    ' : ' ');
    }
     
    int main()
    {
        while(scanf("%d%d", &n, &k)!=EOF)
        {
            for(int i = 0; i < n; i++)
                for(int j = 0; j < k; j++)
                    scanf("%d", &_data[i].feature[j]);
            kd.Build(0, n - 1, 1, 0);
            int t, m;
            scanf("%d", &t);
            while(t--)
            {
                Node p;
                for(int i = 0; i < k; i++)
                    scanf("%d", &p.feature[i]);
                scanf("%d", &m);
                while(!Q.empty()) Q.pop();   //事先需要清空优先队列
                kd.Query(p, m, 1, 0);
                printf("the closest %d points are:
    ", m);
                Node tmp[25];
                for(int i = 0; !Q.empty(); i++)
                {
                    tmp[i] = Q.top().second;
                    Q.pop();
                }
                for(int i = m - 1; i >= 0; i--)
                    Print(tmp[i]);
            }
        }
        return 0;
    }
    

    《数据结构(C++语言版)》——邓俊辉    P242

  • 相关阅读:
    PDF,仅支持英译中,可以下载后的pdf或者word版
    pip指定源安装【自用】
    【jQuery01】jQuery选择器
    【jQuery00】什么是jQuery,为什么要学jQuery,配置jQuery环境,解决冲突,大致使用流程
    什么是召回率??
    编程学习路线
    堆排序
    二叉插入排序
    每天算法一丁点(4)--递归算法应用:分书问题
    每天算法一丁点(3)--递归算法应用:半数集
  • 原文地址:https://www.cnblogs.com/long98/p/10352174.html
Copyright © 2020-2023  润新知