面試南大夏令營的同學說被問到了這個問題,我的第一反應是建小頂堆,但是據他說用的是快排的方法說是O(n)的時間復雜度,
但是后來經過我的考證,這個算法在最壞的情況下是O(n^2)的,但是使用堆在一般情況下是O(n+klogn),最壞的情況是O(nlogn)
把兩種方法整理一下,我還是推薦使用小頂堆的方法,因為它不僅可以求第K大,還可以求前K大。。。
一。快排。借用了快排的partition思想,其實是一種分治的方法。對於一個partition,他左邊的數小於他,右邊的數全大於他
那么:
1.如果他本身就是第K個數,直接返回。
2.如果他的下標是比K大的某個數,那么第K小數肯定出現在他左邊。那么到partition的左邊遞歸地求解
3.如果他的下標是比K小的某個數,那么第K小數肯定出現在他右邊。那么到partition的右邊遞歸地求解
唯一需要注意的地方是,要注意在遞歸的過程中,第K小數是一個相對值,即相對於區間[l,r]的左邊界l;
#include <bits/stdc++.h>
using namespace std; const int maxn = 1000; int a[maxn]; int n = 10,k = 6; int part(int low, int high) { int pivot = a[low]; while(low < high){ while(low < high&&a[high] >= pivot) high--; a[low] = a[high]; while(low < high&&a[low] <= pivot) low++; a[high] = a[low]; } a[low]=pivot; return low; } int quicksort(int l, int r, int k){ int pos = part(l,r); if(pos - l + 1 == k) return a[pos]; else if(pos - l + 1> k) return quicksort(l,pos-1,k); else return quicksort(pos+1,r,k-(pos-l+1)); } int main() { srand((unsigned)time(NULL)); for(int i = 0; i < n; ++i){ a[i] = rand()%(n<<1); } for(int i = 0; i < n; ++i) printf("%d ",a[i]); printf("\n"); int x = quicksort(0,n-1,k); printf("%d\n", x); }
二。小頂堆
使用堆可以求出最小的元素,通過不斷地彈出元素,就可以找到第K大的元素
#include <bits/stdc++.h>
using namespace std; const int maxn = 1000; int a[maxn]; int n,k; void adjust_heap(int rt,int n) { int x=a[rt],pos = rt; while(rt <= n){ int lc = rt << 1,rc = lc|1; if(lc <= n&&a[lc] < x) pos = lc; if(rc <= n&&a[rc] < a[pos]) pos = rc; if(pos == rt) break; a[rt] = a[pos]; rt = pos; } a[pos] = x; } int search_k() { for(int i = n/2;i >= 1; --i){//建堆的復雜度是O(n)的 adjust_heap(i,n); } int sz = n; for(int i = 1; i < k; ++i){//每次彈出要向下調整,調整K次,復雜度為O(Klogn) a[1] = a[sz--]; adjust_heap(1,sz); } return a[1]; } int main() { srand((unsigned)time(NULL)); scanf("%d%d",&n,&k); for(int i = 1; i <= n; ++i){ a[i] = rand()%(n<<1); } for(int i = 1; i <= n; ++i) printf("%d ",a[i]); printf("\n"); int x = search_k(); printf("%d\n", x); }