首页 > 其他分享 >K-D tree 学习笔记

K-D tree 学习笔记

时间:2024-05-23 21:40:59浏览次数:9  
标签:cnt int 复杂度 tree 笔记 学习 立方体 id define

\(\text{K-D tree}\) 学习笔记

\(\text{K-D tree}\) 是一种针对 \(k\) 维问题求解的算法,并且拥有出色的时空复杂度。

思想

\(\text{K-D tree}\) 本质上是一棵 \(k\) 维的二叉平衡树,这保证了其树高稳定在 \(\log n\) 附近,为求解提供了较为优异的建树模式。

\(\text{K-D tree}\) 首先将所有的 \(k\) 维点建立到一棵树上,这样在求解的时候就可以通过遍历所有结点统计答案,但这样显然不优,因此考虑分治策略。因为点与点之间互不影响,所以我们将子树的求解转化成当前结点、左子树和右子树的求解即可。

但在此时依旧没有本质优化。考虑到问题在于我们忽略了建树提供的子树信息而只在意当前结点维护的信息,我们不妨通过维护子树的一些信息来进行剪枝优化。

不难看出,对于一些点组成的集合最好的概括方式是这些点组成的极小的 \(k\) 维立方体,也就是包含所有点的最小 \(k\) 维立方体。虽然仅仅凭借这个 \(k\) 维立方体我们无法精确的找到子树中所有点的位置,但可以依靠这个 \(k\) 维立方体进行估价。

以 \(k\) 维立方体范围内的点权求和为例,不妨考虑当前子树所对应的极小 \(k\) 维立方体和给定的范围的关系:

  1. 当前子树的立方体被给定范围完全包含。那么此时整个子树均符合条件,返回子树和即可。
  2. 当前子树的立方体和给定范围没有相交部分。那么此时整个子树均不符合条件,返回 \(0\) 即可。
  3. 当前子树的立方体和给定范围部分有交。那么不能直接得出结果,将答案分成当前结点、左子树、右子树分别求解即可。

不难看出这样子的剪枝显然正确,接着考虑时间复杂度。显然时间复杂度只与第 \(3\) 类的子树的个数有关。更详细的,有多少个第 \(3\) 类子树,单次求解的时间复杂度就是多少。而第 \(3\) 类又分为立方体完全包含指定范围和部分相交,前者显然最多有 \(O(h)=O(\log n)\) 个点,现在考虑后者。如果让给定范围的立方体的每一条边偏移一定距离,使其不穿过任何一个点,那么此时第 \(3\) 类子树的数量就是给定立方体的边穿过的立方体数,对于一个 \(3\) 类立方体来说,其有 \(2^k\) 个孙子,这些孙子都经过了 \(k\) 维重划分,显然一条边最多经过其中的一半,也就是 \(2^{k-1}\) 个,那么就有:

\[T(n)=2^{k-1}T(\frac{n}{2^k})+O(1) \]

根据主定理得到 \(T(n)=O(n^{1-\frac{1}{k}})\)。

在信息学竞赛中,遇到的问题 \(k\) 常为 \(2\),因此复杂度通常记为 \(O(\sqrt{n})\)​​。

代码(P4148 简单题)

int Query(int id){
    if(id==0)return 0;
    for(int i=0;i<K;i++){
        if(mx(id,i)<l.pos[i]||mn(id,i)>r.pos[i])return 0;//如果整个立方体都不在给定范围内
    }
    bool flag=false;
    for(int i=0;i<K;i++)flag|=(!(l.pos[i]<=mn(id,i)&&mx(id,i)<=r.pos[i]));
    if(!flag)return sum(id);//如果整个立方体全部在给定范围内
    int ans=0;
    flag=false;
    for(int i=0;i<K;i++)flag|=(!(l.pos[i]<=pos(id,i)&&pos(id,i)<=r.pos[i]));
    if(!flag)ans+=cnt(id);//当前点在给定范围内
    return ans+=Query(ls(id))+Query(rs(id));//继续递归求解
}

