关于在指定区间求第k小的数,求高效率算法

nuptxxp 2011-07-02 06:49:08
加精
给定一个整数数列a[1..n],其中每个元素都不相同,你的程序要能回答一组格式为Q (i , j , k)的查询,Q(i, j ,k)的意思是“在a[i..j]中第k小的数是多少?”
例如令 a = {1, 5, 2, 6, 3, 7, 4},查询格式为Q (2 , 5 , 3),数列段a[2..5] = {5, 2, 6, 3},第3小的数是5,所以答案是5。

第一行包括一个正整数n,代表数列的总长度,还有一个数m,代表有m个查询。n、m满足:1≤n≤100 000,1≤m≤5 000。

第二行有n个数,代表数列的元素,所有数都不相同,而且不会超过10^9

接下来有m行,每行三个整数i、j、k,代表一次查询,i、j、k满足:1≤i≤j≤n, 1≤k≤j−i+1。

关于这个问题可能已经讨论了很多次,但是我用快排的变形还是超时,希望能看更高效的算法

...全文
4394 102 打赏 收藏 转发到动态 举报
写回复
用AI写文章
102 条回复
切换为时间正序
请发表友善的回复…
发表回复
Sky丶Memory 2013-10-11
  • 打赏
  • 举报
回复
可以用划分树做,
缺媳妇的郝 2011-08-05
  • 打赏
  • 举报
回复
学习了……
soulou 2011-08-04
  • 打赏
  • 举报
回复
推荐博文:
http://blog.csdn.net/v_july_v/article/details/6452100
qman007 2011-08-04
  • 打赏
  • 举报
回复
首先进行排序,推荐用堆排序。
然后索引k中的数据就是第k小
的数据。

优化时要对排序过程优化,记录
从小到大排序的索引==k时停止排序。
AndyZhang 2011-08-03
  • 打赏
  • 举报
回复
果断线段树或者树状数组(有一维和二维之分)。。。。
陈绍唐我爱你 2011-07-30
  • 打赏
  • 举报
回复
学习中!
pzlvv 2011-07-29
  • 打赏
  • 举报
