1.线性时间选择:找中位数
元素选择问题的一般提法是∶给定线性序集中n个元素和一个整数k,1<= k<=n,要求找出这n个元素中第k小的元素,即如果将这n个元素依其线性序排列时,排在第k个的元素即为要找的元素。当k=1时,就是要找最小元素;当k= n时,就是要找最大元素;当k = (n+1)/2时,称为找中位数。
在某些特殊情况下,很容易设计出解选择问题的线性时间算法。例如,找n个元素的最小元素和最大元素显然可以在O(n)时间完成。如果k <= n/logn,通过堆排序算法可以在O(n + klogn) =O(n)时间内找出第k小元素。当k>= n --- n/logn时也一样。
一般的选择问题,特别是中位数的选择问题似乎比找最小元素要难。但事实上,从渐近阶的意义上看,它们是一样的。一般的选择问题也可以在O (n)时间内得到解决。下面要讨论解一般的选择问题的分治算法。该算法实际上是模仿快速排序算法设计出来的。其基本思想也是对输入数组进行递归划分。与快速排序算法不同的是,它只对划分出的子数组之一进行递归处理。
int Partition(int* nums, int left, int right)//定义一个划分函数
{
int i = left, j = right;
int tmp = nums[i]; //以nums[i]作为划分基准
while (i < j)
{
while (i<j && nums[j]>tmp) --j;
if (i < j)nums[i] = nums[j];
while (i < j && nums[i] <= tmp) ++i;
if (i < j)nums[j] = nums[i];
}
nums[i] = tmp;
return i;
}
int SelectK(int* nums, int left, int right, int k)
{
if (left == right && k == 1) return nums[left];//数组中仅有一个元素,且k=1,直接返回
int i = Partition(nums, left, right); //划分基准的数组下标
int j = i - left + 1; //划分基准是第j小元素
if (k == j) return nums[i]; //k=j时,恰好就是划分基准,直接返回
if (k < j) return SelectK(nums, left, i-1, k);//说明在划分基准的左面
else return SelectK(nums, i + 1, right, k - j);//说明在划分基准的右面
}
int SelectKMin(int* nums, int n, int k)
{
if (nullptr == nums ||n<1|| k<1 || k>n)return -1;
return SelectK(nums, 0, n - 1, k);
}
int main()
{
int arr[] = { 56,23,78,45,90,89,12,34,67,89,100 };
int n = sizeof(arr) / sizeof(arr[0]);
for (int k = 1; k <= n; k++)
{
int kmin = SelectKMin(arr, n, k);
cout << k << " : " << kmin << endl; //我们直接打印第几小的数
}
return 0;
}
划分以后的数组: