AiSD (index)


AiSD (11) - drzewa BST

OBOWIĄZKOWE DO PRZESŁANIA: jedno zadanie

WPROWADZENIE

W drzewach BST (ang. Binary Search Tree, binarne drzewo poszukiwań) przeglądanie wierzchołków w kolejności inorder daje elementy posortowane niemalejąco. Aby to uzyskać trzeba wstawiać elementy do drzewa w następujący sposób:

Koszt wykonania podstawowych operacji w drzewie BST (insert, search, remove) jest rzędu O(h), gdzie h jest wysokością drzewa. Jeżeli drzewo BST jest zrównoważone, wtedy h ~ log(n). W przypadku zdegenerowanym h = n.


// Szablon dla przypadkowego drzewa binarnego.
template <typename T>
class BinarySearchTree {
    BSTNode<T> *root;
public:
    BinarySearchTree() : root(nullptr) {} // konstruktor domyślny
    ~BinarySearchTree() { delete root; } // trzeba wyczyścić
    void clear() { delete root; root = nullptr; }
    bool empty() const { return root == nullptr; }
    T& top() { assert(root != nullptr); return root->value; } // podgląd korzenia
    void insert(const T& item) { root = insert(root, item); }
    void remove(const T& item); // usuwanie przez złączanie
    // Szukając element dostajemy wskaźnik do elementu lub nullptr.
    T* search(const T& item) const {
        auto ptr = search(root, item);
        return ((ptr == nullptr) ? nullptr : &(ptr->value));
    }
    T* iter_search(const T& item) const { // wg libavl
        for (auto node=root; node != nullptr; ) {
            if (item == node->value) {
                return &(node->value);
            } else if (item < node->value) {
                node = node->left;
            } else {
                node = node->right;
            }
        }
        return nullptr;
    }
    T* find_min();
    T* find_max();
    void preorder() { preorder(root); }
    void inorder() { inorder(root); }
    void postorder() { postorder(root); }
    void iter_preorder();
    void iter_inorder(); // trudne
    void iter_postorder(); // trudne
    void bfs(); // przejście poziomami (wszerz)
    void display() { display(root, 0); }

    // Metody bezpośrednio odwołujące się do node.
    // Mogą działać na poddrzewie.
    BSTNode<T> * insert(BSTNode<T> *node, const T& item); // zwraca nowy korzeń
    BSTNode<T> * search(BSTNode<T> *node, const T& item) const; // zwraca węzeł
    BSTNode<T> * remove(BSTNode<T> *node); // zwraca nowy korzeń poddrzewa
    void preorder(BSTNode<T> *node);
    void inorder(BSTNode<T> *node);
    void postorder(BSTNode<T> *node);
    void display(BSTNode<T> *node, int level);
    virtual void visit(BSTNode<T> *node) {
        assert(node != nullptr);
        std::cout << "visiting " << node->value << std::endl;
    }
};

template <typename T>
BSTNode<T> * BinarySearchTree<T>::insert(BSTNode<T> *node, const T& item) {
    if (node == nullptr) {
        return new BSTNode<T>(item);
    }
    if (item < node->value) {
        node->left = insert(node->left, item);
    } else {
        node->right = insert(node->right, item);
    }
    return node; // zwraca nowy korzeń
}

template <typename T>
BSTNode<T> * BinarySearchTree<T>::search(BSTNode<T> *node, const T& item) const {
    if (node == nullptr || node->value == item) {
        return node;
    } else if (item < node->value) {
        return search(node->left, item);
    } else {
        return search(node->right, item);
    }
}

ROTACJE DRZEWA BST

Rotacja drzewa (ang. tree rotation) jest operacją na drzewie BST, która zmienia jego lokalną strukturę bez zmiany kolejności elementów, przy przechodzeniu przez drzewo BST metodą inorder. Wyróżnia się dwie symetryczne operacje: rotacja w prawo i rotacja w lewo.


|     X     --[w prawo]-o    Y     |
|    / \                    / \    |
|   Y   C   o-[w lewo]--   A   X   |
|  / \                        / \  |
| A   B                      B   C |

template <typename T>
BSTNode<T> * BinarySearchTree<T>::rotate_right(BSTNode<T> *node) {
    if (node->left == nullptr)
        return node; // bez zmian
    BSTNode<T> *top = node; // węzeł X
    node = top->left; // węzeł Y
    top->left = node->right; // przepinamy węzeł B
    node->right = top; // przepinamy węzeł X
    return node; // zwracamy węzeł Y
}