回复
LZ的方法在最坏的情况下会产生O(n^2)的复杂度,如果稍加改进,能得到最坏情况为O(n)的算法。
具体如下:
对n个元素划分为ceil(n/5)组,那么除了最后一组为 n mod 5个元素,其它均为5个。
对每组进行排序(由于固定是5-个元素,所以无论排序算法,些处复杂度均为O(n)
选出每一组的中位数,再递归调用本函数找出这些中位数的中位数。
按中位数的中位数对原数组进行划分,这样就能保证是一个比较优秀的划分(至少不会是最差的)
后面就跟LZ的方法一样了,这里只是对划分函数进行了改进而已。附上一个测试代码,不过不知道有没有BUG,呵呵。
#include <iostream>
#include <cmath>
#include <string>
using namespace std;

template <class T>
int _partition(T a[], int p, int r, T x)
{
for (int i=p; i<r; i++)
{
if (a[i] == x)
{
swap(a[i], a[r]);
break;
}
}
int i = p-1;
for (int j=p; j<r; j++)
{
if (a[j] <= x)
{
i++;
swap(a[i], a[j]);
}
}
swap(a[i+1], a[r]);
return i+1-p;
}

template <class T>
void insertion_sort(T a[], int n)
{
for (int i=1; i<n; i++)
{
T key = a[i];
int j = i-1;
for (; j>=0 && a[j]>key; j--)
{
a[j+1] = a[j];
}
a[j+1] = key;
}
}

template <class T>
void insertion_sort(T a[], int p, int r)
{
insertion_sort(a+p, r-p+1);
}

template <class T>
int select(T a[], int p, int r, int k)
{
if (r==p)
return a[r];
int i=p;
int m = ceil((r-p+1)/5.0);
T* mid = new T[m];

while (i<r)
{
if (i+4<=r)
{
insertion_sort(a, i, i+4);
mid[(i-p)/5] = a[i+2];
i+=5;
}
else
{
insertion_sort(a, i, r);
mid[m-1] = a[i+(r-i)/2];
i=r+1;
}
}

int x = select(mid, 0, m-1, (m-1)/2);
int index = _partition(a, p, r, x);
delete[] mid;
if (index == k) return x;
else if (index < k)
{
x = select(a, p+index+1, r, k-index-1);
return x;
}
else return select(a, p, p+index-1, k);
}

int main()
{
int a[10] = {2,3,6,9,0,7,1,8,5,10};
for (int i=0; i<10; i++)
{
cout << select(a, 0, 9, i) << ' ';
}
cout << endl;
}
nuptxxp 2011-07-29
  • 打赏
  • 举报
回复
[Quote=引用 95 楼 pzlvv 的回复:]
LZ的方法在最坏的情况下会产生O(n^2)的复杂度,如果稍加改进,能得到最坏情况为O(n)的算法。
具体如下:
对n个元素划分为ceil(n/5)组,那么除了最后一组为 n mod 5个元素,其它均为5个。
对每组进行排序(由于固定是5-个元素,所以无论排序算法,些处复杂度均为O(n)
选出每一组的中位数,再递归调用本函数找出这些中位数的中位数。
按中位数的中位数对原数组进行划分,这样就……
[/Quote]
ls说的是线性时间选择算法吧,这个虽然号称线性,但是它由于前面的c系数很大,所以他的实际复杂度平均起来甚至比不过快排的变形
ls可以测试一下我在84l发的测试数据,再与我在85l发的程序对比一下,你可以发现时间差距与效率的差别
foiresdn 2011-07-28
  • 打赏
  • 举报
回复
kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
无聊找乐 2011-07-27
  • 打赏
  • 举报
回复
如果没有重复的数,遍历一次,就能得到结果
jackzhhuang 2011-07-27
  • 打赏
  • 举报
回复
[Quote=引用 91 楼 jackzhhuang 的回复:]
从区间段开始遍历,构造一个只有N个结点的二叉树,左根结点最大,父节点中间,右根结点最小,遍历结束就构造完毕,这时,最右边的根结点就是第N小的值,构造这个二叉树时间是lgN
[/Quote]

对了,遍历需要时间是和区间长度有关,所以所需时间应该是O(n)
jackzhhuang 2011-07-27
  • 打赏
  • 举报
回复
从区间段开始遍历,构造一个只有N个结点的二叉树,左根结点最大,父节点中间,右根结点最小,遍历结束就构造完毕,这时,最右边的根结点就是第N小的值,构造这个二叉树时间是lgN
lmc158 2011-07-27
  • 打赏
  • 举报
回复
#include<stdio.h>
#define LIM 100000+10
int a[LIM];
int b[LIM];
void qicksort(int *a,int left,int right,int k);
void swap(int *p,int a,int b)
{
int temp;
temp=p[a];
p[a]=p[b];
p[b]=temp;
}
int main()
{
//freopen("in.txt","r",stdin);
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
scanf("%d",a+i);
for(int i=0;i<m;++i)
{
int left,right;
int k;
scanf("%d%d%d",&left,&right,&k);
for(int i=left-1;i<=right-1;++i)
b[i]=a[i];
qicksort(b,left-1,right-1,k+left-2);
printf("%d\n",b[k+left-2]);
}
return 0;
}
void qicksort(int *a,int left,int right,int k)
{
int i;
int last;
if(left>=right)
return ;
last=left;
for(i=left+1;i<=right;++i)
if(a[i]<a[left])
swap(a,++last,i);
swap(a,left,last);
if(last>k)
qicksort(a,left,last-1,k);
else if(last<k)
qicksort(a,last+1,right,k);
}
这是我的程序,由于数据达到10^8,必须需要O(n)的算法
zzxap 2011-07-26
  • 打赏
  • 举报
回复
哈希表+小根堆
leonardWang 2011-07-26
  • 打赏
  • 举报
回复
POJ 都有题的 用划分树做
http://poj.org/problem?id=2104
zhoujk 2011-07-26
  • 打赏
  • 举报
回复
还是个TopK问题啊,只是在进入TopK判断之前,先判断一下是否在(i,j)的范围之内。
nuptxxp 2011-07-26
  • 打赏
  • 举报
回复
线段树代码可以参考34楼的
划分树代码如下:郑重声明:这段代码是copy别人的,当时是copy谁的我也忘了,sorry。。。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>


using namespace std;

int SU[100001];
int U[21][100001];
int toL[21][100001];
int n,m,a,b,k,t;

/*inline int cmp(const void *a,const void *b)
{
return *((int *)a) - *((int *)b);
}*/

void buildtree(int l,int r,int d)
{
if (l==r) return;
int mid = (l+r)>>1 , nowl = l , nowr = mid+1 , midtol = 0;
for (int i = mid ;i>=l && SU[i] == SU[mid] ; i--) ++midtol;
for (int i = l ; i <= r ; i++)
{
toL[d][i] = i==l ? 0 : toL[d][i-1];
if (U[d][i] < SU[mid])
{
U[d+1][nowl++] = U[d][i];
++toL[d][i];
}
else if (U[d][i]==SU[mid] && midtol)
{
U[d+1][nowl++] = U[d][i];
++toL[d][i];
--midtol;
}
else U[d+1][nowr++] = U[d][i];
}
buildtree(l,mid,d+1);
buildtree(mid+1,r,d+1);
}

int answer(int a,int b,int k)
{
int l = 1,r = n,d = 0;
int ls,rs,mid;
while (a != b)
{
ls = a==l ? 0 : toL[d][a-1];
rs = toL[d][b];
mid = (l+r)>>1;
if (k <= rs - ls)
{
a = l+ls;
b = l+rs-1;
r = mid;
}
else
{
a = mid+1+a-l-ls;
b = mid+1+b-l-rs;
k -= rs-ls;
l = mid+1;
}
++d;
}
return U[d][a];
}

void init()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&t);
SU[i]=U[0][i]=t;
}
//qsort(SU+1,n,sizeof(SU[0]),cmp);
sort(SU+1,SU+n+1);
buildtree(1,n,0);
}

