Drzewa binarne i drzewa BST

Kurs: Wstęp do programowania
Lekcja 13: Struktury hierarchiczne: Drzewa
Temat 1: Drzewa binarne i drzewa BST

⇓ spis treści ⇓


Drzewa binarne i drzewa BST (Binary Search Trees) to fundamentalne struktury danych, które odgrywają kluczową rolę w wielu algorytmach i aplikacjach. Drzewo binarne to struktura hierarchiczna, w której każdy węzeł ma co najwyżej dwóch potomków, zwanych lewym i prawym dzieckiem. Drzewa BST to szczególny rodzaj drzew binarnych, które wprowadzają dodatkowe ograniczenia, aby umożliwić szybkie operacje wyszukiwania, wstawiania i usuwania elementów. W tej lekcji omówimy szczegółowo, czym są drzewa binarne i drzewa BST, jak działają, jakie są ich właściwości oraz jak je implementować i wykorzystywać w praktycznych zastosowaniach.

Podstawy drzew binarnych

Drzewo binarne składa się z węzłów, z których każdy zawiera wartość oraz wskaźniki na swoje dzieci: lewe i prawe. Węzeł bez dzieci nazywany jest liściem, a węzeł bez rodzica jest korzeniem drzewa. Drzewa binarne są szeroko stosowane w różnych aplikacjach, takich jak reprezentowanie wyrażeń arytmetycznych, struktury plików, oraz modelowanie hierarchii danych. W zależności od sposobu organizacji danych węzły drzewa mogą tworzyć różne typy drzew binarnych, w tym pełne drzewa binarne, zbalansowane drzewa binarne, oraz drzewa BST.

Właściwości drzew binarnych
  • Głębokość drzewa: Głębokość drzewa to długość najdłuższej ścieżki od korzenia do liścia.
  • Wysokość węzła: Wysokość węzła to liczba krawędzi od tego węzła do najdalszego liścia.
  • Pełne drzewo binarne: Pełne drzewo binarne to drzewo, w którym każdy węzeł wewnętrzny ma dokładnie dwoje dzieci.
  • Idealne drzewo binarne: Idealne drzewo binarne to drzewo, w którym wszystkie liście są na tym samym poziomie, a każdy węzeł ma dwoje dzieci.

Drzewa binarne są często wykorzystywane do implementacji bardziej zaawansowanych struktur danych, takich jak drzewa BST, które wprowadzają dodatkowe reguły i są bardziej zoptymalizowane pod kątem operacji na danych.

Drzewa BST (Binary Search Trees)

Drzewa BST to drzewa binarne, które spełniają warunek, że dla każdego węzła w drzewie wszystkie wartości w lewym poddrzewie są mniejsze od wartości w bieżącym węźle, a wszystkie wartości w prawym poddrzewie są większe. To uporządkowanie umożliwia wydajne operacje wyszukiwania, wstawiania i usuwania, które mają średnią złożoność O(log n), gdzie n to liczba węzłów w drzewie.

Właściwości drzew BST
  • Wyszukiwanie: Wyszukiwanie w drzewie BST jest szybkie, ponieważ w każdej iteracji zmniejszamy przestrzeń wyszukiwania o połowę.
  • Wstawianie: Wstawianie nowego elementu do drzewa BST wymaga znalezienia odpowiedniego miejsca, co również ma złożoność O(log n) w zbalansowanym drzewie.
  • Usuwanie: Usuwanie elementu może być bardziej skomplikowane, zwłaszcza jeśli węzeł ma dwoje dzieci. W takich przypadkach znajdujemy najmniejszą wartość w prawym poddrzewie lub największą w lewym poddrzewie i zastępujemy usuwany węzeł.
Implementacja drzewa BST w C++

Oto przykład implementacji drzewa BST w języku C++:

struct Node {
    int data;
    Node* left;
    Node* right;

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

class BST {
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);
        }
    }

    bool search(Node* node, int value) const {
        if (!node) return false;
        if (value == node->data) return true;
        if (value < node->data) return search(node->left, value);
        return search(node->right, value);
    }

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

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

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

    bool search(int value) const {
        return search(root, value);
    }

    void displayInOrder() const {
        inOrderTraversal(root);
        std::cout << std::endl;
    }

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

W powyższej implementacji klasa BST definiuje metody do wstawiania, wyszukiwania i przechodzenia po drzewie BST. Używamy rekurencji, aby dodawać elementy do drzewa i wyszukiwać wartości. Przechodzenie w porządku inorder umożliwia wyświetlenie elementów drzewa w kolejności rosnącej.

Zalety i wady drzew BST

Drzewa BST mają wiele zalet, ale również pewne ograniczenia:

  • Zalety: Wydajne operacje wyszukiwania, wstawiania i usuwania; łatwa implementacja; możliwość uporządkowania danych.
  • Wady: W przypadku niezbalansowanego drzewa wydajność może spaść do O(n); podatność na degenerację w strukturę podobną do listy, jeśli dane są uporządkowane w określony sposób.

Zbalansowane drzewa BST

Aby uniknąć problemów związanych z niezbalansowanymi drzewami, stosuje się różne techniki balansowania, takie jak drzewa AVL i drzewa czerwono-czarne. Drzewa AVL to rodzaj drzew BST, które automatycznie utrzymują balans, aby zapewnić, że wysokość drzewa jest jak najmniejsza. W drzewach AVL różnica wysokości między lewym a prawym poddrzewem dowolnego węzła nie może być większa niż 1.

Implementacja drzewa AVL (ogólna koncepcja)

Drzewo AVL wprowadza dodatkowe operacje rotacji, które są wykonywane podczas wstawiania i usuwania elementów, aby utrzymać balans. Dzięki temu złożoność operacji pozostaje O(log n) nawet w najgorszym przypadku.

Implementacja drzew AVL jest bardziej skomplikowana niż standardowych drzew BST, ale w wielu przypadkach znacznie poprawia wydajność operacji na danych. W praktyce drzewo AVL jest szeroko stosowane w aplikacjach, gdzie wydajność jest kluczowa.

Podsumowanie

Drzewa binarne i drzewa BST to potężne struktury danych, które znajdują zastosowanie w wielu algorytmach i aplikacjach. W tej lekcji omówiliśmy ich właściwości, zalety i wady oraz przedstawiliśmy przykłady implementacji. Zrozumienie, jak działają drzewa BST, jest kluczowe dla efektywnego zarządzania danymi i optymalizacji wydajności aplikacji. Dzięki tej wiedzy będziesz w stanie implementować i wykorzystywać drzewa binarne i drzewa BST w swoich projektach, a także zrozumieć, kiedy warto używać bardziej zaawansowanych struktur, takich jak drzewa AVL.

Następny temat ==> Implementacja drzew o różnych strukturach



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: