gpt4 book ai didi

c++ - Top K 最小选择算法 - O (n + k log n) vs O (n log k) for k << N

转载 作者:搜寻专家 更新时间:2023-10-31 00:45:33 29 4
gpt4 key购买 nike

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

但是,当我使用 C++ 进行基准测试时,它显示 O (n log k) 快了 2 倍以上。这是完整的基准测试程序:

#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 n)。find_topk2的做法是建立一个大小为k(O(k))的堆,使得最大元素在最顶层,然后从k到n,比较是否有元素小于顶层元素,如果有则pop顶部元素,然后推送新元素,这意味着 n 倍 O(log k)。这两种方法的编写方式非常相似,所以我认为除了算法和数据集(随机的)之外,任何实现细节(如创建临时对象等)都不会造成差异。

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

  1. 不管实现或基准测试,我期望 O(n + k log n) 应该比 O(n log k) 更好的期望是错误的吗?如果我错了,请解释为什么以及如何推理,以便我可以看到 O(n log k) 实际上更好。
  2. 如果我的期望没有 1 没有错,那为什么我的基准测试结果不是这样?

最佳答案

多个变量中的大 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 log n) 和 O(n + k log n) = O(n),哪个更好。请注意,log log 2^1024 = 10,因此对于任何实际 n,隐藏在 O(n) 中的常量可能大于 log log n。

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

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

因此不清楚哪个“更好”,尽管我的快速分析倾向于表明 O(n + k log n) 在对 k 的温和假设下表现更好。

例如,如果 k 是一个非常小的常量(比如 k = 3),那么我敢打赌,make_heap 方法在真实世界数据上的表现比优先级队列更差。

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

关于c++ - Top K 最小选择算法 - O (n + k log n) vs O (n log k) for k << N,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6669838/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com