【问题标题】:O(NlogN) algorithm runs faster than O(n)... wait, what?O(NlogN) 算法比 O(n) 运行得更快……等等,什么?
【发布时间】:2018-12-30 23:36:24
【问题描述】:

说实话,我有点困惑。我正在解决经典算法问题之一。给定一个整数集合,找出是否有 2 个元素的和等于给定数字。

所以我实施了 2 个解决方案。

bool find1(std::vector<int>& V, int sum) 
{
    std::unordered_set<int> hashTable;
    for (int i = 0; i < V.size(); ++i) 
    {
        if (hashTable.find(V[i]) != hashTable.end()) 
        {
            return true;
        }
        hashTable.insert(sum - V[i]);
    }
    return false;
}

bool find2(std::vector<int>& V, int sum) 
{
    for (int i = 0; i < V.size() ; ++i) 
    {
        if (std::binary_search(V.begin(), V.end(), sum - V[i])) 
        {
            return true;
        }
    }
    return false;
}

Find1 预计是一个线性算法(取决于桶的负载和散列函数的效率)。

Find2 预计为 NlogN,我们循环并为每次迭代进行二进制搜索。

实现这个功能后,我尝试在一个比较大的集合上测试这些算法的运行时间,结果让我很困惑..

int main() 
{
    std::vector<int> V(10000,0);

    std::chrono::system_clock::time_point now1 = std::chrono::system_clock::now();

    for (int i = 0; i < 100; ++i) 
    {
        bool b = find1(V, 1000);
    }

    std::chrono::system_clock::time_point then1 = std::chrono::system_clock::now();
    std::cout <<"Linear with hashing = "<< std::chrono::duration_cast<std::chrono::microseconds>(then1 - now1).count()<<std::endl;

    std::chrono::system_clock::time_point now2 = std::chrono::system_clock::now();
    std::sort(V.begin(), V.end());
    for (int i = 0; i < 100; ++i)
    {
        bool b = find2(V, 1000);
    }

    std::chrono::system_clock::time_point then2 = std::chrono::system_clock::now();
    std::cout <<"NlogN with binary_search = " <<std::chrono::duration_cast<std::chrono::microseconds>(then2 - now2).count() << std::endl;

    system("pause");
}

在这里,我用 0 初始化 vector,以确保两个算法都运行最坏的情况。
程序的输出是:

Linear with hashing = 6759245         
NlogN with binary_search = 4508025

这怎么可能?谁能给我解释一下?

【问题讨论】:

  • 您不仅在Find1 中搜索元素,还插入了一个元素。此外,您还执行 IO 操作,这可能是一种开销。您是否尝试过在没有这些操作的情况下运行它?
  • 为什么在一个全为 0 的数组中搜索 1000 是二进制搜索的最坏情况?我希望它在发现数组中的最大值小于 1000 时立即终止。
  • @BluesSolo IO 操作不在计时范围内,因此它们不参与计算。
  • @NathanOliver 他之前的方法中有一个std::cout &lt;&lt; V[i] &lt;&lt; std::endl;。显然它在一秒钟前被删除了。
  • V 已经排序,所以std::sort(V.begin(), V.end()); 可能是O(N)

标签: c++ algorithm sorting search time-complexity


【解决方案1】:

您创建了一个没有预期大小的哈希表。然后,您将元素一一插入。这会导致哈希表反复调整大小,从而导致系统调用分配更多内存。

虽然每次插入都会摊销O(1),但系统调用的隐藏常量足够大,可以使二分搜索更快。

尝试将哈希表的预期大小设置为sizeof(V) * 1.2 左右以避免重新散列。如果这还不够,请将时间与100000, 1000000, 10000000, ... 值进行比较。随着N 变大,您应该会看到哈希表获胜。

注意:使用V.end() == 0 进行二分搜索将在第一次比较时终止,这不是最坏的情况。这是最好的情况。可能是它更快的更多原因。

【讨论】:

    【解决方案2】:

    仅仅因为一种算法的渐近复杂度的上限小于另一种算法,并不意味着它对于任何任意输入都更快。这只是意味着存在一定大小的输入N',超过这个大小,不太复杂的算法会更快。此大小将特定于运行该程序的每个特定系统。

    测量渐近更复杂的算法更快意味着您的测试低于N'的大小。但是,这假设您的复杂性分析首先适用于程序。例如,如果您的程序使用最佳情况输入测试算法,则分析最坏情况复杂度是错误的,反之亦然。

    对于它的价值,在我的系统上的结果是:

    Linear with hashing = 9557
    NlogN with binary_search = 15828
    

    【讨论】:

      【解决方案3】:

      O(N)渐近比 O(N Log N) 快。这并不意味着它更快。

      查看 Big-O 表示法的定义。

      【讨论】:

        猜你喜欢
        • 2015-03-14
        • 2023-03-25
        • 2021-09-25
        • 1970-01-01
        • 2014-11-04
        • 1970-01-01
        • 2021-10-22
        • 2019-10-23
        • 1970-01-01
        相关资源
        最近更新 更多