求兩個有序數組的中位數或者第k小元素


問題:兩個已經排好序的數組,找出兩個數組合並后的中位數(如果兩個數組的元素數目是偶數,返回上中位數)。

設兩個數組分別是vec1和vec2,元素數目分別是n1、n2。

 

算法1:最簡單的辦法就是把兩個數組合並、排序,然后返回中位數即可,由於兩個數組原本是有序的,因此可以用歸並排序中的merge步驟合並兩個數組。由於我們只需要返回中位數,因此並不需要真的合並兩個數組,只需要模擬合並兩個數組:每次選數組中較小的數,統計到第(n1+n2+1)/2個元素就是要找的中位數。算法復雜度為O(n1+n2)

int findMedian_merge(vector<int> &vec1, vector<int> &vec2)
{
    int N1 = vec1.size(), N2 = vec2.size();
    int medean = (N1 + N2 + 1) / 2, i = 0, j = 0;
    for(int k = 1; k < medean; k++)
    {
        if(i < N1 && j < N2)
        {
            if(vec1[i] < vec2[j])i++;
            else j++;
        }
        else if(i >= N1)//數組vec1到達末尾
            j++;
        else if(j >= N2)//數組vec2到達末尾
            i++;
    }
    if(i < N1 && j < N2)
        return vec1[i] < vec2[j] ? vec1[i] : vec2[j];
    else if(i >= N1)
        return vec2[j];
    else if(j >= N2)
        return vec1[i];
}

 

講下面的算法之前,先說2個結論1某個數組中有一半的元素不超過數組的中位數,有一半的元素不小於中位數(如果數組中元素個數是偶數,那么這里的一半並不是嚴格意義的1/2)。結論2:如果我們去掉數組比中位數小的k個數,再去掉比中位數大的k個數,得到的子數組的中位數和原來的中位數相同。

算法2:利用折半查找的思想,假設兩個數組的中位數分別是vec1[m1], vec2[m2]                                                      本文地址

1、如果vec1[m1] = vec2[m2] ,那么剛好有一半元素不超過vec1[m1],則vec1[m1]就是要找的中位數。

2、如果vec1[m1] < vec2[m2] 根據結論1很容易可以推理出,這個中位數只可能出現在vec1[n1/2,…,n1-1]或vec2[0,…,(n2-1)/2]中,那么vec1[n1/2,…,n1-1]和vec2[0,…,(n2-1)/2]的中位數是不是和原來兩個數組的中位數相同呢?根據結論2,如果原數組長度相等,即n1=n2,那么中位數不變;如果長度不相等,vec2中去掉的大於中位數的數的個數 > vec1中去掉的小於中位數的數的個數 ,則中位數不一定不變。因此我們要在兩個數組中去掉相同個數的元素。如下圖所示,假設n1 < n2, 兩個數組都去掉n1/2個元素,則子數組vec1[n1/2,…,n1-1]和vec2[0,…,n2-1-n1/2]的中位數和原來的中位數相同,圖中紅色方框里是去掉的元素。

注意:在n1<n2的假設下,不管我們是求上中位數還是下中位數,我們每次去掉的元素都是n1/2(整數除法)個。例如vec1 = [1,3,5,7],vec2 = [2,4,6,8], 如果我們要求的是上中位數,m1 = m2 =1,即3 < 4, 要刪掉vec1的前半段,這里vec1[m1] = 3 要不要刪除呢,我們只要判斷一下3能否可能成為中位數,假設3是中位數,不超過3的數只有3個(1,2,3),總得元素有8個,因此3不可能成為上中位數,我們可以在vec1中刪除2兩個元素。如果是求下中位數,即m1 = m2 = 2,即5 < 6,刪除vec1前半段時要不要刪除5呢?注意到比不超過5的數有5個,不低於5的數有4個,因此5有可能成為下中位數,因此5不能刪除,vec1中只能刪除左邊兩個元素。同理當vec1的個數是奇數時,vec1的中位數永遠不能刪除,即只能刪除vec1的n1/2(整數除法)個元素

image

3、如果vec1[m1] > vec2[m2] ,同上分析,中位數只可能出現在vec1的前半段或vec2的后半段。如下圖所示,兩個數組分別去掉n1/2個元素后,子數組vec1[0,…,n1/2-1]和vec2[n1/2,…,n2-1]的中位數和原來的中位數相同

image

子數組遞歸求解,即可求出中位數,算法復雜度為O(log(n1+n2)).注意一下遞歸結束條件和邊界處理。如果要求下中位數只要稍作修改,可以參考另一篇博客

