寻找无序数组中第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;
}
}