利用划分树求解整数区间内第K大的值

划分树的基本概念:划分树是一种基于线段树的数据结构,其基本思想就是将待查找区间划分为两个子区间:不大于数列中间值的元素被分配到左儿子的子区间,简称左子区间;大于数列中间值的元素被分配到右儿子的子区间,简称右子区间。

算法思路类似于二分法。

整个算法分两步:

1)建树——离线构建整个查询区间的划分树。

2)查找——在划分树中查找某个子区间中第K大值。

建树过程:

对于区间【l,r】,首先通过对远数列排序找到这个区间的中间值的位置Mid=【(l+r)/2】(向下取整),不大于中间值的数划入左子树【l,mid】,大于中间值的数划入右子树【mid+1,r】。同时,对于第i个数,记录在【l,i】区间内有多少整数被划入左子树。然后继续对它的左子树区间【l,mid】和右子树区间【mid+1,r】递归建树,直到划分出最后一层的叶节点为止,显然,建树过程是自上而下的。

具体实现方法:

先将读入的数据排序成sorted【】,取区间【l,r】的中间值sorted【mid】(mid=【(l+r)/2】(向下取整)),然后扫描【l,r】区间,依次将每个整数划分入左子区间【l,mid】或右子区间【mid+1,r】中去。注意,被分到每个子树的整数是相对有序的,即对于被分到每一棵子树里面的数,不改变他们以前的相对顺序。另外,在进行这个过程的时候,记录一个类似于前缀和的东西,即l到i这个区间内有多少整数被划分到左子树。设:

tree【p】【i】——第 p层中第i位置的整数值,初始序列为tree【0】【】。

sorted【】——排序序列,即存储tree【0】【】排序后的结果。

toleft【】【】——toleft【p】【i】表示第p层前i个数中有多少个整数倍分入下一层的左子区间。

lpos和rops——下一层左子区间和右子区间的指针。

same——当前区间等于中间值的整数个数。

具体代码实现:

void bulid(int l,int r,int dep)
{
if(l==r) return;
int mid=(l+r)>>1;
int same=mid-l+1;

for(int i=1;i<=r;i++)
if(tree[dep][i]<sorted[mid])
same--;

int lpos=l
int rpos=mid+1;
for(int i=1;i<=r;i++)
{
if(tree[dep][i]<sorted[mid])
tree[dep+1][lops++]==tree[dep][i];
elseif(tree[dep][i]==sorted[mid]&&same>0)
{
tree[dep+1][lops++]=tree[dep][i];
same--;
}
else tree[dep+1][rops++]=tree[dep][i];
toleft[dep][i]=toleft[dep][i-1]+lops-1;
}
build(l,mid,dep+1);
build(mid+1,r,dep+1);
}

在划分树上查询子区间【l,r】中第K大的数:
从划分树的根出发自上而下的寻找:若查询至叶子(l==r),则该整数(tree【dep】【l】)为子区间【l,r】中第K大的树;否则区间【L,l-1】内有toleft【dep】【l-1】个整数进入下一层的左子树,区间【L,r】内有toleft【def】【l-1】个整数进入下一层的左子树,区间【L,r】内有toleft【dep】【r】个整数进入下层的左子树。显然,在查询子区间【l,r】有cnt=toleft【def】【r】-toleft【dep】【l-1】个整数进入了下一层的左子树。如果cnt》=k,则递归查询左子树【L,mid】;否则递归查询右子树。
难点是,如果在对应子树的大区间中计算被查询的子区间【newl,newr】
若递归查询左子树则,则当前子区间的左边是上一层区间【L,l-1】里的toleft【dep】【l-1】-toleft【dep】【L-1】个整数,由此得到newl=L+toleft【dep】【l-1】-toleft[dep][L-1];加上上一层查询区间【l,r】中的cnt个整数,newr=newl+cnt-1.
int query(int L,int R,int l,int r,int dep,int k)
{
	if(l==r) return tree[dep][l];
	
	int mid=(L+R)>>1;
	int cnt=toleft[dep][r]-toleft[dep][l-1];
	if(cnt>=k)
{
	int newl=L+toleft[dep][l-1]-toleft[dep][L-1];
	int newr=newl+cnt-1;
	return query(L,mid,newl,newr,dep+1,k);
}


	else{
		int newr=r+toleft[dep][R]-toleft[dep][r];
		int newl=newr-(r-1-cnt);
		return query(mid+1,R,newl,newr,dep+1,k-cnt);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值