int findMedian_logn(int vec1[], int n1, int vec2[], int n2)
{
    int m1 = (n1-1) / 2, m2 = (n2-1) / 2;
    if(n1 == 1)
    {//遞歸結束條件
        if(n2 == 1)
            return vec1[0] < vec2[0] ? vec1[0] : vec2[0];
        if(n2 % 2 == 0)
        {
            if(vec1[0] >= vec2[m2+1])
                return vec2[m2+1];
            else if(vec1[0] <= vec2[m2])
                return vec2[m2];
            else return vec1[0];
        }
        else
        {
            if(vec1[0] >= vec2[m2])
                return vec2[m2];
            else if(vec1[0] <= vec2[m2-1])
                return vec2[m2-1];
            else return vec1[0];
        }
    }
    else if(n2 == 1)
    {//遞歸結束條件
        if(n1 % 2 == 0)
        {
            if(vec2[0] >= vec1[m1+1])
                return vec1[m1+1];
            else if(vec2[0] <= vec1[m1])
                return vec1[m1];
            else return vec2[0];
        }
        else
        {
            if(vec2[0] >= vec1[m1])
                return vec1[m1];
            else if(vec2[0] <= vec1[m1-1])
                return vec1[m1-1];
            else return vec2[0];
        }
    }
    else
    {
        int cutLen = n1/2 > n2/2 ? n2/2 : n1/2;//注意每次減去短數組的一半,如果數組長度n是奇數,一半是指n-1/2
        if(vec1[m1] == vec2[m2])return vec1[m1];
        else if(vec1[m1] < vec2[m2])
            return findMedian_logn(&vec1[cutLen], n1-cutLen, vec2, n2-cutLen);
        else
            return findMedian_logn(vec1, n1-cutLen, &vec2[cutLen], n2-cutLen);
    }
}

 

算法3:這里我們把問題擴展一下,求兩個有序數組的第k小的元素。我們假設這個第k小的元素是X,若X在數組vec1的第i個位置,如果把X放到vec2中,那么X在排數組vec2中的第(k-i+1)個位置,則X >= vec2中第k-i個元素 且 X <= vec2中第k-i+1個元素。

因此我們可以首先假設元素X在數組vec1中,對vec1中的元素進二分查找。

選取vec1中的元素vec1[idx1](idx1 = n1/(n1+n2)*(k-1), 即第idx1+1個元素,由於不是中位數,因此不是選取中間元素),看vec2中的元素vec2[idx2](idx2 = k-idx1-2, 即第k-idx1-1個元素):

注意到這里的一個不變式:idx1及前面元素的個數 + idx2及前面元素的個數 = k,即(idx1+1)+(idx2+1)= k

1、如果vec1[idx1] == vec2[idx2] ,剛好有idx1+1+idx2+1 = k個元素不超過vec1[idx1], 則vec1[idx1]為所求

2、如果vec1[idx1] < vec2[idx2], 不超過vec1[idx1]的元素個數肯定小於k,因此vec1[idx1]以及其前面的元素肯定小於我們要求的元素;對於vec2[idx2+1]以及其后面的元素,不超過他們的數的個數肯定大於K個,因此vec2[idx2+1]以及其后面的元素肯定大於我們要求的元素。故搜索范圍縮小到vec1[idx1+1,…,n1-1] 和vec2[0...idx2]

3、如果vec1[idx1] > vec2[idx2], 同理搜索范圍縮小到vec1[0...idx1]和vec2[idx2+1,....n2-1]

其實算法思想和上面的算法2相同。上述算法也可以每次取vec1和vec2的第k/2個元素比較,這樣每次可以使k減小一半,可以參考here

注意邊界處理。算法中每次迭代平均k都會減小約k/2,因此算法復雜度為O(logk),而k = (n1+n2)/m, m是一個常數,即復雜度為O(log(n1+n2))

 1 //找到兩個有序數組中第k小的數,k>=1
 2     int findKthSmallest(int vec1[], int n1, int vec2[], int n2, int k)
 3     {
 4         //邊界條件處理
 5         if(n1 == 0)return vec2[k-1];
 6         else if(n2 == 0)return vec1[k-1];
 7         if(k == 1)return vec1[0] < vec2[0] ? vec1[0] : vec2[0];
 8         
 9         int idx1 = n1*1.0 / (n1 + n2) * (k - 1);
10         int idx2 = k - idx1 - 2;
11      
12         if(vec1[idx1] == vec2[idx2])
13             return vec1[idx1];
14         else if(vec1[idx1] < vec2[idx2])
15             return findKthSmallest(&vec1[idx1+1], n1-idx1-1, vec2, idx2+1, k-idx1-1);
16         else
17             return findKthSmallest(vec1, idx1+1, &vec2[idx2+1], n2-idx2-1, k-idx2-1);
18     }

 

算法4:對於尋找兩個有序數組第k小的元素,還有一種簡化算法1,復雜度為O(k)的算法,在歸並兩個數組的過程中,如果如果已經選擇的元素達到k,就不許要再歸並下去了。

 

參考資料:

kenby:http://blog.csdn.net/kenby/article/details/6833407

David Luo:http://www.cnblogs.com/davidluo/articles/k-smallest-element-of-two-sorted-array.html

Hackbuteer1: http://blog.csdn.net/hackbuteer1/article/details/7584838

Median of Two Sorted Arrays

Find the k-th Smallest Element in the Union of Two Sorted Arrays

 leetcode之 median of two sorted arrays

 

【版權聲明】轉載請注明出處http://www.cnblogs.com/TenosDoIt/p/3554479.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM