归并树&划分树详解

原文网址http://www.xuebuyuan.com/829409.html
这里写图片描述
红色的点是此节点中被划分到左子树的点。

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

  那我们如何确定一个子树的边界? 在划分树中,我们都是采用递归的方式进行访问的,如果一个节点的边界是(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

include

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个元素有多少被划分到了左子树,看代码很容易理解。

建树代码如

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值