AiSD (index)


AiSD (10) - ADT TREE

OBOWIĄZKOWE DO PRZESŁANIA: jedno zadanie

WPROWADZENIE

Definicja rekurencyjna drzewa z korzeniem.

W teorii grafów definiuje się drzewo jako graf nieskierowany acykliczny, a korzeń to pewien wyróżniony wierzchołek. Wierzchołki bez dzieci to liście. W każdym drzewie istnieje dokładnie jedna ścieżka pomiędzy każdą parą wierzchołków. Zwykle rozważamy drzewa uporządkowane, gdzie kolejność dzieci jest ustalona. Poziom węzła to długość ścieżki do korzenia plus jeden. Poziom korzenia wynosi jeden. Wysokość niepustego drzewa to największy poziom węzła w drzewie. Wysokość pustego drzewa to z definicji zero.

Drzewa o stopniu rozgałęzienia m można zaimplementować tak, że każdy węzeł ma m łączy dla dzieci, niektóre mogą być puste. Inne bardziej oszczędne podejście to implementacja na lewo syn, na prawo brat (ang. left child, right sibling, LCRS), która zasadniczo jest drzewem binarnym.

W drzewie binarnym (uporządkowanym) każdy wierzchołek ma co najwyżej dwoje dzieci (lewe i prawe). Drzewo binarne zbalansowane to drzewo binarne, w którym dla każdego węzła wysokości lewego i prawego poddrzewa różnią się co najwyżej o jeden.


// Szablon dla węzła drzewa binarnego i drzewa BST.
template <typename T>
struct BSTNode {
// the default access mode and default inheritance mode are public
    T value;
    BSTNode *left, *right;
    //BSTNode *parent;   // używane w pewnych zastosowaniach
    // kostruktor domyślny
    BSTNode() : value(T()), left(nullptr), right(nullptr) {}
    // konstruktor zwykły
    BSTNode(const T& item) : value(item), left(nullptr), right(nullptr) {}
    ~BSTNode() { delete left; delete right; } // destruktor rekurencyjny
};

// Szablon dla przypadkowego drzewa binarnego.
template <typename T>
class RandomBinaryTree {
    BSTNode<T> *root;
public:
    RandomBinaryTree() : root(nullptr) {} // konstruktor domyślny
    ~RandomBinaryTree() { delete root; } // trzeba wyczyścić (rekurencyjnie)
    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); // na razie nie usuwamy elementów
    // 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* 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;
    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;
    }
};

// Wyświetlanie obróconego (counterclockwise) drzewa binarnego.
template <typename T>
void RandomBinaryTree<T>::display(BSTNode<T> *node, int level) {
    if (node == nullptr) return;
    display(node->right, level + 1);
    std::cout << std::string(3 * level, ' ') << node->value << std::endl;
    display(node->left, level + 1);
}

// Druga wersja display() z rysowaniem krawędzi drzewa (Maksymilian Brzozowski).
template <typename T>
void RandomBinaryTree<T>::display(BSTNode<T> *node, int level) {
    if (node == nullptr) return;
    display(node->right, level + 1);
    for (int i = 0; i < level; i++) {
        std::cout << "   |";
    }
    std::cout << "---" << node->value << std::endl;
    display(node->left, level + 1);
}

#include <cstdlib>   // std::rand(), RAND_MAX, std::srand()

template <typename T>
BSTNode<T> * RandomBinaryTree<T>::insert(BSTNode<T> *node, const T& item) {
    if (node == nullptr) {
        return new BSTNode<T>(item);
    }
    if (std::rand() % 2) { // można lepiej
        node->left = insert(node->left, item);
    } else {
        node->right = insert(node->right, item);
    }
    return node; // zwraca nowy korzeń
}

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

PRZECHODZENIE PO DRZEWIE WSZERZ (BFS)

Przechodzenie wszerz polega na odwiedzeniu korzenia, potem na przechodzeniu poziomami. Poziomy są przechodzone w kolejności od najbliższych do najdalszych, a w ramach poziomu węzły są odwiedzane od lewej do prawej. W implementacji wykorzystuje się kolejkę.


template <typename T>
void RandomBinaryTree<T>::bfs() {
    if (root == nullptr) return;
    std::queue<BSTNode<T>*> Q; // wskaźniki do wezłów
    BSTNode *node = root;
    Q.push(node);
    while (!Q.empty()) {
        node = Q.front(); // podglądamy
        Q.pop();        // usuwamy z kolejki
        visit(node); // tu jest właściwe przetworzenie węzła
        if (node->left != nullptr)
            Q.push(node->left); // pierwsze wyjdzie lewe, potem prawe
        if (node->right != nullptr)
            Q.push(node->right);
    }
}

PRZECHODZENIE PO DRZEWIE W GŁĄB

Podczas przechodzenia w głąb przechodzimy możliwie daleko w lewo (lub w prawo), następnie wracamy do najbliższego rozwidlenia i przechodzimy jeden krok w prawo (lub w lewo). Czynności powtarzamy, aż odwiedzimy wszystkie węzły. Moment odwiedzania węzła może być wybrany na kilka sposobów, stąd pochodzą trzy podstawowe odmiany przechodzenia:


template <typename T>
void RandomBinaryTree<T>::inorder(BSTNode<T> *node) {
    if (node == nullptr) return;
    inorder(node->left);
    visit(node);
    inorder(node->right);
}

template <typename T>
void RandomBinaryTree<T>::preorder(BSTNode<T> *node) {
    if (node == nullptr) return;
    visit(node);
    preorder(node->left);
    preorder(node->right);
}

template <typename T>
void RandomBinaryTree<T>::postorder(BSTNode<T> *node) {
    if (node == nullptr) return;
    postorder(node->left);
    postorder(node->right);
    visit(node);
}

Przechodzenie przez drzewo w kolejności preorder możemy zaimplementować iteracyjnie za pomocą stosu.


template <typename T>
void RandomBinaryTree<T>::iter_preorder() {
    if (root == nullptr) return;
    std::stack<BSTNode<T>*> S; // wskaźniki do węzłów
    BSTNode<T> *node = root;
    S.push(node);
    while (!S.empty()) {
        node = S.top(); // podglądamy
        S.pop();        // usuwamy ze stosu
        visit(node); // tu jest właściwe przetworzenie węzła
        if (node->right != nullptr) // najpierw prawe poddrzewo!
            S.push(node->right);
        if (node->left != nullptr)
            S.push(node->left);
    }
}

Iteracyjne wersje inorder() i postorder() są bardziej skomplikowane [Drozdek].

ZADANIE 10.1

Napisać funkcję calc_leaves(), która zlicza liście drzewa binarnego. Stworzyć dwie wersje: rekurencyjną i iteracyjną.

ZADANIE 10.2

Załóżmy, że drzewo binarne przechowuje liczby. Napisać funkcję calc_total(), która podaje sumę liczb przechowywanych w drzewie. Stworzyć dwie wersje: rekurencyjną i iteracyjną.

ZADANIE 10.3

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

ZADANIE 10.4

Napisać funkcję calc_height() obliczającą wysokość drzewa binarnego. Stworzyć dwie wersje: rekurencyjną i iteracyjną. Wskazówka: w wersji iteracyjnej na stos można odkładać parę (węzeł, poziom węzła), poziom korzenia wynosi 1.

ZADANIE 10.5

Napisać funkcję calc_nodes() znajdującą liczbę węzłów drzewa binarnego. Stworzyć dwie wersje: rekurencyjną i iteracyjną.


AiSD (index)