建树

为了让两边平衡,我们显然希望两边的点的个数一样多,那当前作为根节点的点一定是当前维度的中位数,直接排序的复杂度是 \(O(n\log n)\) 的,这使得建树的复杂度为 \(O(n\log^2n)\)。但实际上我们只需要让中位数在正确的位置上即可。利用快速排序的思路,将所有小于某个数的数放在该数左边,大于某个数的数放在该数右边,这样枚举到的数就在正确的位置上,我们只需要一直递归包含中位数的一侧即可,这样的复杂度是 \(O(n)\) 的。可以利用 nth_element 函数帮我们快速实现这个操作,这样建树的复杂度就是 \(O(n\log n)\)​ 的。

代码

#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define cnt(x) t[x].cnt
#define sum(x) t[x].sum
#define pos(x,i) t[x].pos[i]
#define mn(x,i) t[x].mn[i]
#define mx(x,i) t[x].mx[i]

int n,tot,cnt;
int rt,b[N];
struct KDtree{
    int ls,rs;
    int cnt,sum;
    int pos[K];
    int mn[K],mx[K];
}t[N];

void Update(int id){
    sum(id)=sum(ls(id))+sum(rs(id))+cnt(id);
    for(int i=0;i<K;i++){
        mn(id,i)=mx(id,i)=pos(id,i);
        if(ls(id)){
            mn(id,i)=min(mn(id,i),mn(ls(id),i));
            mx(id,i)=max(mx(id,i),mx(ls(id),i));
        }
        if(rs(id)){
            mn(id,i)=min(mn(id,i),mn(rs(id),i));
            mx(id,i)=max(mx(id,i),mx(rs(id),i));
        }
    }
    return ;
}

int Build(int l,int r,int dep=0){
    int mid=(l+r)>>1;
    nth_element(b+l,b+mid,b+r+1,[dep](int x,int y){return pos(x,dep)<pos(y,dep);});
    int x=b[mid];
    if(l<mid)ls(x)=Build(l,mid-1,dep^1);
    if(mid<r)rs(x)=Build(mid+1,r,dep^1);
    Update(x);
    return x;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        ++tot;
        cin>>pos(tot,0)>>pos(tot,1)>>cnt(tot);
        b[++cnt]=tot;
    }
    rt=Build(1,cnt);
    return 0;
}

增加

对于有修改的 \(\text{K-D tree}\),我们并不能保证加入后依旧平衡,所以需要重构 \(\text{K-D tree}\),常见的分治方式有根号重构和二进制分组,我一般选择理论更优的二进制分组。

根号重构

思路是存储所有需要插入的点的信息,当大小到达 \(B\) 时进行建树,修改复杂度均摊 \(O(\frac{n\log n}{B})\),查询是 \(O(B+n^{1-\frac{1}{k}})\),当 \(B=O(\sqrt{n\log n})\) 时最优,修改的复杂度为 \(O(\sqrt{n\log n})\),查询是 \(O(\sqrt{n\log n}+n^{1-\frac{1}{k}})\)。

二进制分组

思路是用多个 \(\text{K-D tree}\) 共同维护信息,第 \(i\) 个 \(\text{K-D tree}\) 的大小严格 \(2^i\)。当插入一个新节点时,将已有结点的 \(\text{K-D tree}\) 两两合并(拍扁重构),到没有能合并的子树时进行构建即可,复杂度是均摊的 \(O(n\log^2n)\)。

代码

#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define cnt(x) t[x].cnt
#define sum(x) t[x].sum
#define pos(x,i) t[x].pos[i]
#define mn(x,i) t[x].mn[i]
#define mx(x,i) t[x].mx[i]

int tot,cnt;
int R[L],b[N];
struct KDtree;

void Update(int id);

int Build(int l,int r,int dep=0);

