BST vs SPLAY tree (błędne wyniki)

0

Cześć wszystkim,

dostałem zadanie poniżej

"napisać program, który czyta długi tekst np. książkę. Każde nowe słowo dodawane jest do drzewa, a jego licznik ustawiany jest na 1. Jeżeli drzewo zawierało już to słowo – jego licznik jest zwiększany. 
Proszę porównać czas budowy drzewa w obu wersjach oraz wysokości otrzymanych drzew . Dodatkowo, po zbudowaniu drzewa, proszę losowo wybrać 100 słów z tekstu i zbadać ilość operacji potrzebnych do ich odnalezienia. Doświadczenie powtórzyć dla kilku (5-10) tekstów. "

moje implementacje wyglądają tak:
BST https://pastebin.pl/view/c78e2860
SPLAY https://pastebin.pl/view/d189b88c

problem jest taki, że czas budowania drzewa BST jest dłuższy niż SPLAY, a powinno być raczej odwrotnie w związku z dodatkowy narzutem w SPLAY

w załączniku wykresy oraz książki użyte jako dane wejściowe LotR1.txt LotR2.txt LotR3.txt hobbit.txt joyland.txt

1.png2.png3.png

Byłbym bardzo wdzięczny gdyby ktoś mógłby zerknąć i podpowiedzieć w czym jest problem i dlaczego budowanie drzewa BST trwa dłużej?

0
  1. Na tak małych danych czas wykonywania operacji zdominują ci takie rzeczy jak CPU cache a nie złożoność operacji...
  2. Rekurencyjne implementacje też nie ułatwiaja sprawy
  3. Nie wiem czemu uważasz ze budowa Splay musi być wolniejsza. Splay ma to do siebie że częściej odwiedzane nody są blizej korzenia, a twoje BST ma wszystko w kolejności dodawania. To oznacza że np. jeśli na początku w tekscie masz dużo słów, które później się nie pojawiają, to BST będzie działać wolniej. Zresztą widać to np. po długościach węzłów. Sprawdź np. jaka jest średnia długość węzła w obu drzewach.
0

połączyłem teksty w jeden duży i zduplikowałem x16 w jeden plik i BST zrobiło w 44s Splay w 37s

co do tej średniej wysokości drzew, nie mam jestem pewien jak się za to zabrać, żeby policzyć, czy mógłbyś dać jakieś wskazówki?

dzięki za odpowiedź

1

połączyłem teksty w jeden duży i zduplikowałem x16 w jeden plik i BST zrobiło w 44s Splay w 37s

Nadal brzmi mało. Ile masz L1,L2 i L3 cache? Na nowym CPU możesz mieć np. 16 czy 32MB. Żeby serio coś obserwować w benchmarku to trzeba by wrzucić np. GB tekstu, powiedzmy cała polską wikipedie ;)

co do tej średniej wysokości drzew, nie mam jestem pewien jak się za to zabrać, żeby policzyć, czy mógłbyś dać jakieś wskazówki?

Puść N wyszukiwań losowych słów i policz ile średnio nodów trzeba przejść zanim się coś znajdzie (w przypadku Splay ile "ruchów" trzeba wykonać). Jak weźmiesz losowe słowa to BST pewnie będzie wygrywać, ale jak weźmiesz słowa zgodnie z rozkładem statystycznym w tekście to spodziewałbym się ze Splay będzie dużo szybszy.

edit:
@tomek3113 pokaże ci kilka "trików", zebyś nie myślał że takie gdybanie nad jakimś cache ma w ogóle sens.

  1. Patrz na taki kod:
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <algorithm>
#include <vector>

using namespace std::chrono;
using namespace std;

vector<int> generateArray(int count, int max) {
    vector<int> array;
    array.reserve(count);
    for (int i = 0; i < count; i++) {
        array.push_back(rand() % max);
    }
    return array;
}

void plain(int count, int max) {
    vector<int> array = generateArray(count, max);
    auto start = high_resolution_clock::now();
    int v = 0;
    for (int i = 0; i < count; i++) {
        if (array[i] < max / 2) {
            v += 1;
        }
    }
    printf("%d %d\n", v, high_resolution_clock::now() - start);
}

void sorting(int count, int max) {
    vector<int> array = generateArray(count, max);
    std::sort(array.begin(), array.end());
    auto start = high_resolution_clock::now();
    int v = 0;
    for (int i = 0; i < count; i++) {
        if (array[i] < max / 2) {
            v += 1;
        }
    }
    printf("%d %d\n", v, high_resolution_clock::now() - start);
}

int main() {
    int count = 10000000;
    int max = 10;
    plain(count, max);
    sorting(count, max);
}

Obie funkcje robią dokładnie to samo, robią tablicę z losowymi wartościami w zakresie 0..max a potem w pętli zliczają ile z tych wartości jest mniejsze od max/2 (powinna być jakaś połowa). Jedyna różnica między tymi dwiema funkcjami jest taka, ze funkcja sorting sortuje tablicę przed tą pętlą zliczającą. Sama pętla wygląda tak samo, więc gdyby patrzeć na to tylko z perspektywy złożonosci obliczeniowej to mamy w obu funkcjach O(n), a jednak empirycznie możesz to sobie odpalić i zobaczysz że wersja sortujaca będzie w praktyce dużo szybsza, u mnie na laptopie jakieś 3 razy (przy -O0). Zastanów się czemu tak się dzieje.

  1. Teraz drugi trik:
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <algorithm>
#include <vector>

using namespace std::chrono;
using namespace std;

vector<int> generateArray(int count, int max) {
    vector<int> array;
    array.reserve(count);
    for (int i = 0; i < count; i++) {
        array.push_back(rand() % max);
    }
    return array;
}

void sequential(int count, int max) {
    vector<int> array = generateArray(count, max);
    std::vector<int> indices(count);
    std::iota(std::begin(indices), std::end(indices), 0);
    auto start = high_resolution_clock::now();
    int v = 0;
    for (int index: indices) {
        v += array[index];
    }
    printf("%d %d\n", v, high_resolution_clock::now() - start);
}

void randomized(int count, int max) {
    vector<int> array = generateArray(count, max);
    std::vector<int> indices(count);
    std::iota(std::begin(indices), std::end(indices), 0);
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(indices.begin(), indices.end(), g);
    auto start = high_resolution_clock::now();
    int v = 0;
    for (int index: indices) {
        v += array[index];
    }
    printf("%d %d\n", v, high_resolution_clock::now() - start);
}

int main() {
    int count = 10000000;
    int max = 10;
    randomized(count, max);
    sequential(count, max);
}

Znowu mamy dwie funkcje które robią dokładnie to samo, liczymy ile czasu wykonuje się prosta pętla sumująca elementy tablicy. Jedyna różnica jest taka, że w jednym przypadku lecimy po kolejnych indeksach tablicy (bo nasze indices ma wartości 0,1,2,3...) a w drugim przypadku lecimy losowymi indeksami (bo zrobiliśmy shuffle na tablicy indeksów). Z punktu widzenia złożoności obliczeniowej znowu jest tu zwyczajnie O(n), a jednak u mnie lokalnie opcja sekwencyjna jest 5 razy szybsza. Znowu, zastanów się czemu jest taka różnica, skoro niby robimy dokładnie to samo.

(Wszystko to przy -O0)

1 użytkowników online, w tym zalogowanych: 0, gości: 1