划分树基础知识

原网址:划分树详解

对4 5 2 8 7 6 1 3 分别建划分树和归并树

划分树如下图

红色的点是此节点中被划分到左子树的点。

我们一般用一个结构体数组来保存每个节点,和线段树不同的是,线段树每个节点值保存一段的起始位置和结束位置,而在划分树和递归树中,每个节点的每个元素都是要保存的。为了直观些,我们可以定义一个结构体数组,一个结构体中保存的是一层的元素和到某个节点进入左子树的元素的个数,不同于线段树,我们不能保存一个节点的起始结尾位置,因为随层数的增加,虽然每个结构体保存的元素数目是一定的,但随层数的增加,元素早已被划分到不同的子树中了,而且这数目是指数增加的。

那我们如何确定一个子树的边界? 在划分树中,我们都是采用递归的方式进行访问的,如果一个节点的边界是(l,r),假设mid = (l+r )/2,那么他的左右子树的边界恰好是(l,mid)和(mid+1, r),然后在进行下一层的递归。

const int maxn = 100000 + 10;
typedef struct node
{
    int num[maxn];
    int cnt[maxn];
}tree[20];

至于这里为什么将tree大小定义20!和线段树一样,划分树都是完全完全二叉树,叶子节点的深度相差不会超过1,而且所有非叶子节点都有左右子树。关于划分树的题目,我们遇到的数据量一般都是10^5,也就是说如果把这些数建成树的话深度不会超过20。

我们看图片会发现划分树有以下几个特点。

1,树的根节点是原来的数组,没有做任何处理。

2,和快排有些类似,每个节点的子节点(如果有)中,左节点的所有元素都有小于右节点的所有元素,前提是原数组中无重复的数,关于存在重复的元素的情况,我们会详细讨论。

3,如果我们从左到有取出所有叶子节点,每个叶子节点的元素(肯定只有一个元素),这些元素必然是有序的。

我们以poj 2104   k-th number 为例,详细说一下划分树的建造和查询。

//poj 2104
//2013-04-15-19.47
#include <stdio.h>
#include <algorithm>
const int maxn = 100005;
using namespace std;

int sor[maxn];
struct node
{
    int num[maxn];
    int cnt[maxn];
}tree[20];

接下来是建树的函数,建树之前,将数组放树的第一层,当做根节点,然后将原数组进行排序(至于升降视情况而定,但在整个程序中要统一)放在另外一个数组中,我这里放在sor中。我们先讨论集合中没有重复元素的情况,先找出mid(当前节点的中间位置),然后从左到右遍历所有元素,如果小于等于sor[mid] 放入左子树,否则放入右子树,然后递归创建左右子树。

如果其中包含相同元素,而且恰好sor[mid] 左右都有与他相同的元素,这样我们就不能仅仅依靠比较大小来决定该数的去向了。当然,小于sor[mid]的必然去左子树,这是毋庸置疑的,但对于相同的元素,我们有个巧妙的处理方法,先计算在有序数组中sor[mid]左边有多少个和sor[mid]的元素,比如说有x个,然后在建树过程中将出现的前x+1个和sor[mid]相等的放入左子树,其他如右子树即可,然后递归建立左右子树。

另外还有非常重要的一部分,不是还有一个cnt[]数组吗,这个数组是划分树的核心部分,它的作用是记录这个节点到第i个元素有多少被划分到了左子树,看代码很容易理解。

建树代码如下

