Я спрашиваю об этом в отношении алгоритма 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 состоит в том, чтобы создать полную кучу размером n в O (n), а затем удалить верхний элемент кучи k раз O (log 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. Тогда почему мой тест показывает иначе?