Implementacja drzew o różnych strukturach

Kurs: Wstęp do programowania
Lekcja 13: Struktury hierarchiczne: Drzewa
Temat 2: Implementacja drzew o różnych strukturach

⇓ spis treści ⇓


Drzewa o różnych strukturach są kluczowym elementem w wielu aplikacjach informatycznych, umożliwiając efektywne przechowywanie i zarządzanie danymi. Różne rodzaje drzew mają swoje unikalne właściwości i zastosowania, a wybór odpowiedniego typu drzewa może mieć ogromny wpływ na wydajność operacji, takich jak wyszukiwanie, wstawianie, usuwanie i przetwarzanie danych. W tej lekcji omówimy szczegółowo implementację kilku typów drzew, w tym drzew binarnych, drzew AVL, drzew czerwono-czarnych, oraz bardziej zaawansowanych struktur, takich jak drzewa B-drzewo i drzewa trie. Zrozumienie, jak działają te struktury i jak je implementować, jest niezbędne dla każdego programisty zajmującego się algorytmami i strukturami danych.

Drzewa binarne

Drzewo binarne to struktura, w której każdy węzeł ma co najwyżej dwóch potomków: lewego i prawego. Drzewa binarne są podstawą dla wielu innych struktur drzewiastych, takich jak drzewa BST (Binary Search Trees) i drzewa AVL. Implementacja drzewa binarnego może być stosunkowo prosta, ale istnieją różne warianty drzew binarnych, które mogą mieć istotny wpływ na wydajność.

Implementacja drzewa binarnego w C++
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) : data(value), left(nullptr), right(nullptr) {}
};

class BinaryTree {
private:
    Node* root;

    void insert(Node*& node, int value) {
        if (!node) {
            node = new Node(value);
            return;
        }
        if (value < node->data) {
            insert(node->left, value);
        } else {
            insert(node->right, value);
        }
    }

    void preOrderTraversal(Node* node) const {
        if (!node) return;
        std::cout << node->data << " "; preOrderTraversal(node->left);
        preOrderTraversal(node->right);
    }

public:
    BinaryTree() : root(nullptr) {}

    void insert(int value) {
        insert(root, value);
    }

    void displayPreOrder() const {
        preOrderTraversal(root);
        std::cout << std::endl;
    }

    ~BinaryTree() {
        // Implementacja destruktora do zwalniania pamięci
    }
};

W tej implementacji klasa BinaryTree definiuje metody do wstawiania elementów oraz przechodzenia drzewa w porządku pre-order. Węzły są tworzone dynamicznie, a drzewo jest budowane rekurencyjnie.

Drzewa AVL

Drzewo AVL to zbalansowane drzewo binarne, które automatycznie utrzymuje równowagę poprzez wykonywanie operacji rotacji. W drzewie AVL różnica wysokości między lewym a prawym poddrzewem dowolnego węzła nie może być większa niż 1. Dzięki temu operacje wyszukiwania, wstawiania i usuwania mają złożoność O(log n).

Implementacja drzewa AVL w C++

Implementacja drzewa AVL jest bardziej skomplikowana niż standardowego drzewa binarnego, ponieważ musimy dbać o balansowanie drzewa poprzez rotacje.

struct AVLNode {
    int data;
    AVLNode* left;
    AVLNode* right;
    int height;

    AVLNode(int value) : data(value), left(nullptr), right(nullptr), height(1) {}
};

class AVLTree {
private:
    AVLNode* root;

    int getHeight(AVLNode* node) {
        return node ? node->height : 0;
    }

    int getBalanceFactor(AVLNode* node) {
        return node ? getHeight(node->left) - getHeight(node->right) : 0;
    }

    AVLNode* rotateRight(AVLNode* y) {
        AVLNode* x = y->left;
        AVLNode* T2 = x->right;
        x->right = y;
        y->left = T2;
        y->height = std::max(getHeight(y->left), getHeight(y->right)) + 1;
        x->height = std::max(getHeight(x->left), getHeight(x->right)) + 1;
        return x;
    }

    AVLNode* rotateLeft(AVLNode* x) {
        AVLNode* y = x->right;
        AVLNode* T2 = y->left;
        y->left = x;
        x->right = T2;
        x->height = std::max(getHeight(x->left), getHeight(x->right)) + 1;
        y->height = std::max(getHeight(y->left), getHeight(y->right)) + 1;
        return y;
    }