template <typename T>
BSTNode<T> * BinarySearchTree<T>::rotate_left(BSTNode<T> *node) {
    if (node->right == nullptr)
        return node; // bez zmian
    BSTNode<T> *top = node; // węzeł Y
    node = top->right; // węzeł X
    top->right = node->left; // przepinamy węzeł B
    node->left = top; // przepinamy węzeł Y
    return node; // zwracamy węzeł X
}

USUWANIE PRZEZ ZŁĄCZANIE


template <typename T>
void BinarySearchTree<T>::remove(const T& item) { // usuwanie przez złączanie
    // Najpierw znajdujemy węzeł i jego rodzica.
    BSTNode<T> *node = root;
    BSTNode<T> *prev = nullptr;
    while (node != nullptr) {
        if (node->value == item)
            break;
        prev = node;
        if (item < node->value) {
            node = node->left;
        } else {
            node = node->right;
        }
    }
    // Szukamy przyczyny przerwania pętli while.
    if (node != nullptr && node->value == item) {
        // node ma być usunięty.
        if (node == root) {
            root = remove(node);
        } else if (prev->left == node) {
            prev->left = remove(node);
        } else {
            prev->right = remove(node);
        }
    } else if (root != nullptr) {
        ; // klucza nie znaleziono
    } else { // root == nullptr
        ; // drzewo jest puste
    }
}

template <typename T>
BSTNode<T> * BinarySearchTree<T>::remove(BSTNode<T> *node) {
    // Mamy usunąć węzeł i zwrócić nowy korzeń poddrzewa.
    assert(node != nullptr);
    BSTNode<T> *new_root;
    if (node->right == nullptr) { // node nie ma prawego dziecka
        new_root = node->left;
    } else if (node->left == nullptr) { // node nie ma lewego dziecka
        new_root = node->right;
    } else { // obecne lewe i prawe dziecko
        new_root = node; // zapisujemy stary korzeń
        node = node->left; // idziemy w lewo
        while (node->right != nullptr) { // idziemy w prawo do końca
            node = node->right;
        }
        node->right = new_root->right; // prawe poddrzewo przełączone
        node = new_root; // węzeł do usunięcia
        new_root = node->left; // nowy korzeń
    }
    delete node;
    return new_root;
}

|      X                      Usuwamy węzeł X.
|    /   \               Nowym korzeniem poddrzewa będzie Y.
|   Y     C       Y         Z jest poprzednikiem X.
|  / \           / \
| A   Z         A   Z
|    /             / \
|   B             B   C

ALGORYTM DSW

https://en.wikipedia.org/wiki/Day%E2%80%93Stout%E2%80%93Warren_algorithm

Algorytm DSW (Day, Stout, Warren) służy do zrównoważenia drzewa BST. Algorytm działa w miejscu i w czasie O(n).

ZADANIE 11.1

Napisać funkcje find_min() i find_max() znajdujące odpowiednio wartość najmniejszą i największą w drzewie BST. Funkcje powinny zwracać wskaźnik do wartości lub nullptr dla pustego drzewa.

ZADANIE 11.2

Napisać funkcję find_successor() znajdujący następnik danego węzła przy przechodzeniu inorder (lub nullptr, jeżeli go nie ma). Węzły powinny posiadać łącze do rodzica.

ZADANIE 11.3

Napisać funkcję find_predecessor() znajdujący poprzednik danego węzła przy przechodzeniu inorder (lub nullptr, jeżeli go nie ma). Węzły powinny posiadać łącze do rodzica.

ZADANIE 11.4

Napisać funkcję sprawdzającą, czy drzewo binarne jest drzewem BST. Nie wystarczy sprawdzić dla każdego węzła, czy zachodzi node->left->value <= node->value, oraz node->value <= node->right->value. Musimy do węzła przekazać informację od rodzica o ograniczeniach na wartość przechowywaną w węźle.


|       5          Przykład drzewa binarnego, które nie jest drzewem BST.
|     /   \        Wartość 6 jest większa od 3, ale nie jest mniejsza od 5.
|   3       8
|  / \     / \
| 1   6   7   9

ZADANIE 11.5

Zaimplementować algorytm DSW.


AiSD (index)