最小的选择算法 - 关于 /N + 注册 N/ vs. o /n k./ 为了 K << N

我询问了它与算法有关 Top K. 我会这么想 O/n + k log n/ 应该更快,因为好 ... 例如,如果您尝试连接 k = 300 和 n = 100000000, 例如,我们会看到 O/n + k log n/ 较少的。

但是,当我制作基准时 C++, 他告诉我 O /n log k/ 超过B. 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 &lt; 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 &lt; n; ++i/
{
if /arr[i] &lt; 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 这是建造一个全束的大小 n 期间 o/n/ 然后删除上堆元素 k 时间 o/登记 N/.
一种方法 find_topk2 这是建造一堆尺寸 k /O/k// 这样最大元素位于顶部,然后来自 k 到 n, 比较看任何元素是否小于上部元素,如果是的话,则拔出顶部项目并按将意味着的新项目 n 时间 O/log k/.
两种方法都是非常平等的,所以我不相信实施的任何细节 /例如,创建临时对象等。/ 除了算法和数据集外可能导致差异 /这是随机的/.

我真的可以介绍基准的结果,可以看到这一点 find_topk 实际上导致比较运算符远远超过一次 find_topk2. 但我对理论复杂性的推理更感兴趣..这两个问题。

忽略执行或基准,无论我是错的,还期待那样 O/n + k log n/ 应该比 O/n log k/? 如果我错了,请解释为什么和如何理性,这样我就可以看到 O/n log k/ 实际上更好。

如果我没有误,等待 № 1. 那么为什么我的测试表现相反?
</int></int></int></int></int></int></int></int></int></int></int></cstdlib></ctime></iterator></algorithm></vector></iostream>
已邀请:

三叔

赞同来自:

Big O 在若干变量中,很难,因为您需要对变量如何相互缩放的假设,因此您可以明确地将限制取决于无限远。

例如,如果。 到 ~ n^/1/2/, 然后 o/n k./ 将 o/n 记录 N/ 和 o/N + 注册 N/ 会o。/N + n^/1/2/ 登记 N/ = 关于/N/, 哪个更好。

如果一个 K ~ 报告 N, 对于 o/n k./ = 关于/N 期刊记录 N/ 和 o/N + 注册 N/ = 关于/N/, 哪个更好。 注意 log log 2^1024 = 10, 因此,隐藏的常量 O /n/, 也许更多 log log n 对于任何现实 n.

如果一个 K = 常数,是 o/n k./ = 关于/N/ 和 o/N + 注册 N/ = 关于/N/, 哪个是一样的。

但常量发挥着大作用:例如,构建堆可能包括数组读数 3 次数,同时构建优先级队列长度 k 当你只通过一个传递阵列和一个小的常量 Times log k 搜索。

什么 "better", 因此,虽然我的快速分析趋于表现出来 O/n + k log n/ 效果更好,柔和的假设 k.

例如,如果一个k-非常小的常数 /让我们说 k = 3/, 然后我准备争论这种方法
make_heap

它工作比实际数据的优先级队列方法更糟糕。

使用渐近分析与思想,最重要的是,在得出结论之前。

郭文康

赞同来自:

您可以比较最坏情况的两个顶级边界。 对于第一种方法,最坏的情况在很大程度上等于平均值​​。 在第二种情况下,如果输入数据是随机的,当您将其传递到一堆更少数的项目时,立即抛出新值的概率,因为它不会替换任何顶部 K, 非常高,因此对最坏情况的评估是悲观的。

如果比较挂钟的时间,而不是比较,你可能会发现基于堆的算法,大桩,通常不会赢得很多比赛,因为它们具有可怕的存储位置 - 现代微处理器上的恒定因素高度依赖于您最终工作的记忆级别 - 检测真实内存芯片中的数据 /或者更糟糕的是,在磁盘上/, 而不是什么 - 然后缓存的水平会慢下来,这非常抱歉,因为我真的很喜欢 heapsort.

冰洋

赞同来自:

请记住,现在您可以使用 std::nth_element 而不是使用一堆并自己做所有事情。 由于默认比较器运营商 - std::less<>//, 你可以说这样的话:

std::nth_element/myList.begin//, myList.begin// + 到 myList.end///;

现在 myList 来自位置OT。 0 到 k 将至少有 K 元素。

要回复问题请先登录注册