int main()
{
freopen("out.txt","r",stdin);
init();
for (;m;--m)
{
scanf("%d%d%d",&a,&b,&k);
printf("%d\n",answer(a,b,k));
}
return 0;
}
nuptxxp 2011-07-26
  • 打赏
  • 举报
回复
谢谢ls各位的回答,关于这个问题最后还是两个数据结构:划分树与线段树最好

如果认为堆还是很好的可以试一下下面这个生成测试数据的程序

各位可以试试维护堆所用的时间与划分树,线段树效率的比较

相信大家测试之后就会明白划分树与线段树的高效了
#include <iostream>

using namespace std;

int main()
{
freopen("out.txt","w",stdout);
time_t it;
int n,m;
srand(time(&it));
for(int i=0;i<3;++i){//生成3组测试数据
if(i == 0){//第一组使用最坏的数据
n=100000;
m=5000;
}
if(i == 1){//第二组使用稍小一点的数目
n=rand()%10000+50000;
m=rand()%1000+3000;
}
if(i == 2){//第三组更小
n=rand()%10000+1;
m=rand()%2000+1;
}
printf("%d %d\n",n,m);
for(int k=0;k<n;++k){
printf("%d ",rand()%100000000+1);
}
printf("\n");
for(int k=0;k<m;++k){
int a=rand()%n+1;
int b=rand()%n+a;
int c=rand()%(b-a+1)+1;
printf("%d %d %d\n",a,b,c);
}
}
return 0;

}

ArtStealer_Sub 2011-07-26
  • 打赏
  • 举报
回复
选择排序,选择最小的,循环选择到第K次结束,返回第k个数据。
最差情况 n*lon(n)
xiaoxi880 2011-07-26
  • 打赏
  • 举报
回复
顶一下。
加载更多回复(74)

33,007

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