寻找无序数组中第K大的数

问题叙述:

从array[1, n]这n个数中,找出第k大的数。

输入:

5
3 1 2 4 5
2

输出:

4

问题思路:

把第一个数设为基数e,并将剩下的数划分为两个集合:比e大或相等的数的集合为S1,比e小的数的集合为S2。
如果S1大小大于等于k,说明第k大的数属于S1这个集合;如果S1大小小于k,说明k在S2中;如果k=S1,说明找到了第k大的数。
这样下来,我们把在原来的数组中找出第k大的数分成了好几个子问题,就可以使用递归的方法来解决问题。

在当前递归中,我们把最左边的数设为l,最右边的数设为r。
开一个死循环来划分集合。在划分集合的过程中,设置一个变量left寻找第一个比基数小的数,right寻找从右往左第一个比基数大的数,然后交换s[left]和s[right],这样就保证数组中l到left和right到r符合我们的要求。继续划分,直到left和right相遇。
接着,讲s[l]和s[left-1]交换,这样基数e左边都比e小,右边都比e大。(left之前和right相遇了,而且之前找基准的条件中left移动到比e大时才截止,所以left-1才是S1的右边界)
以下是划分集合的代码:

int e = s[left]; //取首位为基数
int l = left, r = right;

while (1)    {    //序列中比基数大的排左边,小的排右边
    while ((left <= right) && (e <= s[left]))
        left++;
    while ((left < right) && (e > s[right]))
        right--;
    if (left < right)    {
        Swap(&s[left], &s[right]);
    } else    {
        break;
    }
}
Swap(&s[left - 1], &s[l]); //将基数换到两集合之间

 

之后就是怎么递归的问题了。 划分完成后,k就可以成为该进行到哪个集合中继续解决子问题的标准。
left-l-1如果大于等于k,即k在S1中,那么就要在S1中进行;如果left-l-1,那么在S2中进行;如果等于k-1,说明e刚好是第k大的数,返回。

完整函数如下:

int findKthLargest(int s[], int k, int left, int right)	{
	int e = s[left]; //取首位为基数
	int l = left, r = right;

	while (1)	{	//序列中比基数大的排左边,小的排右边
		while ((left <= right) && (e >= s[left]))
			left++;
		while ((left < right) && (e < s[right]))
			right--;
		if (left < right)	{
			Swap(&s[left], &s[right]);
		} else	{
			printf("%d %d\n", left, right);
			break;
		}
	}
	Swap(&s[left - 1], &s[l]); //确保比基数小的数在比基数大的数左边
	if ((left - l - 1) >= k)	{//left - l - 1代表了比基数小的集合的大小
		return findKthLargest(s, k, l, left - 2);
	} else if ((left - l - 1) < k - 1)	{//左边集合大小小于k-1那么表明第k大的数在右边的集合
		return findKthLargest(s, k - (left - l - 1) - 1, left, r);
	} else	{//如果刚好相等那么说明找到了第k大的数
		return e;
	}
}
posted @ 2023-04-22 16:08  王旁青头戋五一  阅读(86)  评论(0编辑  收藏  举报