线性时间选择
题目:
给定线性序集中n个元素和一个整数k(1<=k<=n),要求找出这n个元素中第k小的元素,即如果将这n个元素依其线性序排列时,排在第k个位置的元素即为要找的元素。当k=1时,就是要找最小元素;当k=n时,就是要找最大元素;当k=(n+1)/2时,称为找中位数。
分析:
可以模仿快速排序,与快速排序不同的是,它只对划分出的子数组之一进行递归处理。为了使递归树更加对称,提高算法效率,可以采用快排中的随机分区法 RandomizedPartition
来实现。在执行完随机分区后,原数组 a[p:r]
被划分成两个子数组 a[p:i]
和 a[i+1:r]
。如果 k>i
则说明在右半分区, 如果 k<=i
则说明在左半分区。确定好目标元素所在分区后,仅需对该目标分区进行递归分治即可。
实现:
- 边界条件为
left==right
时,说明k所在分区都已排序完成,返回a[left]
即可。
if(left==right){
return a[left];
}
- 通过随机分区
randomizedPartition
获取基准位置pivot
。
// 随机分区
int randomizedPartition(int a[], int left, int right){
int randPivot = rand() % (right - left) + left;
swap(a, left, randPivot);
int p = left;
int pivot = a[left];
while(left < right){
while(left < right && a[right] >= pivot){
right--;
}
while(left < right && a[left] <= pivot){
left++;
}
swap(a, left, right);
}
swap(a, left, p);
return left;
}
- 通过比较基准位置
pivot
和目标位置k
的大小,来确定要往哪个分区进一步划分。当k <= pivot
时往右边部分再次递归;当k > pivot
时往左边部分再次递归。
if(k <= pivot){
// 在右边部分
lineTimeSelect(a, left, pivot, k);
} else {
// 在左边部分
lineTimeSelect(a, pivot+1, right, k);
}
完整代码:
#include<stdio.h>
/*
主算法
a: 数组
left: 左边界
right: 有边界
k: 第几个元素
*/
int lineTimeSelect(int a[], int left, int right, int k){
if(left==right){
return a[left];
}
int pivot = randomizedPartition(a, left, right);
if(k <= pivot){
// 在右边部分
lineTimeSelect(a, left, pivot, k);
} else {
// 在左边部分
lineTimeSelect(a, pivot+1, right, k);
}
}
// 随机分区
int randomizedPartition(int a[], int left, int right){
int randPivot = rand() % (right - left) + left;
swap(a, left, randPivot);
int p = left;
int pivot = a[left];
while(left < right){
while(left < right && a[right] >= pivot){
right--;
}
while(left < right && a[left] <= pivot){
left++;
}
swap(a, left, right);
}
swap(a, left, p);
return left;
}
// 交换
void swap(int a[], int left, int right) {
int temp = a[right];
a[right] = a[left];
a[left] = temp;
}
void main(){
int a[10] = {4,7,3,1,2,6,6,9,8};
int length = sizeof(a) / sizeof(int);
int ak = lineTimeSelect(a, 0, length-1, 7);
printf("%d\n", ak);
return;
}