求第K小/大數這個題目經常出現,面試,考試以及OJ上都有類似的題目。
首先聲明一點,個人覺得既然是第K小(大是一樣的),那麼重複的元素就不應該算了,當然如果算了就相對簡單一些。。
最原始的解法,快排,然後取第K個數。或者是構建一個小頂堆,遍曆數組取最小的K個數,再然後還有所謂的快速選擇,有人證明了可以在O(n)時間內解決,不過我不太清楚這種演算法是否對重複元素有效(但看了代碼其實就是快排的一種應用,但個人覺得不適應於重複元素)
其實最簡單的辦法:
#include<algorithm>
sort(array,array+n);
unique(array,array+n)
直接調用庫函數排序,去重,一般的資料這樣就足夠快了。
但是學了樹狀數組,總是看人家的代碼感覺沒學到什麼,剛好前幾日曙光也問過我同樣的問題,YY了一陣子,正好用樹狀數組解決一下,別忘了,我們的樹狀數組最基本的功能哦【第i位置上村的是比左邊大的數】
好了迴歸正題。第K小(如果是大的話就是第length-K-1小)
建樹的過程必須特別注意,我們之前關於樹狀數組的題目中tree中每個元素都是有實際值得,但是這裡我們考慮的話,某些位置是空的,也就是tree的最終大小由我們所要處理的數組中最大元素的大小決定的,所以如果使用樹狀數組的話,前提就是所要處理的數組中最大值不能太大【基數排序中的要求也是類似】否則記憶體吃不消。
在這個分類總的第一篇文章是翻譯了一篇老外講的BIT,很詳細,其中有兩個部分:readSingle函數和find函數當時看懂了,但是不知道實際應用在哪了,終於找到它的實際應用地方了【嘿嘿,好開心】。
建樹的時候我們不能像之前那樣每掃描一次數組就更新一次,因為有重複元素,一旦更新重複元素的話,樹就建錯了,我們就得不到所要的結果,那麼怎麼處理呢,這時翻譯的那篇文章中提到的readSingle函數大顯神威:
建樹代碼如下:具體的已經寫在了注釋中
for(int i=0;i<len;i++){if((data[i]&1)==1){//對於基數來說,這個位置上有值就意味著有這個元素if(!tree[data[i]]){//當沒有時才允許更新數組update(data[i],1);flag[data[i]]=1;}}else{/*對於偶數位置來說,以為偶數位置儲存的都是一段和,不能僅僅通過當前的值來判斷,這時需要還原出原始的tree[i]了,還原就是readSingle函數,如果還原出的值為0,就意味著這個值沒有,那麼我們需要更新,如果有的話,那麼就跳過更新的過程,進行下一個元素的考察*/int tmp=readSingle(data[i]);if(!tmp){update(data[i],1);flag[data[i]]=1;}}}
樹建好了,工作就完成了一大半了:
現在說一下查詢第K小的數了,輪到find函數大顯神威了,關於這個函數就不多做解釋了,不理解的可以回去看下我之前翻譯的那篇關於BIT的詳細解釋,跟著注釋以及圖就可以看明白:【唯一要說的是原版的文章中給了2種find方法,第一種是找到一個就返回,第二種就是找到最大的idx返回,但是我測試的時候發現找到一個返回就可行,但是找到最大的有問題】
//查詢第K小的數int Find_Kth(int K){int idx = 0; int bitMask=MaxVal;int num=0;while(bitMask){num++;bitMask>>=1;}bitMask=1<<(num-1);while ((bitMask != 0) && (idx < MaxVal)){int tIdx = idx + bitMask; if (K == tree[tIdx]) //找到一個就返回return tIdx; if (K >tree[tIdx]){ idx = tIdx;K -= tree[tIdx]; }bitMask >>= 1; }if (K != 0) return -1;elsereturn idx;//return 1;}
好了完整的代碼如下:我們的時間複雜度為O(nlog(MaxVal))【說明一下,readSingle函數的時間複雜度為c*log(idx),基本可以看做是常量,find_kth函數時間複雜度為O(logMaxVal),所以總的時間複雜度為O(nlog(MaxVal))】
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=110000;int n,val[N],a[N];int len,tree[N];bool flag[N];int MaxVal;int lowbit(int x){ return x&(-x);}void update(int x,int val){ while(x<=MaxVal){ tree[x]+=val; x+=lowbit(x); }}int getsum(int x){ int res=0; while(x){ res+=tree[x]; x-=lowbit(x); } return res;}int readSingle(int idx){int sum = tree[idx]; // sum will be decreasedif (idx > 0){ // special caseint z = idx - (idx & -idx); // make z firstidx--; // idx is no important any more, so instead y, you can use idxwhile (idx != z){ // at some iteration idx (y) will become zsum -= tree[idx]; // substruct tree frequency which is between y and "the same path"idx -= (idx & -idx);}}return sum;}//查詢第K小的數int Find_Kth(int K){int idx = 0; int bitMask=MaxVal;int num=0;while(bitMask){num++;bitMask>>=1;}bitMask=1<<(num-1);while ((bitMask != 0) && (idx < MaxVal)){int tIdx = idx + bitMask; if (K == tree[tIdx]) return tIdx; if (K >tree[tIdx]){ idx = tIdx;K -= tree[tIdx]; }bitMask >>= 1; }if (K != 0) return -1;elsereturn idx;//return 1;}int main(){//不考慮重複的數int data[10]={1,2,2,2,2,2,2,3,10,5};MaxVal=10;len=10;//update(1,1);/*掃描數組,對於位置為奇數和偶數分開處理flag數組記錄有哪些元素,因為我們最終找到的值是一個和,仍然需要進行下一步處理即向前搜尋找到一個存在的值,那麼如果再用readSingle函數太麻煩,用一次之後就用flag標記一下就可以下次用了*/for(int i=0;i<len;i++){if((data[i]&1)==1){//對於基數來說,這個位置上有值就意味著有這個元素if(!tree[data[i]]){//當沒有時才允許更新數組update(data[i],1);flag[data[i]]=1;}}else{/*對於偶數位置來說,以為偶數位置儲存的都是一段和,不能僅僅通過當前的值來判斷,這時需要還原出原始的tree[i]了,還原就是readSingle函數,如果還原出的值為0,就意味著這個值沒有,那麼我們需要更新,如果有的話,那麼就跳過更新的過程,進行下一個元素的考察*/int tmp=readSingle(data[i]);if(!tmp){update(data[i],1);flag[data[i]]=1;}}}//我們取得了第4小的值,但是這個位置到底有沒有值,需要通過flag數組來判斷int res=Find_Kth(4);while(!flag[res])res--;printf("%d",res);return 0;}