1. 程式人生 > >用O(lgn)時間求出兩個已排序陣列的中位數

用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;
}