    AVLNode* insert(AVLNode* node, int value) {
        if (!node) return new AVLNode(value);

        if (value < node->data) {
            node->left = insert(node->left, value);
        } else if (value > node->data) {
            node->right = insert(node->right, value);
        } else {
            return node; // Duplicates are not allowed
        }

        node->height = 1 + std::max(getHeight(node->left), getHeight(node->right));
        int balance = getBalanceFactor(node);

        if (balance > 1 && value < node->left->data) {
            return rotateRight(node);
        }

        if (balance < -1 && value > node->right->data) {
            return rotateLeft(node);
        }

        if (balance > 1 && value > node->left->data) {
            node->left = rotateLeft(node->left);
            return rotateRight(node);
        }

        if (balance < -1 && value < node->right->data) {
            node->right = rotateRight(node->right);
            return rotateLeft(node);
        }

        return node;
    }

public:
    AVLTree() : root(nullptr) {}

    void insert(int value) {
        root = insert(root, value);
    }

    ~AVLTree() {
        // Implementacja destruktora do zwalniania pamięci
    }
};

W tej implementacji każda operacja wstawiania sprawdza, czy drzewo wymaga balansowania, i wykonuje odpowiednie rotacje, aby utrzymać balans.

Drzewa czerwono-czarne

Drzewa czerwono-czarne to kolejne zbalansowane drzewa binarne, które zapewniają, że wysokość drzewa jest zawsze w przybliżeniu równa logarytmowi liczby węzłów. Drzewo czerwono-czarne stosuje zestaw reguł kolorowania i rotacji, aby utrzymać balans. W porównaniu do drzew AVL, drzewa czerwono-czarne są mniej rygorystyczne w utrzymywaniu równowagi, co czyni je bardziej efektywnymi w niektórych zastosowaniach.

Właściwości drzew czerwono-czarnych
  • Każdy węzeł jest albo czerwony, albo czarny.
  • Korzeń jest zawsze czarny.
  • Każdy liść (null) jest czarny.
  • Jeśli węzeł jest czerwony, to jego dzieci muszą być czarne (bez dwóch czerwonych węzłów z rzędu).
  • Każda ścieżka od korzenia do liścia zawiera taką samą liczbę czarnych węzłów.

Implementacja drzewa czerwono-czarnego jest bardziej złożona niż drzewa AVL, ale jest szeroko stosowana w praktyce, na przykład w implementacjach kontenerów map i set w bibliotece standardowej C++.

Drzewa B-drzewo

Drzewo B-drzewo to struktura danych, która jest szeroko stosowana w systemach baz danych i systemach plików. B-drzewo jest drzewem zbalansowanym, które umożliwia efektywne operacje wyszukiwania, wstawiania i usuwania. Węzły drzewa B-drzewa mogą mieć więcej niż dwóch potomków, co czyni je bardzo wydajnymi w przypadku dużych zbiorów danych.

Właściwości drzew B-drzewo
  • Każdy węzeł może mieć do m dzieci, gdzie m jest stopniem drzewa.
  • Każdy węzeł (oprócz korzenia) musi mieć co najmniej ⌈m/2⌉ dzieci.
  • Wszystkie liście są na tym samym poziomie.

Drzewa B-drzewo są stosowane w systemach plików, takich jak NTFS i bazy danych, aby umożliwić szybkie wyszukiwanie i aktualizację danych.

Podsumowanie

Implementacja drzew o różnych strukturach, takich jak drzewa binarne, AVL, czerwono-czarne i B-drzewo, jest kluczowa dla efektywnego zarządzania danymi. Każdy typ drzewa ma swoje unikalne właściwości i zastosowania, a zrozumienie, jak działają i jak je implementować, pozwala na tworzenie wydajnych i niezawodnych aplikacji. Wybór odpowiedniej struktury drzewa zależy od specyfiki problemu i wymagań dotyczących wydajności operacji na danych.

Następny temat ==> Przechodzenie po drzewach



Spis Treści - Wstęp do programowania

Lekcja 3: Rozwiązywanie problemów i poprawność programów Lekcja 4: Praca z różnymi typami danych Lekcja 5: Obsługa plików i pamięci Lekcja 6: Zaawansowane techniki programistyczne Lekcja 7: Wskaźniki i pamięć dynamiczna Lekcja 8: Struktura kodu i abstrakcja Lekcja 9: Rekurencja i jej zastosowania Lekcja 10: Analiza wydajności algorytmów Lekcja 11: Technika "dziel i zwyciężaj" Lekcja 12: Struktury danych o dynamicznej budowie Lekcja 13: Struktury hierarchiczne: Drzewa Lekcja 14: Struktury danych z bibliotek Lekcja 15: Algorytmy z nawrotami Lekcja 16: Programowanie dynamiczne Lekcja 17: Programowanie zachłanne Lekcja 18: Praca z grafami

Jeśli chciałbyś być poinformowany o następnych kursach to zapisz się do naszego newslettera: