现在的位置: 首页 > 综合 > 正文

求第K小/大的数(树状数组解法)

2014年01月21日 ⁄ 综合 ⁄ 共 3369字 ⁄ 字号 评论关闭

求第K小/大数这个题目经常出现,面试,考试以及OJ上都有类似的题目。

首先声明一点,个人觉得既然是第K小(大是一样的),那么重复的元素就不应该算了,当然如果算了就相对简单一些。。

最原始的解法,快排,然后取第K个数。或者是构建一个小顶堆,遍历数组取最小的K个数,再然后还有所谓的快速选择,有人证明了可以在O(n)时间内解决,不过我不太清楚这种算法是否对重复元素有效(但看了代码其实就是快排的一种应用,但个人觉得不适应于重复元素)

其实最简单的办法:

#include<algorithm>

sort(array,array+n);

unique(array,array+n)

直接调用库函数排序,去重,一般的数据这样就足够快了。

但是学了树状数组,总是看人家的代码感觉没学到什么,刚好前几日曙光也问过我同样的问题,YY了一阵子,正好用树状数组解决一下,别忘了,我们的树状数组最基本的功能哦【第i位置上村的是比左边大的数】

好了回归正题。第K小(如果是大的话就是第length-K-1小)

建树的过程必须特别注意,我们之前关于树状数组的题目中tree中每个元素都是有实际值得,但是这里我们考虑的话,某些位置是空的,也就是tree的最终大小由我们所要处理的数组中最大元素的大小决定的,所以如果使用树状数组的话,前提就是所要处理的数组中最大值不能太大【基数排序中的要求也是类似】否则内存吃不消。

在这个分类总的第一篇文章是翻译了一篇老外讲的BIT,很详细,其中有两个部分:readSingle函数和find函数当时看懂了,但是不知道实际应用在哪了,终于找到它的实际应用地方了【嘿嘿,好开心】。

建树的时候我们不能像之前那样每扫描一次数组就更新一次,因为有重复元素,一旦更新重复元素的话,树就建错了,我们就得不到所要的结果,那么怎么处理呢,这时翻译的那篇文章中提到的readSingle函数大显神威:

建树代码如下:具体的已经写在了注释中

for(int i=0;i<len;i++)
	{
		if((data[i]&1)==1)
		{//对于基数来说,这个位置上有值就意味着有这个元素
			if(!tree[data[i]])
			{//当没有时才允许更新数组
				update(data[i],1);
				flag[data[i]]=1;
			}
		}
		else
		{/*对于偶数位置来说,以为偶数位置存储的都是一段和,不能仅仅通过当前的
		值来判断,这时需要还原出原始的tree[i]了,还原就是readSingle函数,
		如果还原出的值为0,就意味着这个值没有,那么我们需要更新,如果有的话,那么
		就跳过更新的过程,进行下一个元素的考察*/
			int tmp=readSingle(data[i]);
			if(!tmp)
			{
				update(data[i],1);
				flag[data[i]]=1;
			}
		}
	}

树建好了,工作就完成了一大半了:

现在说一下查询第K小的数了,轮到find函数大显神威了,关于这个函数就不多做解释了,不理解的可以回去看下我之前翻译的那篇关于BIT的详细解释,跟着注释以及图就可以看明白:【唯一要说的是原版的文章中给了2种find方法,第一种是找到一个就返回,第二种就是找到最大的idx返回,但是我测试的时候发现找到一个返回就可行,但是找到最大的有问题】

//查询第K小的数
int Find_Kth(int K)
{
	int idx = 0; 
	int bitMask=MaxVal;
	int num=0;
	while(bitMask)
	{
		num++;
		bitMask>>=1;
	}
	bitMask=1<<(num-1);
	while ((bitMask != 0) && (idx < MaxVal))
	{
		int tIdx = idx + bitMask; 
		if (K == tree[tIdx]) //找到一个就返回
			return tIdx;
		 if (K >tree[tIdx]){ 
			idx = tIdx;
			K -= tree[tIdx]; 
		}
		bitMask >>= 1; 
	}
	if (K != 0) 
		return -1;
	else
		return idx;	

	//return 1;
}

好了完整的代码如下:我们的时间复杂度为O(nlog(MaxVal))【说明一下,readSingle函数的时间复杂度为c*log(idx),基本可以看做是常量,find_kth函数时间复杂度为O(logMaxVal),所以总的时间复杂度为O(nlog(MaxVal))】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=110000;

int n,val[N],a[N];
int len,tree[N];
bool  flag[N];
int MaxVal;
int lowbit(int x){
    return x&(-x);
}

void update(int x,int val){
    while(x<=MaxVal){
      tree[x]+=val;
        x+=lowbit(x);
    }
}

int getsum(int x){
    int res=0;
    while(x){
        res+=tree[x];
        x-=lowbit(x);
    }
    return res;
}
int readSingle(int idx)
{
	int sum = tree[idx]; // sum will be decreased
	if (idx > 0)
	{ // special case
		int z = idx - (idx & -idx); // make z first
		idx--; // idx is no important any more, so instead y, you can use idx
		while (idx != z)
		{ // at some iteration idx (y) will become z
			sum -= tree[idx]; 
			// substruct tree frequency which is between y and "the same path"
			idx -= (idx & -idx);
		}
	}
	return sum;
}
//查询第K小的数
int Find_Kth(int K)
{
	int idx = 0; 
	int bitMask=MaxVal;
	int num=0;
	while(bitMask)
	{
		num++;
		bitMask>>=1;
	}
	bitMask=1<<(num-1);
	while ((bitMask != 0) && (idx < MaxVal))
	{
		int tIdx = idx + bitMask; 
		if (K == tree[tIdx]) 
			return tIdx;
		 if (K >tree[tIdx]){ 
			idx = tIdx;
			K -= tree[tIdx]; 
		}
		bitMask >>= 1; 
	}
	if (K != 0) 
		return -1;
	else
		return idx;	

	//return 1;
}
int main(){
	//不考虑重复的数
	int data[10]={1,2,2,2,2,2,2,3,10,5};
	MaxVal=10;
	len=10;
	//update(1,1);
	/*扫描数组,对于位置为奇数和偶数分开处理
	flag数组记录有哪些元素,因为我们最终找到的值是一个和,仍然需要进行下一步处理
	即向前搜索找到一个存在的值,那么如果再用readSingle函数太麻烦,用一次之后就用
	flag标记一下就可以下次用了*/
	for(int i=0;i<len;i++)
	{
		if((data[i]&1)==1)
		{//对于基数来说,这个位置上有值就意味着有这个元素
			if(!tree[data[i]])
			{//当没有时才允许更新数组
				update(data[i],1);
				flag[data[i]]=1;
			}
		}
		else
		{/*对于偶数位置来说,以为偶数位置存储的都是一段和,不能仅仅通过当前的
		值来判断,这时需要还原出原始的tree[i]了,还原就是readSingle函数,
		如果还原出的值为0,就意味着这个值没有,那么我们需要更新,如果有的话,那么
		就跳过更新的过程,进行下一个元素的考察*/
			int tmp=readSingle(data[i]);
			if(!tmp)
			{
				update(data[i],1);
				flag[data[i]]=1;
			}
		}
	}
	//我们取得了第4小的值,但是这个位置到底有没有值,需要通过flag数组来判断
	int res=Find_Kth(4);
	while(!flag[res])
		res--;
	printf("%d",res);
	return 0;
}

抱歉!评论已关闭.