求两个有序数组的中位数和者第k小元素

筅森魡賤 提交于 2019-12-22 13:23:41

问题:两个已经排好序的数组,找出两个数组合并后的中位数(如果两个数组的元素数目是偶数,返回上中位数)。

设两个数组分别是vec1和vec2,元素数目分别是n1、n2。

 

算法1:最简单的办法就是把两个数组合并、排序,然后返回中位数即可,由于两个数组原本是有序的,因此可以用归并排序中的merge步骤合并两个数组。由于我们只需要返回中位数,因此并不需要真的合并两个数组,只需要模拟合并两个数组:每次选数组中较小的数,统计到第(n1+n2+1)/2个元素就是要找的中位数。算法复杂度为O(n1+n2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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[m1+1,…,n1-1]或vec2[0,…,m2-1]中,那么vec1[m1+1,…,n1-1]和vec2[0,…,m2-1]的中位数是不是和原来两个数组的中位数相同呢?根据结论2,如果原数组长度相等,即n1=n2,那么中位数不变;如果长度不相等,vec2中去掉的大于中位数的数的个数 > vec1中去掉的小于中位数的数的个数 ,则中位数不一定不变。因此我们要在两个数组中去掉相同个数的元素。如下图所示,假设n1 < n2, 两个数组都去掉m1+1个元素,则子数组vec1[m1+1,…,n1-1]和vec2[0,…,n2-m1-2]的中位数和原来的中位数相同,图中红色方框里是去掉的元素。

image

3、如果vec1[m1] > vec2[m2] ,同上分析,中位数只可能出现在vec1的前半段或vec2的后半段。如下图所示,两个数组分别去掉n1-m1-1个元素后,子数组vec1[0,…,m1-1]和vec2[n1-m1-1,…,n2-1]的中位数和原来的中位数相同

image

子数字递归求解,即可求出中位数,算法复杂度为O(log(n1+n2)).注意一下递归结束调节和边界处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 = (m1 > m2 ? m2 : m1) + 1;
        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:该算法本质上和算法2一样,只是从另一个角度来看。这里我们把问题扩展一下,求两个有序数组的第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个元素):

1、如果vec1[idx1] >= vec2[idx2] 且 vec1[idx1] <= vec2[idx2+1] 则vec1[idx1]为所求

2、如果vec1[idx1] < vec2[idx2], 到vec1[idx1+1,…,n1-1]中寻找

3、如果vec1[idx1] > vec2[idx2+1], 到vec1[0,…,idx1-1]中寻找

4、如果vec1寻找完且没有找到,则到vec2中寻找

同理注意边界处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//找到两个有序数组中第k小的数
int findKthSmallest(int vec1[], int n1, int vec2[], int n2, int k)
{
    if(k == 1)return vec1[0] < vec2[0] ? vec1[0] : vec2[0];
    int idx1 = n1*1.0 / (n1 + n2) * (k - 1);
    int idx2 = k - idx1 - 2;
 
    if(vec1[idx1] >= vec2[idx2] &&
       ( idx2 == n2-1 || vec1[idx1] <= vec2[idx2+1]))
        return vec1[idx1];
    else if(vec1[idx1] < vec2[idx2])
    {
        if(idx1 == n1-1)return findKthSmallest(vec2, n2, vec1, n1, k);//vec1中没找到,到vec2中寻找
        return findKthSmallest(&vec1[idx1+1], n1-idx1-1, vec2, n2, k-idx1-1);
    }
    else
    {
        if(idx1 == 0)return findKthSmallest(vec2, n2, vec1, n1, k);//vec1中没找到,到vec2中寻找
        return findKthSmallest(vec1, idx1, vec2, n2, 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

 

 

【版权声明】转载请注明出处:http://www.cnblogs.com/TenosDoIt/p/3554479.html

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!