用O(lgn)時間求出兩個已排序陣列的中位數
相關問題:
設 x[1..n]和Y[1..n]為兩個陣列,每個都包含n個已排序的數。給出一個求陣列X和Y中所有2n個元素的中位數的O(lgn)時間的演算法。
思考過程:
開始我想把兩個陣列X與Y放入到一個數組Z中,對Z進行排序,這樣Z的中位數易求。但是有2點原因使得這種做法不可行,1.是將2陣列放入到1個數組中的過程會產生O(n)的時間,所以不滿足題意。2如果對新陣列Z排序,那麼最少需要線性O(n)時間進行排序,也不滿足O(lgn)這個時間的需求。然後我又試圖用最壞時間線性選擇子程式,但是馬上想到,這樣就是O(n),不是O(lgn)的時間了,所以用不上書中9.3節的內容。
後來我想到如果要想達到O(lgn)這個數量級的查詢,那麼可以選擇二分法進行查詢,首先判斷陣列X(Y)是否所有數都小於陣列Y(X),如果假設成立,那麼直接返回陣列X最後一個數即可。如果集合X與Y之中的數存在集合X的某元素<集合Y的某元素,同時集合X的某元素>集合Y的某元素,那麼進入遞迴。具體遞迴過程請見程式碼註釋。(PS:其實我認為這個問題就是考察對二分法的靈活運用。)
具體步驟:
開始隨機化陣列,然後對陣列進行排序。因為原題的意思是要想進行O(lgn)時間的選擇,那麼前提是兩陣列已排序,所以不能將排序過程算在總的選擇時間裡面。當然你也可以選擇自己設定陣列元素值,使其初始化時便已有序。
Two_groups_array_Median函式具體步驟是:
step 1:判斷陣列X(Y)所有數是否均小於陣列Y(X)所有數。如果小於,則直接返回中位數。程式立即結束。
step 2:判斷陣列X與Y,begin項≥end項?如果成立,則進入最後的微調與返回中位數階段。
step 3:如果step 1,2都不成立,那麼進入二分法的遞迴過程。具體過程見程式碼註釋。
#include <iostream> #include <time.h> using namespace std; const n=25; void INSERTION_SORT(int A[],int r)//利用任意一個排序演算法設定陣列為題目條件“有序”。 { int key; for (int j=1;j<=r;j++) { key=A[j-1]; int i=j-1; while (i>0&&A[i-1]>key)//a)插入排序時間複雜度O(n^2)對於長度為k的子列表都有O(k^2),則n/k個為(k^2)*n/k=O(nk) { A[i]=A[i-1]; i=i-1; } A[i]=key; } } void Initialization(int A[],int r,int a,int b)//初始化陣列 { srand( (unsigned)time( NULL ) ); for (int i=0;i<n;i++) { A[i]=rand()%(a-b+1)+b; } } void Print(int A[],int r) { for (int i=0;i<r;i++) { cout<<A[i]<<" "; } cout<<endl; } int Two_groups_array_Median(int A[],int B[],int beginA,int endA,int beginB,int endB) { int p=(beginA+endA)/2;int r=(beginB+endB)/2; int t=p+r+2,flag=0;//經過大量的實驗證明(我試圖用數學歸納法證明,慚愧的是我沒有完整證明出來,如果有大牛覺的我的這個結論是錯誤的,那麼可以舉反例證明我的錯誤以待我去改正,小弟在此先感謝糾正我錯誤給我留言的人):t≈n 最多相差常數個誤差。誤差產生的原因是p和r定義時除以2後,全部向下取整了,而精確的中位數是需要有時向上取整的。 //t表示當前中位數p和r前面有多少個元素,由於陣列下標是從0開始的,所以需要+上A[0]和B[0]。 if (A[n-1]<B[0])//如果集合A與B之中的某一集合所有的數均小於另外一集合所有數,則直接返回較小陣列的最後一位即為中位數 { return A[n-1]; } else if (A[0]>B[n-1]) { return B[n-1]; } if (beginA>=endA||beginB>=endB)//如果A與B哪個先遞迴到begin項≥end項,那麼就進行下面的判斷。 { if (n-t>0) { for (int i=1;i<=n-t;i++)//對於上面註釋所說的誤差所差常數的具體數值,根據這個常數數值再求精確的中位數,否則如果沒有這個迴圈,那麼所求中位數與真實中位數要相差常數個位置 { ++p;++r; if (A[p]<B[r]) { --r; flag=1; } else { --p; flag=0; } } if (flag)//這個if-else語句是要返回A與B中較小的那個數,較小的數就是中位數。 { return A[p]; } else { return B[r]; } } else//如果n=t,無需進行微調,由於p+r+2正好等於這2n個數的中間值下標n,那麼就返回較大值,較大值正好是第n個數。 { if (A[p]<B[r]) { return B[r]; } else { return A[p]; } } } if (A[p]>B[r])//如果陣列A的中位數大於陣列B的中位數,那麼兩陣列的中位數必在陣列A當前中位數的左邊,陣列B當前中位數的右邊 { return Two_groups_array_Median(A,B,beginA,p,r,endB);//對陣列A的左半部分陣列B的右半部分進行遞迴。 } else //小於情況類似,這裡不做累述。 { return Two_groups_array_Median(A,B,p,endA,beginB,r); } } void main() { int A[n]={0},B[n]={0}; cout<<"隨機陣列A初始化。。。"<<endl; Initialization(A,n,10000,9); INSERTION_SORT(A,n);//利用插入排序設定陣列使其變為題目所說的“已排序”的陣列。 Print(A,n); cout<<"隨機陣列B初始化。。。"<<endl; Initialization(B,n,15000,1000); INSERTION_SORT(B,n); Print(B,n); cout<<"兩陣列中位數="<<Two_groups_array_Median(A,B,0,n-1,0,n-1)<<endl; }