描述: 寻找一个数组中第i 小的元素
很容易联想到最小值问题,O(n)内解决,但好像无法直接应用,这时最直观的就是先排序,再查找O(nlgn), 仔细想来,一旦完成排序意味者所有的元素都可以找到,而我们只需要寻找一个(常数个),显然解决方案是一个“更加泛型”的算法,这样就会出现“杀鸡用了牛刀—大材小用”,如果改用“鸡刀”显然复杂度会有所降低。
如何降低呢?与排序算法一样,就是减少不必要的比较,此时几种O(nlgn)的排序算法浮现出来,merge, quick都是只对一半的元素比较,此时也可以只对一半元素比较,第一步就是确定划分,然后决定位于哪一部分
rand_select(a,start,end,i)
if(start == end)
return a[start]
p = rand_partion(a,start,end);
k = p-start+1
if(k==i) //whether the pivot is the answer
return a[p]
else if(k>i) // in the left
return rand_select(a,start,p-1,i)
else //in the right
return rand_select(a,p+1,end,i-k)
当然真实的代码中必须检测i 的合法性,分析可知,关键部分rand_partion将数据分为两部分(有点divide and conquer),然后按partion节点分为三种情况,left, current , right, 意味者每一次理想情况下只检查一半,O(n+n/2 + n/4 +…) = O(2n) = O(n).
// order statistic
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int partion(vector<int>& a, int start, int end)
{
int key = a[start];
int i = start;
int j = end;
while (i < j)
{
while (i<j && a[j]>key)
{
j--;
}
a[i] = a[j];
while (i<j && a[i]<=key)
{
i++;
}
a[j] = a[i];
}
a[i] = key;
return i;
}
int select(vector<int>& a, int start, int end, int i)
{
if (i > a.size())
return -1;
int p = partion(a, start, end);
int k = p - start + 1;
if (k == i)
return a[p];
else if (i < k)
return select(a, start, p - 1, i);
else
return select(a, p + 1, end, i - k);
}
int main(int argc, int** argv)
{
vector<int> array;
for (int i = 0; i < 10; i++)
array.push_back(rand() % 100);
cout << "old array" << endl;
for (auto val : array)
cout << val << " ";
cout << endl;
for(int i=1;i<=array.size();i++)
cout << select(array, 0, array.size()-1, i) << endl;
}