PTA_數據結構學習與實驗指導_題解_1-3.1兩個有序序列的中位數

進階實驗1-3.1 兩個有序序列的中位數

 已知有兩個等長的非降序序列S1, S2, 設計函數求S1與S2並集的中位數。有序序列A​0​​,A​1​​,⋯,A​N−1​​的中位數指A​(N−1)/2​​的值,即第⌊(N+1)/2⌋個數(A​0​​爲第1個數)。算法

輸入樣例1:數組

5
1 3 5 7 9
2 3 4 5 6

輸出樣例1:函數

4

輸入樣例2:spa

6   
-100  -10  1  1  1  1  
-50  0  2  3  4  5

輸出樣例2:設計

1

題目:進階實驗1-3.1 兩個有序序列的中位數 (25分)3d

算法分析

 看完題目第一反應是兩個集合求並集,再排個序輸出中間的數就行了。可是看到數據量10,0000個數,時間限制是200ms。快排時間複雜度是O(nlogn),必定是會超時的。
 因此必定有一個更好的算法。
 接下來留意到題目中的序列是非降序序列,想到取各自的中位數而後比較。經過比較縮小問題規模的辦法。若是辦法有效,算法的時間複雜度應該是O(logn),知足評分要求。
 那麼下面開始驗證這個想法。code

算法驗證

思想
 咱們首先取序列S1的中位數設爲mida,再取序列S2的中位數設爲midb
 因爲序列S一、S2都是升序排列的。故S1mida左邊的數都小於mida,右邊的數都大於mida。序列S2同理。
 此時比較midamidb
 因爲mida是S1的中位數,midb是S2的中位數。故集合U=S1∪S2中,大於MAX{mida,midb}的全部數都不多是中位數。同理可得,集合中小於MIN{mida,midb}的全部數也都不多是中位數。
 經過比較mida和midb的大小,咱們把集合U劃分紅了兩個區間.
 即A=[MIN{mida,midb}的右區間, MAX{mida,midb}的左區間]∁UA
 此時問題就被簡化成了求集合A的中位數。
 然後經過不斷的二分查找,A最後必定會變成一個只有2個數的集合。那麼根據中位數的定義,此時中位數必然是min{A},即兩個數中更小的那個。blog


 咱們來模擬一下這個過程。排序

image.png

 這是序列S1,此時mida=5。遞歸

image.png

 這是序列S2,此時midb=4。

 因爲mida > midb,故此時U=S1∪S2被分紅了兩個集合,A={1,3,5}∪{4,5,6}(藍色)及∁UA(白色)。

image.png

中位數必然在集合A中。由於中位數是排序後位於數列中間,因此它應該在兩個升序子序列的中位數的中間。

 此時問題就變成了在集合A中取得中位數。白色的∁UA能夠直接拋棄。

 遞歸上述操做,咱們能夠逐步迭代集合A。

直到這一步,咱們會遇到一個問題,也是筆者遇到的一個大坑。

image.png

 此時兩個序列中的數字個數都爲偶數數,中位數爲倆數中小的那個也就是前面那個。若繼續按這種方式迭代,接下來的集合會變成這個。

image.png

因爲{3,5}中,中位數爲3,小於4,那麼接下來應該取它右邊的序列。此時會發現此序列取右邊的序列仍是{3,5}!它會形成無限遞歸或者死循環!

 因此分析到這一步咱們發現,應該是要分辨集合中數字的個數爲奇數仍是偶數來分別取子序列。最終咱們發現,除了0之外,天然數中最小的偶數是2。在序列長度爲2且升序的狀況下,中位數直接就是前面那個。
 把它擴展到4,那麼咱們發現只要拋掉首位兩個數,狀況就退化成了上述狀況。即又一次迭代了集合A。換做到題目中,即直接拋掉偶數序列中的邊界數便可。即mida或midb(兩個子序列都是偶數則兼有之)。

因此{4,5,6,1,3,5}以後的迭代出的{4,5,3,5}是不正確的!

 正確迭代方式應該拋掉{4,5,6,1,3,5}中的midamidb

 正確的集合A以下。

image.png

 最終篩選出中位數爲4。

代碼

下面給出筆者的代碼。因爲最近在複習C語言,寫的是尾遞歸的版本。

#include <stdio.h>

#define MAX_N 100000

/* 二分查找函數聲明 aleft a數組左下標,aright a數組右下標*/ 
int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright); 

int main()
{
    int n = 0, a[MAX_N] = {0}, b[MAX_N] = {0};
    scanf("%d", &n);
    for(int i = 0;i<n;i++){
        scanf("%d", &a[i]);
    }
    for(int i=0;i<n;i++){
        scanf("%d", &b[i]);
    }
    int mid = bin_search(a, 0, n-1, b, 0, n-1);
    printf("%d\n", mid);
    return 0;
}

int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright){
    int al=0, ar=0, bl=0, br=0; /* 下一步遞歸的a,b數組下標 */ 
    /* indexa a數組中位數下標 mida a數組中位數的值*/ 
    int indexa = (aleft+aright)/2, indexb = (bleft+bright)/2, mida = a[indexa], midb = b[indexb];
    /* 若是倆數組中位數相等 則必是解 */
    if(mida == midb){ 
        return mida;
    }
    /*若是待查找區間內只有一個數,則小的那個爲解*/
    if(aleft >= aright && bleft >= bright){  
        return mida<midb?mida:midb;
    }
    if(mida > midb){
        bl = indexb; /* 小的取右區間 */
        br = bright;
        ar = indexa; /* 大的取左區間 */ 
        al = aleft;    
        if( (aright-aleft+1) % 2 == 0){  /*偶數個數縮小範圍時拋掉當前中位數*/
            bl = indexb+1;
        }
    }else if(mida < midb){
        al=indexa;
        ar = aright;
        bl=bleft;
        br = indexb;
        if((bright-bleft+1) % 2 == 0){
            al=indexa+1;
        }
    }
    return bin_search(a, al, ar, b, bl, br);
}

 運行狀況以下。

image.png

 看了下最快耗時是25ms左右。並無數量級上的差距。若是把遞歸改爲循環,緩衝輸入改爲快速輸入應該能有差很少的時間耗時,說明此算法應該是目前爲止最快的了。

小結

 本次題目難度不大,主要鍛鍊了下寫代碼態度QAQ。畢竟很久沒寫代碼了。對於邊界條件的掌握仍是有些生疏,但願可以更加嚴謹。 朋友們有什麼問題的也歡迎跟我交流~

相關文章
相關標籤/搜索