void Del(int &id){
    if(id==0)return ;
    b[++cnt]=id;
    Del(ls(id));
    Del(rs(id));
    id=0;
    return ;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        ++tot;
        cin>>pos(tot,0)>>pos(tot,1)>>cnt(tot);
        b[cnt=1]=tot;
        for(int j=0;j<L;j++){
            if(R[j]==0){
                R[j]=Build(1,cnt);
                break;
            }else Del(R[j]);
        }
    }
    return 0;
}

标签:cnt,int,复杂度,tree,笔记,学习,立方体,id,define
From: https://www.cnblogs.com/DycBlog/p/18209427

相关文章

  • 给大家分享一套非常棒的python机器学习课程
    给大家分享一套非常棒的python机器学习课程——《AI小天才:让小学生轻松掌握机器学习》,2024年5月完结新课,提供配套的代码+笔记+软件包下载!学完本课程,可以轻松掌握机器学习的全面应用,复杂特征工程,数据回归,分类,算法的项目实战应用,以小学生的视角和知识储备即可学会。课程名字:AI小天才......
  • 【Java学习】第19节:时间类(Date、Calendar、SimpleDateFormat)、包装类
    目录第一章Date类1.1Date概述1.2Date常用方法第二章SimpleDateFormat类2.1构造方法2.2格式规则2.3常用方法2.4练习1(初恋女友的出生日期)2.5练习2(秒杀活动)第三章Calendar类3.1概述3.2常用方法3.3get方法示例3.4set方法示例:3.5add方法示例:第......
  • 学习第一天
    常用插件Chinese.中文语言包LiveServer:本地服务器,自动实时更新页面MarkdownPreviewEnha...预览markdown文档MarkdownLint:markdown语法检查器lidlnamelage[---|---|---10ladmin|30|l10ladmin|30110ladminl30单行代码:一个反引号·console.log('helloworld');·......
  • Flutter笔记:Widgets Easier组件库-使用隐私守卫
    Flutter笔记WidgetsEasier组件库:使用隐私守卫-文章信息-Author:李俊才(jcLee95)VisitmeatCSDN:https://jclee95.blog.csdn.netMyWebSite:http://thispage.tech/Email:[email protected]:https://blog.csdn.net......
  • Redis安装学习记录
    一、Redis介绍Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过RedisSentinel提供高可用......
  • dfs-tree
    华为20220923题目描述有一家云存储服务提供商,他们的存储系统采用主从模式以确保高可用性。当主节点发生故障时,系统会自动切换到备用节点。为了保证系统的稳定性,需要检测服务状态,并在必要时触发主备切换。存储系统中的服务有依赖关系,每个服务最多依赖一个其他服务,且依赖关系不成......
  • 03-Excel基础操作-学习笔记
    本节接着继续介绍排序工具以及一个重要内容分类汇总工具的使用。01自定义排序我们在上一节接触到了使用排序工具,对数字之类的Excel内置的程序可以通过点击操作,但是当超出Excel内置的范围又当如何应对?比如,存在如下场景:针对文字的排序,我们对销售部门所在列进行排序,顺序为“一部......
  • golang 的学习曲线
     Go(Golang)语言的设计目标之一就是让其学习曲线尽可能平缓,这意味着对于大多数开发者来说,学习Go语言比许多其他现代编程语言可能更快上手。以下是通常Golang学习曲线的一个概述:1入门阶段: 基本语法:Go语言的语法相对简单,与C/C++和Java有一定的相似性,所以对于有这些背景的开发......
  • 问题2:yum install pstree无法安装
    解决办法1.查找pstree命令在哪个包内,执行命令:yum provideslsof2.找到对应的包名:执行安装命令:yuminstallpsmisc3.结果再次执行pstree查看命令执行情况  ......
  • Linux学习笔记16---常用操作命令(free命令)
    free命令显示系统内存的使用情况,包括物理内存、虚拟内存(swap)和内核缓冲区内存。如果加上-h选项,输出的结果会友好很多:有时我们需要持续的观察内存的状况,此时可以使用-s选项并指定间隔的秒数:$free-h-s3上面的命令每隔3秒输出一次内存的使用情况,直到你按下ctr......