在乙個陣列中找到第k小的數(線性時間選擇)
在這一部分,我們討論的題目為元素選擇問題。這類題目的一般問法為:給定線性序集中n個元素和乙個整數k,1 <= k <= n,要求找出這n個元素中第k小的元素,如(1,4,3,2,5)中第一小的元素就是1,第5小的元素就是5,第2小的元素就是2。
在某些特殊情況下,很容易設計出解選擇問題的線性時間演算法。如:當要選擇最大元素或最小元素時,顯然可以在o(n)時間完成。如果k <= n/logn,通過堆排序演算法可以在o(n + klogn) = o(n)時間內找出第k小元素。當k >= n - n/logn時也一樣。
一般的選擇問題,特別是中位數的選擇問題似乎比最小(大)元素要難。但實際上,從漸近階的意義上,它們是一樣的。也可以在o(n)時間完成。
下面我們用兩種方法進行求解。
第一種:分治演算法randomizedselect
思想:呼叫了隨機劃分函式randomizedpartition,所以這個分治演算法也是乙個隨機化的演算法。
通過將其劃分,我們逐漸可以縮小查詢的範圍進行查詢。
時間複雜度:一般情況下為o(n),最壞情況下,數字有序且找最大的數,則為o(n)
**:
#include#include#include#include#include#includeusing namespace std;
templateint partition(type *ar,int left,int right)
templatetype selectk(type *ar,int left,int right,int k)
第二種:中位值的中位數法,類似於第一種方法,但是在最壞情況下也可以在o(n)時間內完成選擇任務的演算法select。,使得按這個基準所劃分出的2個子陣列的長度都至少為原陣列長度的ε倍(0
步驟:
①:將n個輸入元素劃分成n/5(向下取整)個組,每組5個元素,最多隻可能有乙個組不是5個元素。用任意一種排序演算法,將每組中的元素排好序,並取出每組的中位數,共n/5(向下取整)個。
②:遞迴呼叫select來找出這n/5(向下取整)個元素的中位數。如果n/5(向下取整)是偶數,就找它的2個中位數中較大的乙個。以這個元素作為劃分基準。
畫圖示意:
例如:按遞增順序,找出下面29個元素的第18個元素:
8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,54,16,5,41,7,23,22,46,29
(1) 這些元素可以分為(29)/5 == 5組(向下取整),並且排序。
分為這5組: (8,31,60,33,17),(4,51,57,49,35),(11,43,37,3,13),(52,6,19,25,32),(54,16,5,41,7);
排序結果:
(2) 提取每一組的中位數元素,依次放到陣列最前面並排序,構成集合;
(3) 將放到最前面的各個小組的中位數傳入遞迴演算法中求取中位數的中位數,得到m=25;
(4) 根據m=25, 呼叫快速排序,先找到值為25的數字置,再將其與第乙個值進行交換,以此為基準再進行正常的快速排序,最終成功把29個元素劃分為了3個子陣列(按原有順序)
(5) 由於p組有13個元素,q組有1個元素,k組有18個元素,所以我們要找的第18小元素肯定不在p組和q組內,所以放棄p,q,使k=18-13-1=4,對r遞迴地執行本演算法;
(6) 將r劃分成3(floor(15/5))組:,,
(7) 求取這3組元素的中值元素分別為:,這個集合的中值元素是43;
(8) 根據43將r劃分成3組: ,,
(9)依次這樣尋找下去,我們可以發現,幾乎每次都是對半分,接近於二分法,所以效率很高,當然這個陣列中中位數沒有重複值,不過如果有,那麼我們將中位數的重複值集中在中位數周圍,如果這樣的元素m >= 1,則加上一步判斷:如果j <= k <= j+m-1(j為基準前陣列的個數),則不必再進行遞迴,直接return 基準值即可,當然,這個陣列中位數是沒有重複值的,所以可以很快找到第十八小元素為33。
時間複雜度:設陣列長度為n
當n<75時,演算法select所用的計算時間不超過某一常數c1,所以直接呼叫其他排序演算法即可。
當n≥75時,for迴圈執行n/5次,每次用時為某一常數;select找中位數的中位數,由於長度為原長度的1/5,所以用時可記為t(n/5);劃分以後所得到陣列至多有3n/4個元素,用時記為t(3n/4)。
由此,我們得到t(n)的遞迴式為:
解此遞迴式可得t(n) = o(n)。
由於我們將每一組的大小定為5,並選取75作為是否作遞迴呼叫的分界點(大於75使用該演算法)。這2點保證了t(n)的遞迴式中2個自變數之和n/5+3n/4=19n/20=εn,0
注意:
①:設中位數的中位數是x,比x小和比x大的元素至少3*(n-5)/10個,原因:3*(n/5-1)*1/2
②:而當n≥75時,3(n-5)/10≥n/4所以按此基準劃分所得的2個子陣列的長度都至少縮短1/4,也就是說,長度最長為原長度的3/4。
如圖,劃分的左上部分肯定是要比x小的(大概佔1/4)右下部分是肯定比x大的(大概佔1/4)左下和右上不確定,就算這兩部分同時不比x小或比x大,在極端情況下劃分成的子區間也能至少縮短1/4!
**:
#include#include#include#include#includeusing namespace std;
void bubblesort(int a,int p,int r)
}swap(a[p+1],a[s+2]);//交換每組中的中位數到前面
}//(r-p-4)/5表示組數-1,則[p,p+(r-p-4)/5]的區間長度等於組數
int x=select(a,p,p+(r-p-4)/5,(r-p-4)/10);//求中位數的中位數
int i=partition(a,p,r,x),j=i-p+1;
if(k<=j)return select(a,p,i,k);
else return select(a,i+1,r,k-j);
}
在乙個陣列中找到等於某個數的組合
方法1 include include include include include includeusing namespace std void getresult vectora,int result include include using namespace std typedef v...
在乙個陣列中找到等於某個數的組合
方法1 include include include include include includeusing namespace std void getresult vectora,int result include include using namespace std typedef v...
在有序旋轉陣列中找到乙個數
題目 有序陣列arr可能經過一次旋轉處理,也可能沒有,且arr可能存在重複的數。例如,有序陣列 1,2,3,4,5,6,7 可以旋轉處理成 4,5,6,7,1,2,3 等。給定乙個可能旋轉過的有序陣列arr,再給定乙個數num,返回arr中是否含有num。public class getnum in...