首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >K << N上K最小选择算法-O (n +k log n) vs O (n log k)

K << N上K最小选择算法-O (n +k log n) vs O (n log k)
EN

Stack Overflow用户
提问于 2011-07-12 19:21:35
回答 3查看 4.6K关注 0票数 2

这是关于Top算法的问题。我认为O(n +k log n)应该更快,因为..。例如,如果尝试插入k= 300和n= 100000000,我们可以看到O(n +k log )更小。

然而,当我使用C++进行基准测试时,它向我展示了O (n log )的速度超过了2倍。以下是完整的基准测试程序:

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <ctime>
#include <cstdlib>
using namespace std;

int RandomNumber () { return rand(); }
vector<int> find_topk(int arr[], int k, int n)
{
   make_heap(arr, arr + n, greater<int>());

   vector<int> result(k);

   for (int i = 0; i < k; ++i)
   {
      result[i] = arr[0];
      pop_heap(arr, arr + n - i, greater<int>());
   }

   return result;
}

vector<int> find_topk2(int arr[], int k, int n)
{
   make_heap(arr, arr + k, less<int>());

   for (int i = k; i < n; ++i)
   {
      if (arr[i] < arr[0])
      {
     pop_heap(arr, arr + k, less<int>());
     arr[k - 1] = arr[i];
     push_heap(arr, arr + k, less<int>());
      }
   }

   vector<int> result(arr, arr + k);

   return result;
}


int main()
{
   const int n = 220000000;
   const int k = 300;

   srand (time(0));
   int* arr = new int[n];

   generate(arr, arr + n, RandomNumber);

   // replace with topk or topk2
   vector<int> result = find_topk2(arr, k, n);

   copy(result.begin(), result.end(), ostream_iterator<int>(cout, "\n"));


   return 0;
}

find_topk的方法是在O( n )中构建一个大小为n的完整堆,然后删除堆的顶部元素k乘以O(log )。find_topk2的方法是构建一个大小为k (O( k) )的堆,使max元素位于顶部,然后从k到n,比较看是否有任何元素比top元素小,如果是,则弹出顶层元素,并推送新元素,这意味着n乘以O(log K)。这两种方法都编写得非常相似,所以我不相信任何实现细节(比如创建临时人员等)。除了algo和dataset (这是随机的)之外,还会导致差异。

我实际上可以分析基准测试的结果,并且可以看到find_topk实际上比find_topk2更多地调用比较操作符。但我更感兴趣的是理论复杂性的推理。所以有两个问题。

  1. 忽略了实现或基准测试,我认为O(n +k log )应该比O( n )更好,这是否是错误的?如果我错了,请解释为什么和如何推理,这样我才能看到O(n log k)实际上更好。
  2. ,如果我没有错误地期望1号。那么为什么我的基准显示相反呢?
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-07-12 19:45:28

在多个变量中的大O是复杂的,因为您需要假设您的变量是如何相互扩展的,所以您可以毫不含糊地接受无穷大的限制。

如果。K~ n^(1/2),则O(n log k)变为O(n log n),O(n +k log n)变为O(n + n^(1/2) log n) = O(n)。

若k~ log n,则O(n log k) = O(n log n)和O(n +k log n) = O(n)较好。请注意,log 2^1024 = 10,因此隐藏在O( n )中的常量对于任何实际的n都可能大于log n。

如果k=常数,则O(n log k) = O(n)和O(n +k log n) = O(n),这是相同的。

但是常量起着很大的作用:例如,构建一个堆可能需要读取3次数组,而构建长度为k的优先级队列只需要通过数组一次,而查找则需要一个小的常量乘以log。

这是“更好”,因此不清楚,虽然我的快速分析往往表明O(n + k log n)在对k的温和假设下表现更好。

例如,如果k是一个非常小的常量(例如k= 3),那么我可以打赌,make_heap方法的性能比实际数据上的优先级队列方法差。

明智地使用渐近分析,最重要的是,在得出结论之前对代码进行分析。

票数 4
EN

Stack Overflow用户

发布于 2011-07-12 19:50:08

你在比较两个最坏情况的上界。对于第一种方法,最坏的情况几乎等于平均情况。对于第二种情况,如果输入是随机的,那么当您将超过几个项传递到堆中时,将新值一次性丢弃的可能性很高,因为它不会替换任何顶级K,因此对此的最坏估计是悲观的。

如果你比较的是墙时钟时间,而不是比较,你可能会发现基于堆的大堆算法往往不会赢得很多比赛,因为它们有着可怕的存储位置--而现代微处理器上的恒定因素很大程度上取决于你最终工作的内存水平--发现你的数据在真正的内存芯片(或者更糟的是磁盘上)中,而没有某种程度的缓存会让你慢下来很多--这是个耻辱,因为我真的很喜欢heap排序。

票数 1
EN

Stack Overflow用户

发布于 2015-10-09 02:17:36

请记住,您现在可以使用std::nth_element,而不必使用堆并自己动手。由于默认的比较器运算符是std::less<>(),所以可以这样说:

std::nth_element( myList.begin(),myList.begin()+ k,myList.end());

现在,从0到k位置的myList将是最小的k元素。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6669838

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档