c++ - 为什么找到 2 个不同大小的排序数组的中位数需要 O(log(min(n,m)))

标签 c++ algorithm partitioning binary-search median

请考虑这个问题:

我们有 2 个不同大小的排序数组,A[n] 和 B[m]; 我已经实现了一个经典算法,最多需要 O(log(min(n,m)))。

方法如下: 开始将两个数组分为两组(不是两个部分,但两个分区应具有相同数量的元素)。前半部分包含来自第一和第二数组的一些第一个元素,后半部分包含来自第一和第二数组的其余(或最后)元素。由于数组的大小可以不同,因此并不意味着从每个数组中取出每一半。达到一个条件,使得前半部分的每个元素都小于或等于后半部分的每个元素。

请参阅上面的代码:

double median(std::vector<int> V1, std::vector<int> V2) 
{
    if (V1.size() > V2.size())
    {
        V1.swap(V2);
    };
    int s1 = V1.size();
    int s2 = V2.size();
    int low = 0;
    int high = s1;
    while (low <= high) 
    {
        int px = (low + high) / 2;
        int py = (s1 + s2 + 1) / 2 - px;

        int maxLeftX = (px == 0) ? MIN : V1[px - 1];
        int minRightX = (px == s1) ? MAX : V1[px];

        int maxLeftY = (py == 0) ? MIN : V2[py - 1];
        int minRightY = (py == s2) ? MAX : V2[py];

        if (maxLeftX <= minRightY && maxLeftY <= minRightX) 
        {
            if ((s1 + s2) % 2 == 0) 
            {
                return (double(std::max(maxLeftX, maxLeftY)) + double(std::min(minRightX, minRightY)))/2;
            }
            else 
            {
                return std::max(maxLeftX, maxLeftY);
            }
        }
        else if(maxLeftX > minRightY)
        {
            high = px - 1;
        }   
        else
        {
            low = px + 1;
        }
    }
    throw;
}

尽管该方法非常简单并且有效,但我仍然无法说服自己其正确性。此外,我无法理解为什么它需要 O(log(min(n,m)) 步骤。

如果有人可以简要解释正确性以及为什么需要 O(log(min(n,m))) 步骤,那就太棒了。即使您可以提供带有有意义解释的链接。

最佳答案

时间复杂度非常简单,您可以通过对元素较少的数组进行二分搜索来找到这样的分区,这样您就可以找到中位数。您执行的步骤正好为 O(log(#elements)),并且由于您的 #elements 正好为 min(n, m),因此复杂度为 O(log(min(n+m))。

正好有 (n + m)/2 个元素小于中位数,并且有相同数量的元素大于中位数。让我们将它们视为两半(让中位数属于您选择的一个)。

您当然可以将较小的数组分为两个子数组,其中一个完全位于前半部分,第二个子数组位于另一半。但是,您不知道其中有多少个元素。

让我们选择一些 x - 您对前半部分较小数组中元素数量的猜测。它必须在 0 到 n 的范围内。然后您就知道,由于正好有 (n + m)/2 个元素小于中位数,因此您必须从较大的数组中选择 (n+m)/2 - x 元素。然后你必须检查该分区是否确实有效。

要检查分区是否良好,您必须检查较小一半中的所有元素是否小于较大一半中的所有元素。您必须检查是否 maxLeftX <= minRightY 以及 maxLeftY <= minRightX (那么左半部分中的每个元素都小于右半部分中的每个元素)

如果是这样,您就找到了正确的分区。现在,您可以轻松找到中位数(max(maxLeftX, maxLeftY))、min(minRightX, minRightY) 或它们的总和除以 2)。

如果不是,您要么从较小的数组中获取了太多元素(当 maxLeftX > minRightY 时的情况),所以下次您必须猜测 x 的较小值,或者它们太少,那么您必须猜测更大的值对于 x。

为了获得最佳复杂性,请始终猜测 x 可能采用的一系列可能值的中间位置。

关于c++ - 为什么找到 2 个不同大小的排序数组的中位数需要 O(log(min(n,m))),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57135555/

相关文章:

algorithm - 这个递归算法的大O

c++ - 如何读取二进制文件的全部 64 个字节?

C++ 模板继承。子类应该用固定类型替换基类中的类型

algorithm - 二进制搜索第一次出现的 k

c++ - 找出分数 a/b 的小数点后第 k 位,其中 a,b,k 是非常大的整数(小于 10e18)

multithreading - Spring Batch 线程安全的 Map 作业存储库

azure - 表存储PartitionKey可以更新吗?

c++ - std::function 和 std::mem_fn 有什么区别

c++ - 反向迭代流

NativeLibrary for Android 中的 C++11 支持