TopK—返回第K小(大)的數字
給定一個數組,返回數組中第K小(或者第K大)的數字,比較經典的TopK問題。
快排選擇
基於快排的思想,partition函數每次將切分元素放置到正確位置pos:當我們需要的K>pos時,代表我們需要再從(pos,right]中查找;當K
最小(大)堆
基於堆排序的思想,當我們需要查找第K小(大)的數字時,我們將數組前K個元素構建成最大(小)堆,則堆頂元素是前K個數字中最大的(也就是第K小的),再從數組第K+1個元素開始遍歷,如果元素小(大)於當前堆頂元素,則交換到堆頂,再調整堆,維護最大堆,當遍歷完數組後,堆頂元素就是整個數組中第K小的元素。
代碼:
#include<iostream>
using namespace std;
void exchange(int a[],int i,int j);
int partition(int a[],int lo,int hi);
int quick_selector(int a[],int k,int size)
{
k--; //第k小的數索引爲k-1
int lo = 0,hi = size-1;
int pos = partition(a, lo, hi);
while(pos!=k) {
if(pos<k) lo = pos+1; //如果當前位置小於k,則在該位置右側繼續
if(pos>k) hi = pos-1; //如果當前位置大於k,則在該位置左側繼續
pos = partition(a, lo, hi);
}
return a[k];
}
int partition(int a[],int lo,int hi)
{
if(lo>hi) return NULL;
int i = lo-1,j = hi;
int pivot = (lo+hi)/2; //將首、中、尾三處值的中間大小值作爲pivot
if(a[pivot]<a[lo]) exchange(a,pivot,lo);
if(a[pivot]<a[hi]) exchange(a,pivot,hi);
if(a[lo]>a[hi]) exchange(a,lo,hi);
while(true) {
while(a[++i]<a[hi]) if(i==hi) break;
while(a[--j]>a[hi]) if(j==lo) break;
if(i>=j) break;
exchange(a,i,j);
}
exchange(a,i,hi); //尾處作爲pivot,則應該跟指向大值的指針i交換;如果首處作爲pivot,則應該跟指向小值的j交換
return i;
}
void sink(int a[],int i,int k);
int heap_selector(int a[],int k,int size)
{
if(k<=0) return 0;
for(int i=k/2-1;i>=0;--i) { //構建k個元素的最大堆
sink(a,i,k-1);
}
for(int i=k;i<size;++i) { //從第k+1個元素開始替換堆中元素
if(a[i]<a[0])
exchange(a,i,0);
sink(a,0,k-1); //調整堆,保持最大堆
}
return a[0];
}
void sink(int a[],int i,int k) //向下調整
{
while(2*i+1<=k) {
int j = 2*i+1;
if(j<k && a[j]<a[j+1]) j++;
if(a[i]>a[j]) break;
exchange(a,i,j);
i = j;
}
}
void exchange(int a[],int i,int j)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
int main()
{
int a[] = {7, 8, 9, 54, 6, 4, 11, 1, 2, 33};
cout<<quick_selector(a, 10, sizeof(a)/sizeof(int))<<endl;
cout<<heap_selector(a, 10, sizeof(a)/sizeof(int))<<endl;
return 0;
}