这个题目比较经典,本文讨论几个常见方法:
方法一:将数据读入数组然后排序,当然这种办法最容易理解,但事件复杂度不忍直视:O(N^2)。同时,当面试时被问及海量数据,这个则不能满足,因为可能内存中根本放不下这么多数据,同时计算机会运行超时。
方法二:在不断判断数据过程中只保存一个容量为K的数组,将这个数组进行排序,最后输出就可以了。如果只是简单地实现,则时间复杂度是O(N *K)。
若采用二叉树实现则在O(logK)时间内完成删除操作,最终时间复杂度是O(N*logK)。考虑堆、快排或者红黑树,若采用红黑树,则只需要调用STL中的multiset。
利用最大堆实现:
堆排序:
获得左子孩子:
int leftchild(int i )
{
return(2*i+1);
}
template<typename Comparable>
“下滤”调整堆:
void gercdown(vector <Comparable> &a, int i, int n)
{
int child = leftchild(i);
Comparable temp = a[i];
for(; leftchild(i) < n; i = child)
{
child = leftchild(i);
if (child < n -1&&a[child] < a[child + 1])
{
child++;
}
if(temp < a[child])
a[i] = a[child];
else
break;
}
a[i] = temp;
}
建堆:
template<typename Comparable>
void HeapBuild(vector <Comparable> &a)
{
for(int i = a.size()/2 - 1; i >= 0; i--)
{
gercdown(a, i, a.size());
}
}
获得最大的K个数:
void HeapDelete(vector <Comparable> &a, int length)
{
for(int j = length; j > 0; j--)
{
int len = j -1;
swap(a[0],a[len]);
gercdown(a, 0, len);
}
}
在数组a中倒数第K个数就是第K大的数。
利用快速排序的方法称为快速选择,不同于快排的地方是:若将集合S划分为S1和S2,则:
若K = 1 + S1,那么key值就是第K个最大元;
若K>=S1,那么第K个最大元必然在S2中,否则在S1中。
若能够根据以上规则判断出范围则不需要对另一半进行排序。
快速选择代码:
template<typename Comparable>
int median3(vector<Comparable> &a, int left, int right)
{
int center = (left + right) / 2;
if(a[center] < a[left])
swap(a[left],a[center]);
if(a[center] > a[right])
swap(a[right],a[center]);
if(a[center] < a[left])
swap(a[center], a[left]);
swap(a[center], a[right - 1]);
return a[right - 1];
}
template<typename Comparable>
void quickSelect(vector<Comparable> &a, int left, int right, int k,int N)
{
Comparable key = median3(a ,left, right);
int i = left, j = right - 1;
while(1)
{
while(a[++i] < key){}
while(a[--j] > key){}
if(i < j)
{
swap(a[i], a[j]);
}
else
break;
}
swap(a[i], a[right - 1]);
if(k > N - i)
quickSelect(a, left, i - 1, k, N);
else if(k < N -i)
quickSelect(a, i+1, right, k, N);
else
{
cout<<a[i];
return;
}
}
若使用multimet则直接调用STL的函数即可
综上所述,还是堆最方便,而且便于理解,当然实际运行的话,当数据量很大时,快排效果好于堆(当key值取得理想)
细节过几天记得总结一下!!!!
似乎还有个基数排序方法,但是空间复杂度太高了O(10N),时间复杂度是O(N)