当年, 某公司实习生招聘, 在第一轮面试中, 我遇到这样一个题目: 有10000个互不相同的无序整数, 如何找到数组的median值(也就是说: 有一半数比它小, 另一半数比它大)。
当时, 我傻不愣登地说: 用O(N*lgN)的排序啊, 比如: 快速排序,堆排序, 归并排序, 希尔排序。 然后面试官提示我:只需要找出median, 而你排序却做了大量的无用功, 还有没有优化算法? 我当时确实没有想到比较好的方法。
直到后来看到《算法导论》上的"顺序统计量"才明白, 原来:在平均情况下, 第i个最小的数可以在线性时间内获得。
直接看程序吧:
#include <iostream>
using namespace std;
// 可以以线性时间来实现一个划分(如果有的朋友不懂什么是划分, 请先学习快速排序下思想)
// 快速排序的程序我早已写过, 请见:http://blog.csdn.net/stpeace/article/details/8096540
int partition(int a[], int low, int high) //划分
{
int pivotKey = a[low];
while(low < high)
{
while(low < high && a[high] >= pivotKey)
{
high--;
}
a[low] = a[high];
while(low < high && a[low] <= pivotKey)
{
low++;
}
a[high] = a[low];
}
a[low] = pivotKey; //恢复
return low;
}
// 找第i个最小的值
int select(int *a, int low, int high, int i)
{
if(low == high) // 只有一个元素
{
return a[low];
}
int pivot = partition(a, low, high); // 划分, 并返回划分的位置
int k = pivot - low + 1; // low为第1个, pivot为第(pivot - low + 1)个
if(k == i) // 刚好划出来了
{
return a[pivot];
}
if(i < k) // 缩小范围进行递归
{
return select(a, low, pivot - 1, i);
}
return select(a, pivot + 1, high, i - k); // 缩小范围进行递归
}
int main()
{
int a[] = {11, 6, 5, 26, 13, 22, 0, 4, 7, 3, 9};
int n = sizeof(a) / sizeof(a[0]);
cout << "media:";
cout << select(a, 0, n - 1, (n + 1) / 2) << endl; // median值
int i = 1;
for(i = 1; i <= n; i++)
{
cout << select(a, 0, n - 1, i) << " "; // 第i个最小值
}
cout << endl;
return 0;
}
结果为:
media:7
0 3 4 5 6 7 9 11 13 22 26
大家可以好好理解一下, 下次遇到类似问题, 就得心应手了。