почему эта вставка в кучу быстрее, чем вставка в несортированный список?

после вставки 100000000 элементов в мою кучу и несортированный список кажется, что вставка кучи на самом деле быстрее (12 секунд против 20 секунд). Почему так? Я считаю, что вставка кучи O(logn) в то время как несортированный список вставки O(1). Я также заметил, что моя реализация вставки кучи фактически не масштабируется с количеством входных данных. Это тоже сбивает меня с толку.

вот код, который я побежал:

int main ()
{
    clock_t unsortedStart;
    clock_t heapStart;

    double unsortedDuration;
    double heapDuration;

    int num_pushes = 100000000;
    int interval = 10000;

    ofstream unsorted ("unsorted.txt");
    ofstream heap ("heap.txt");

    UnsortedPQ<int> unsortedPQ; 
    HeapPQ<int> heapPQ; 

    unsortedStart = clock();

    for (int i = 0; i < num_pushes; ++i)
    {
        if (i % interval == 0) {
            unsortedDuration = ( clock() - unsortedStart ) / (double) CLOCKS_PER_SEC;
            unsorted << unsortedDuration << " " << i << endl;
        }

        unsortedPQ.insertItem(rand() % 100);
    }

    heapStart = clock();
    for (int i = 0; i < num_pushes; ++i)
    {
        if (i % interval == 0) {
            heapDuration = ( clock() - heapStart ) / (double) CLOCKS_PER_SEC;
            heap << heapDuration << " " << i << endl;
        }
        heapPQ.insertItem(rand() % 100);
    }
    return 0;
}

это реализация кучи insert (использует std::vector):

template <class T>
void HeapPQ<T>::insertItem(T data) { 
    //insert into back of heap (std::vector)
    dataArray.push_back(data);
    int i = dataArray.size() - 1;

    //sifts the inserted element up
    while (i != 0 && dataArray[(i - 1) / 2] > dataArray[i]) {
        swap(dataArray[i], dataArray[(i - 1) / 2]);
        i = (i - 1) / 2;
    }
}

это реализация несортированного списка insert (использует std::list):

//pushes element to the back of a std::list
template <class T>
void UnsortedPQ<T>::insertItem(T data) { dataList.push_back(data); }

1 ответов


вставка в кучу является O(logn), это означает, что каждая вставка может занять не более O(logn) действия. Это не значит, что должно.

в вашем примере средняя стоимость вставки элемента O(1). Почему это?

для простоты, предположим, что вы вставляете 0и 1s в случайном порядке (в текущей версии только цифры 0..99 (rand() % 100) вставляются - расчет более сложный, но поведение остается прежним). После 2*n элементы вставлены, будет около n 0s и n 1s в куче, и куча будет выглядеть следующим образом:

                                 0
                                0 0
                               00 00
                          ...............
                         0 0 0  0  0  0  0
                       11 11 11 11 11 11 11

Итак, 1s Все на последнем уровне k и 0s находятся на уровнях 0..k-1.

  1. если 1 вставляется, делать нечего (нет 2s выше).
  2. если 0 вставляется есть не более одного свопа (1s может находиться на уровне выше последнего уровня, но выше 2 уровня).

МЭС, что в среднем нам нужно только 0.5 ОСП и не k.

имея одинаковое асимптотическое время работы, Все сводится к (амортизированным) затратам на вставку в вектор и в список. Список, кажется, медленнее (мое предположение было бы, что для каждой вставки этого нужно выделить элемент в куче через new и это довольно медленная операция. Затраты зависят от других факторов, например от размера вставленного объекты, и, таким образом, он может варьироваться, какой из них быстрее).


давайте взглянем на ваше дело, где цифры генерируются равномерное распределение [0..99]. После n>>100 вставки у нас будет следующая ситуация (есть некоторые махания руками, но суть должна быть ясной):

  1. последнем уровне (k - й) из кучи имеет n/2 элементы и состоит из цифр 50..99. Таким образом, для 50% возможных чисел (т. е. 50..99) никакого сдвига не требуется.
  2. второй последний уровень (k-1 - й) из кучи имеет n/4 элементы и состоит из цифр 25..49. Это означает, что для 25% возможных чисел требуется ровно 1 сдвиг.
  3. уровень k-2 и n/8 элементы и состоит из цифр 13..24.
  4. уровни выше log 100/log 2 только 0s внутри. Таким образом, максимальное количество возможных сдвигов -m=log 100/log 2, независимая n - количество элементов в куча.

таким образом, в худшем случае затраты на вставку будут log 100/log 2 средняя цена еще меньше:

E(insertion)=0*1/2+1*1/4+2*1/8+...<=1.0

т. е. в среднем у нас меньше 1 смены на вставку.

NB: это не означает, что вставка в кучу амортизировала затраты O(1) - если бы вы вставляли числа не в случайном порядке, а сначала все 99s, тогда 98s,... тогда 0s у вас будут расходы O(log n) за вставки.