中位数和顺序统计量
在一个由n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。一个中位数是它所属集合的“中点元素”。当n为奇数时,中位数是唯一的,位于i=(n+1)/2处;当n为偶数时,存在两个中位数,分别位于i=n/2和i=n/2+1处。如果不考虑n的奇偶性,中位数总是出现在i=⌊(n+1)/2⌋处(下中位数)和i=⌈(n+2)/2⌉(上中位数)。
1、最小值和最大值
在一个有n个元素的集合中,需要做n-1次(上界)比较才能找到最小值或最大值。
int MiniValue(const int *A, int len)
{
int minValue = A[0];
for (int i = 1; i < len; ++i)
{
if (A[i] < minValue)
{
minValue = A[i];
}
}
return minValue;
}
那么如何同时找到最小值和最小值呢?
比较简单的思路是:只要分别独立找出最大值和最小值,各需要n-1次比较,共需2n-2次比较。下面给出一个算法,只需要最多3⌊n/2⌋次比较就可以同时找到最大值和最小值。
思路是:记录已知的最大值和最小值,对输出元素成对地进行处理。
(1)首先,将一对输入元素相互比较,然后将较小的与当前最小值比较,较大的与当前最大值比较,这样每对元素共需3次比较。
(2)如果n是奇数,将最小值和最大值的初值都设为第一个元素的值,然后成对地处理余下的元素;如果n是偶数,就对前两个元素做一次比较,决定最大值和最小值的初值,然后成对地处理余下的元素。
1 void MinMaxValue(const int *A, int len, int &minValue, int &maxValue)
2 {
3 int i, tmpMin, tmpMax;
4
5 if (len % 2 == 0 && len != 0)
6 {
7 minValue = A[0] < A[1] ? A[0] : A[1];
8 maxValue = A[0] + A[1] - minValue;
9 i = 2;
10 }
11 else
12 {
13 minValue = A[0];
14 maxValue = A[0];
15 i = 1;
16 }
17
18 for (; i < len; i += 2)
19 {
20 if (A[i] < A[i + 1])
21 {
22 tmpMin = A[i];
23 tmpMax = A[i + 1];
24 }
25 else
26 {
27 tmpMin = A[i + 1];
28 tmpMax = A[i];
29 }
30
31 if (tmpMin < minValue)
32 {
33 minValue = tmpMin;
34 }
35
36 if (maxValue < tmpMax)
37 {
38 maxValue = tmpMax;
39 }
40 }
41 }
如果n是奇数,共进行3⌊n/2⌋次比较。如果n是偶数,共进行3(n-2)/2+1=3n/2-2次比较。
2、期望为线性时间的选择算法
下面介绍一种解决选择问题的分治算法。
#include <iostream>
using namespace std;
int RandomizedPartition(int *A, int low, int high)
{
int key = A[high], tmp;
int i = low - 1, j;
for (j = low; j < high; ++j)
{
if (A[j] <= key)
{
i = i + 1;
tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
}
A[high] = A[i + 1];
A[i + 1] = key;
return i + 1;
}
int RandomizedSelect(int *A, int low, int high, int i)
{
if (low == high)
{
return A[low];
}
int q = RandomizedPartition(A, low, high);
int k = q - low + 1;
if (i == k)
{
return A[q];
}
else if (i < k)
{
return RandomizedSelect(A, low, q - 1, i);
}
else
{
return RandomizedSelect(A, q + 1, high, i - k);
}
}
//test
int main()
{
int A[] = {10, 9, 5, 4, 3, 2, 1, 8, 7, 6};
for (int i = 1; i <= 10; ++i)
{
cout << RandomizedSelect(A, 0, 9, i) << ' ';
}
cout << endl;
return 0;
}
说明:
(1)RandomizedSelect的最坏运行时间为
,因为每次划分时可能总是按余下的元素中最大的来进行划分,而划分操作需要
时间。
(2)与快速排序不同的是,快速排序会递归处理划分的两边,而RandomizedSelect只处理划分的一边,这一差异体现在性能上就是快速排序的期望运行时间为
,而RandomizedSelect的期望运行时间为
。
3、最坏情况为线性时间的选择算法
下面介绍一个最坏情况运行时间为O(n)的选择算法Select。
步骤1:将输入数组的n个元素划分为⌊n/5⌋组,每组5个元素,且至多只有一组由剩下的n mod 5个元素组成。
步骤2:寻找这⌈n/5⌉组中每一组的中位数:首先对每组元素进行插入排序,然后确定每组有序元素的中位数。
步骤3:对步骤2中找出的⌈n/5⌉个中位数,递归调用Select以找出其中位数x。
步骤4:利用修改过的Partition版本,按中位数的中位数x对输入数组进行划分。
步骤5:k为划分的低区元素个数+1。如果i=k,返回x;如果i<k,则在低区递归调用Select来找出第i小的元素;如果i>k,则在高区递归查找第i-k小的元素。
说明:Select算法通过对输入数组的递归划分来找出所需元素,在该算法中能够保证得到对数组的一个好的划分。Select使用的也是快速排序的确定性划分算法Partition,做的修改是将划分的主元也作为输入参数。

浙公网安备 33010602011771号