void buildtree(int l, int r, int d)
{
    if (l == r)
    {
        return;
    }
    int mid = (l+r)>>1;
    int oplift = l, opright = mid+1;   //对左右子树的操作位置的初始化
    int same_as_mid = 0;
    /*用来计算在mid左边有多少个和sor[mid]相同的数(包括mid),这些都要放到左子树*/
    for (int i = mid; i > 0; i--)
    {
        if (sor[i] == sor[mid])
            same_as_mid++;
        else
            break;
    }
    int cnt_lift = 0;
    for (int i = l; i <= r; i++)
    {
        if (tree[d].num[i] < sor[mid])
        {
            tree[d+1].num[oplift++] = tree[d].num[i];
            cnt_lift++;
            tree[d].cnt[i] = cnt_lift;
        }
        else if(tree[d].num[i] == sor[mid] && same_as_mid)
        {
            tree[d+1].num[oplift++] = tree[d].num[i];
            cnt_lift++;
            tree[d].cnt[i] = cnt_lift;
            same_as_mid--;
        }
        else
        {
            tree[d].cnt[i] = cnt_lift;
            tree[d+1].num[opright++] = tree[d].num[i];
        }
    }
    buildtree(l, mid, d+1);
    buildtree(mid+1, r, d+1);
}

关于询问操作,假如我们询问从区间[ql,qr]中第k大的数(看清具体要求,有些是第k小数),然后我们通过cnt数组,确定[ql,qr]有多少个点进入了左子树,然后判断第k数在左还是右子树,然后递归查询。

肯定有m = cnt[qr] - cnt[ql-1]个数进入了左子树,如果ql是节点的左边界的话就有cnt[qr]个数进入左子树了,这里要额外注意。如果m <= k,,进入左子树查询第k数,否则进入右子树查询k-m数,我们还要确定在子树中查询的边界, 只是一个比较难理解的地方。

int query(int l, int r, int d, int ql, int qr, int k)
//6个参数,分别是当前节点的左右边界、深度、询问的左右边界及k值
{
    if (l == r)
        return tree[d].num[l];
    int mid = (l+r)>>1;
    int sum_in_lift, lift;
    if (ql == l)
    {
        sum_in_lift = tree[d].cnt[qr];
        lift = 0;
    }
    else
    {
        sum_in_lift = tree[d].cnt[qr] - tree[d].cnt[ql-1];
        // 区间进入左子树的总和
        lift = tree[d].cnt[ql-1];
    }
    if (sum_in_lift >= k)
    //证明要找的点在左子树
    {
        int new_ql = l+lift;
        int new_qr = l+lift+sum_in_lift-1;
        return query(l, mid, d+1, new_ql, new_qr, k);
        /*这里有必要解释一下,我们要确定下一步询问的位置,如果在ql的左边有i个进入左子树,
        那么ql到qr中第一个进入左子树的必定在l+i的位置*/
    }
    else
    {
        int a = ql - l - lift;
        int b = qr - ql + 1 - sum_in_lift;
        int new_ql = mid + a + 1;
        int new_qr = mid + a + b;
        return query(mid+1, r, d+1, new_ql, new_qr, k - sum_in_lift);
    }
}
时间: 2024-09-30 07:43:35

划分树基础知识的相关文章

线段树基础知识----(基础数据结构)--(一)

1.定义 引入:为什么要使用线段树而不用数组模拟呢? answer:因为有些题用数组来做就要超时,用线段树的O(log(n))的时间复杂度刚好可以求解 毫无疑问线段树是一种数据结构,但是它实际是一个类似树状的链表结构(个人认为) ///还是要正经一点(照搬教科书)----------- / ////////////////////////////////////////////////////////////////////// 线段树定义:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分

[转帖]交换网络基础知识VLAN及VLAN划分,一分钟了解下

交换网络基础知识VLAN及VLAN划分,一分钟了解下 https://www.toutiao.com/i6772206771248300556/ 一.VLAN基础 VLAN是英文Virtual Local Area Network的缩写, 即虚拟局域网.一方面, VLAN建立在局域网交换机的基础之上:另一方面, VLAN是局域交换网的灵魂.这是因为通过VLAN用户能方便地在网络中移动和快捷地组建宽带网络, 而无需改变任何硬件和通信线路.这样, 网络管理员就能从逻辑上对用户和网络资源进行分配, 而

基础知识漫谈(2):从设计UI框架开始

说UI能延展出一丢丢的东西来,光java就有swing,swt/jface乃至javafx等等UI toolkit,在桌面上它们甚至都不是主流,在web端又有canvas.svg等等. 基于这些UI工具包\框架,又产生了大量通用的或者业务性的UI框架,比如Draw2d.GEF.easyUI乃至国内的EChart.白鹭等等. 这些框架的业务范围各异,一个程序员的时间和精力有限,你不可能全部都掌握,又不能预言出是哪一个将来会独步天下,甚至,连当前哪一个最流行,都够打一阵嘴炮. 那,我们应该学什么?

Oracle数据库基础知识

oracle数据库plsql developer 目录(?)[-] 一     SQL基础知识 创建删除数据库 创建删除修改表 添加修改删除列 oracle cascade用法 添加删除约束主键外键 创建删除索引 创建修改删除视图 二     SQL查询 基本的SQL语句 unionminusintersect 内连接外连接 子查询关联子查询 betweeninexists 复制表insert into selectselect into from 三     SQL查询优化 尽量少用 IN 操

【RAC】RAC相关基础知识

[RAC]RAC相关基础知识 1.CRS简介    从Oracle 10G开始,oracle引进一套完整的集群管理解决方案—-Cluster-Ready Services,它包括集群连通性.消息和锁.负载管理等框架.从而使得RAC可以脱离第三方集群件,当然,CRS与第三方集群件可以共同使用. (1).CRS进程 CRS主要由三部分组成,三部分都作为守护进程出现 <1>CRSD:资源可用性维护的主要引擎.它用来执行高可用性恢复及管理操作,诸如维护OCR及管理应用资源,它保存着集群的信息状态和OC

(转)Linux基础知识学习

Linux基础知识学习 原文:http://blog.csdn.net/ye_wei_yang/article/details/52777499 一.Linux的磁盘分区及目录 Linux的配置是通过修改配置文件来完成. 1.1.Linux磁盘分区 Linux可以将磁盘分为多个分区,每个分区可以被当做一个独立的磁盘使用,磁盘类型:主分区.扩展分区.逻辑分区. 主分区标记为活动,用于操作系统的引导,一块磁盘最多划分4个主分区,主分区存放操作系统的文件或用户数据. 扩展分区:主分区小于4个时才可以划

第二章 面试需要的基础知识

2.1 面试官谈基础知识 基础很重要 算法.复杂的 编程能力 数据结构 2.2 编程语言 程序员写代码总是基于某一种编程语言,因此技术面试的时候直接或者间接都会涉及至少一种编程语言.在面试的过程中,面试官要么直接问语言的语法,要么让应聘者用-~种编程语言写代码解决一个问题,通过写出的代码来判断应聘者对他使用的语言的掌握程度.现在流行的编程语言很多,不同公司开发用的语言也不尽相同.做底层开发比如经常写驱动的人更习惯用C, Linux下有很多程序员用C++开发应用程序,基于Windows的C#项目已

20181128-信息系统基础知识

学习目标 听<信息系统基础知识> 一节 待解决问题 构件的概念 ? 构件是面向软件体系架构的可复用软件模块.构件(component)是可复用的软件组成成份,可被用来构造其他软件.它可以是被封装的对象类.类树.一些功能. 如何表达一个项目的架构,用什么图表? 架构设计作为一个系统开发的中间产品,交付的是什么内容? 各种架构风格的适用场景? 互联网应用  BS架构的应用  ria 富互联网是什么意思? ria 丰富互联网应用. j2ee与.net的比较? 中间件技术的6种功能? 形式化方法是怎样

20181217-信息系统基础知识

学习目标 听<信息系统基础知识> 一节 待解决问题 构件的概念 ? 构件是面向软件体系架构的可复用软件模块.构件(component)是可复用的软件组成成份,可被用来构造其他软件.它可以是被封装的对象类.类树.一些功能. 如何表达一个项目的架构,用什么图表? 架构设计作为一个系统开发的中间产品,交付的是什么内容? 各种架构风格的适用场景? 互联网应用  BS架构的应用  ria 富互联网是什么意思? ria 丰富互联网应用. j2ee与.net的比较? 中间件技术的6种功能? 学习内容 测试评