使用分治法求解数组的第k大数

http://blog.csdn.net/hackbuteer1/article/details/6651804

首先,需要在一个数组中,随机抽出一个元素,然后比这个元素大的放到他的右边,比这个元素小的放在它的左边:

void pailie(int* &p,int len)
{
	time_t t;
   srand((unsigned) time(&t));
   int index=rand()%len;
   swap(p[index],p[len-1]);
   for(int i=0;i<len;i++)
   {
	   cout<<p[i]<<" ";
   }
   cout<<endl;
   int j=-1;
   int i;
   for(i=0;i<len;i++)
   {
	   if(p[i]<p[len-1])
	   {
		   swap(p[++j],p[i]);
	   }
   }
   swap(p[++j],p[len-1]);
}

int main()
{
	int temp[]={4,1,10,5,9,6};
	int *test=temp;
	pailie(test,6);
	for(int i=0;i<6;i++)
   {
	   cout<<test[i]<<" ";
   }
   cout<<endl;
	return 0;
}


 

 

问题:有一个大小为n的数组A[0,1,2,…,n-1],求其中第k大的数。

该问题是一个经典的问题,在《算法导论》中被作为单独的一节提出,而且其解决方法很好的利用了分治的思想,将时间复杂度控制在了O(n),这多少出乎我们的意料,此处暂且不表。

该问题还可以变形为:有一个大小为 n的数组A[0,1,2,…,n-1],求其中前k大的数。

一字之差,原问题是“第k大”,变形的问题是“前k大”,但是平均时间复杂度却都可以控制在O(n),这不由得让人暗暗称奇。

我们先分析原问题:有一个大小为 n的数组A[0,1,2,…,n-1],求其中第k大的数。

我们先取特例,令k=1,那么就是取最大的数,只要扫描一遍数组就可以确定该值,如果k=2,则扫描两边数组就可以确定第二大的数,依此类推下去,时间复杂度是O(k*n),如果k跟n是一个数量级,那么时间复杂度就是O(n*n)了,显然不是最优的解法。

考虑分治法,难点在于如何将该问题分解为两个子问题。

快速排序最基础的一步:

       随机取某一个数x,将其与数组末尾元素交换,然后将比其小的数交换至前,比其大的数交换至后。

这一步使某一数组的快速排序问题分解成两个子数组的排序问题,现在我们就依此来解决取第k大的数这个问题。

设数组下表从0开始,至n-1结束。

1、 随机取某个数,将其与数组末尾元素交换。

a)        idx=rand(0,n-1);生成[0,n-1]间的随机数。

b)        Swap(array[idx], array[n-1]);

2、 用末尾元素x,将比x小的数交换至前,比x大的数交换至后,并返回此时x在数组中的位置mid。

3、 如果mid==n-k,那么返回该值,这就是第k大的数。

如果mid>n-k,那么第k大的数在左半数组,且在左半数组中是第k-(n-mid)大的数。

如果mid<n-k,那么第k大的数在右半数组,而且仍然是第k的数。

方法一:

方法二:

int random_partion(int *p,int n)
{
	int idx=rand()%n;
	swap(p[idx],p[n-1]);
	int i=-1;
	int j=0;
	for(j=0;j<n;j++)
	{
		if(p[j]<p[n-1])
		{
			swap(p[++i],p[j]);
		}
	}
	swap(p[++i],p[n-1]);
	return i;
}
int getMaxK(int *p,int n,int k)
{
	int mid;
	if(k<=0)
		return -1;
	if(n<k)
		return -1;
	mid=random_partion(p,n);
	if(mid==n-k)
		return p[mid];
	else if(mid>n-k)
	{
		getMaxK(p,mid,k-n+mid);
	}
	else
	{
		getMaxK(p+mid+1,n-mid-1,k);
	}
}
int main()
{
	int p[]={0,1,2,3,4,5,6,7,8,19,23,111,22};
	cout<<getMaxK(p,sizeof(p)/sizeof(int),4);
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值