Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

Podstawowe pojęcia dotyczące drzew

SPIS TREŚCI W KONSERWACJI
Tematy pomocnicze
Podrozdziały

Definicje

Drzewo (ang. tree) jest strukturą danych zbudowaną z elementów, które nazywamy węzłami (ang. node). Dane przechowuje się w węzłach drzewa. Węzły są ze sobą powiązane w sposób hierarchiczny za pomocą krawędzi (ang. edge), które zwykle przedstawia się za pomocą strzałki określającej hierarchię. Pierwszy węzeł drzewa nazywa się korzeniem (ang. root node). Od niego "wyrastają" pozostałe węzły, które będziemy nazywać synami (ang. child node). Synowie są węzłami podrzędnymi w strukturze hierarchicznej. Synowie tego samego ojca są nazywani braćmi (ang. sibling node). Węzeł nadrzędny w stosunku do syna nazwiemy ojcem (ang. parent node). Ojcowie są węzłami nadrzędnymi w strukturze hierarchicznej. Jeśli węzeł nie posiada synów, to nazywa się liściem (ang. leaf node), w przeciwnym razie nazywa się węzłem wewnętrznym (ang. internal node, inner node, branch node).

Struktura drzewa    
obrazek   A…H – węzły drzewa
strzałki – krawędzie. Zwrot określa kierunek hierarchii rodzić → dziecko.
A – korzeń drzewa
B, C, D – bracia, synowie węzła A, który jest dla nich ojcem
E, F, G – bracia, synowie węzła B, który jest dla nich ojcem
H – syn węzła D, który jest dla niego ojcem
A, B, D – węzły wewnętrzne
C, E, F, G, H – liście

Za wyjątkiem korzenia wszystkie pozostałe węzły w drzewie posiadają swojego ojca. W normalnym drzewie liczba synów dla dowolnego węzła nie jest ograniczona. Istnieje jednakże bardzo ważna klasa drzew, w których dany węzeł może posiadać co najwyżej dwóch synów. Noszą one nazwę drzew binarnych (ang. binary tree).

Ciąg węzłów połączonych krawędziami nazwiemy ścieżką (ang. path). Od korzenia do określonego węzła w drzewie wiedzie zawsze dokładnie jedna ścieżka prosta, tzn. taka, iż zawarte w niej węzły pojawiają się tylko jeden raz. Długością ścieżki (ang. path length) nazwiemy liczbę krawędzi łączących węzły w ścieżce. Dla naszego drzewa mamy następujące ścieżki proste od korzenia do kolejnych węzłów:

Struktura drzewa   Ścieżki
obrazek   ścieżka od A do A: długość 0: A
ścieżka od A do B: długość 1: A→B
ścieżka od A do C: długość 1: A→C
ścieżka od A do D: długość 1: A→D
ścieżka od A do E: długość 2: A→B→E
ścieżka od A do F: długość 2: A→B→F
ścieżka od A do G: długość 2: A→B→G
ścieżka od A do H: długość 2: A→D→H

Długość ścieżki prostej od korzenia do danego węzła nazywa się poziomem węzła (ang. node level). Korzeń drzewa ma zawsze poziom 0. W naszym drzewie węzły B, C i D mają poziom 1, a E, F, G i H mają poziom 2. Wysokość drzewa (ang. tree height) jest równa największemu poziomowi węzłów (lub najdłuższej ścieżce rozpoczynającej się w korzeniu). Dla naszego drzewa wysokość jest równa 2. Wysokość węzła (ang. node height), to długość najdłuższej ścieżki od tego węzła do liścia. Dla korzenia wysokość węzła jest równa wysokości drzewa:

Struktura drzewa   Wysokości węzłów
obrazek   węzeł A: wysokość = 2
węzeł B: wysokość = 1
węzeł C: wysokość = 0
węzeł D: wysokość = 1
węzeł E: wysokość = 0
węzeł F: wysokość = 0
węzeł G: wysokość = 0
węzeł H: wysokość = 0

Poziom drzewa (ang. tree level, the level of a tree) dla danego węzła to długość ścieżki prostej od korzenia do danego węzła.

Struktura drzewa   Poziomy
obrazek   Poziom 0: A
Poziom 1: B, C, D
Poziom 2: E, F, G, H

Liczba krawędzi powiązanych z danym węzłem nosi nazwę stopnia węzła (ang. node degree). Krawędzie drzewa są krawędziami skierowanymi (ang. directed edge) i oznaczamy je za pomocą strzałek. Kierunek strzałki jednoznacznie określa pozycję w hierarchii – strzałka wychodzi od ojca i kończy się na synu. Z tego powodu stopień węzła rozbija się na dwa stopnie:

stopień wejściowy (ang. node in-degree) – liczba krawędzi wchodzących do węzła, dla drzewa nigdy nie przekracza 1, a jest równy 0 tylko dla korzenia,

stopień wyjściowy (ang. node out-degree) – liczba krawędzi wychodzących z węzła, określa liczbę synów.

Stopień węzła jest sumą stopnia wejściowego i wyjściowego.

Struktura drzewa    
obrazek   węzeł A: we = 0, wy = 3, stopień węzła = 3
węzeł B: we = 1, wy = 3, stopień węzła = 4
węzeł C: we = 1, wy = 0, stopień węzła = 1
węzeł D: we = 1, wy = 1, stopień węzła = 2
węzeł E: we = 1, wy = 0, stopień węzła = 1
węzeł F: we = 1, wy = 0, stopień węzła = 1
węzeł G: we = 1, wy = 0, stopień węzła = 1
węzeł H: we = 1, wy = 0, stopień węzła = 1

Zwróć uwagę, że liście nie będące korzeniem (jeśli korzeń jest liściem, to jego stopień wynosi 0) mają zawsze stopień równy 1.


Na początek:  podrozdziału   strony 

Drzewo binarne

Drzewo, w którym węzły mogą posiadać co najwyżej dwóch synów, nazywa się drzewem binarnym (ang. binary tree, B-tree). Węzły potomne nazywamy odpowiednio synem lewym (ang. left child node) i synem prawym (ang. right child node). Drzewa binarne mają ogromne znaczenie w informatyce, ponieważ za ich pomocą można odwzorować również drzewa, których węzły posiadają dowolną liczbę synów – sposób takiego odwzorowania podamy w dalszej części rozdziału.

Drzewo binarne    
obrazek   A – korzeń, ojciec B i C
B, C – synowie A, B – lewy syn A, C – prawy syn A
D, E – liście, synowie B
F, G –  liście, synowie C

W drzewie binarnym stopień każdego węzła nie przekracza 3. Stopień korzenia nie przekracza 2.

Regularne drzewo binarne (ang. regular binary tree, proper binary tree) zawiera wyłącznie węzły, których stopień wyjściowy jest albo równy 2 (węzeł posiada dwóch synów –  jest węzłem wewnętrznym), albo 0 (węzeł nie posiada synów – jest liściem).

Regularne drzewo binarne   Stopnie wyjściowe
obrazek   A – stopień wyjściowy 2, węzeł wewnętrzny
B –  stopień wyjściowy 2, węzeł wewnętrzny
C – stopień wyjściowy 2, węzeł wewnętrzny
D – stopień wyjściowy 0, liść
E – stopień wyjściowy 0, liść
F – stopień wyjściowy 0, liść
G – stopień wyjściowy 0, liść

Dla regularnego drzewa binarnego liczba węzłów na poziomie k-tym jest zawsze równa 2k. Liczba wszystkich węzłów, czyli rozmiar drzewa ( ang. binary tree size) jest równa 2p - 1, gdzie p oznacza liczbę poziomów.

obrazek

Dla n węzłów liczba poziomów jest równa log2 (n+1).

Ponumerujmy poziomami kolejne węzły, idąc od strony lewej do prawej:

obrazek

Otrzymane numery węzłów są powiązane ze strukturą hierarchii drzewa prostymi zależnościami:

obrazek Jeśli węzeł o numerze k posiada synów, to:

lewy syn ma numer 2k+1
prawy syn ma numer 2k+2
obrazek   Jeśli węzeł o numerze k posiada ojca, to:

ojciec ma numer [(k-1) / 2]

[…] oznacza część całkowitą.

Węzeł o numerze k znajduje się na poziomie o numerze [log 2 (k+1)].

Węzeł o numerze k jest wewnętrzny, jeśli 2k+2 < n. W przeciwnym razie węzeł jest liściem.

Własności te pozwalają odwzorowywać regularne drzewo binarne w ciąg elementów i na odwrót

obrazek

Kompletne drzewo binarne (ang. complete binary tree) posiada zapełnione węzłami wszystkie poziomy z wyjątkiem ostatniego, jednakże na ostatnim poziomie węzły są zapełnione począwszy od lewej strony.

Kompletne drzewo binarne   Niekompletne drzewo binarne
obrazek obrazek

Kompletne drzewo binarne również da się odwzorować w ciąg węzłów. W takim drzewie liczba elementów n może być mniejsza od maksymalnej liczby węzłów, ponieważ ostatni poziom nie musi posiadać kompletu węzłów. Jednakże w przeciwieństwie do drzewa regularnego węzeł wewnętrzny może posiadać tylko jednego, lewego syna (u nas węzłem takim jest węzeł 4). Dlatego w kompletnym drzewie binarnym o rozmiarze n dla węzła o numerze k zachodzi:

2k + 2 > n – węzeł jest liściem
2k + 2 = n – węzeł jest ostatnim węzłem wewnętrznym i posiada tylko lewego syna
2k + 2 < n – węzeł jest węzłem wewnętrznym i posiada obu synów.

Poddrzewo (ang. subtree) jest drzewem zawartym w drzewie, gdy jako korzeń przyjmiemy jeden z węzłów. Dla danego węzła drzewa binarnego mogą istnieć dwa poddrzewa: lewe poddrzewo (ang. left subtree) – korzeniem jest lewy syn i analogicznie prawe poddrzewo (ang. right subtree) – korzeniem jest prawy syn:

obrazek


Na początek:  podrozdziału   strony 

Reprezentacja drzew binarnych

Istnieje wiele różnych rozwiązań dla reprezentacji drzew binarnych w pamięci komputera. Tutaj podamy te najprostsze.

Kompletne drzewo binarne

W tym przypadku drzewo możemy odwzorować w tablicy n-elementowej. Każdy element tablicy jest węzłem. Hierarchię drzewa przedstawiamy przy pomocy indeksów i ich własności dla kompletnych drzew binarnych. Korzeniem drzewa jest element o indeksie 0. Jego dwoma synami są kolejno elementy o indeksach 1 (lewy syn) i 2 (prawy syn). Postępując podobnie z pozostałymi węzłami otrzymamy całe drzewo binarne:

obrazek

obrazek

Niekompletne drzewo binarne

Drzewo odwzorowujemy podobnie jak listę. Każdy element jest strukturą, która oprócz danych zawiera dwa lub trzy wskaźniki:

Wersja uproszczona

Pascal
type
  PBTNode = ^BTNode;
  BTNode  = record
    left  : PBTNode;
    right : PBTNode;
    data  : typ_danych;
  end;
C++
struct BTNode
{
  BTNode * left;
  BTNode * right;
  typ_danych data;
};
Basic
Type BTNode
  left As BTNode Ptr
  right As BTNode Ptr
  data As typ_danych
End Type

Wersja pełna

Pascal
type
  PBTNode = ^BTNode;
  BTNode  = record
    up    : PBTNode;
    left  : PBTNode;
    right : PBTNode;
    data  : typ_danych;
  end;
C++
struct BTNode
{
  BTNode * up;
  BTNode * left;
  BTNode * right;
  typ_danych data;
};
Basic
Type BTNode
  up As BTNode Ptr
  left As BTNode Ptr
  right As BTNode Ptr
  data As typ_danych
End Type

Gdzie:

up – wskazuje ojca danego węzła. Dla korzenia pole to zawiera wskazanie puste
left – wskazuje lewego syna
right – wskazuje prawego syna
data – dane dowolnego typu przechowywane przez węzeł

Wskaźniki pozwalają na przemieszczanie się po węzłach w strukturze drzewa. Wskaźniki left i right umożliwiają przechodzenie w dół drzewa. Wskaźnik up prowadzi w górę do ojca danego węzła. Jeśli ten kierunek nie jest istotny, to wskaźnik może zostać pominięty (wersja uproszczona).


Na początek:  podrozdziału   strony 

Reprezentacja drzew dowolnych

Drzewo dowolne może posiadać węzły o dowolnej liczbie synów. Jeśli liczba możliwych węzłów potomnych nie jest duża, to do reprezentacji takiego drzewa można wykorzystać metodę z drzewa binarnego, zwiększając odpowiednio liczbę wskaźników. Na przykład dla drzew czwórkowych (ang. quadtree) możemy zaimplementować następującą strukturę danych:

Pascal
type
  PQuadTreeNode = ^QuadTreeNode;
  QuadTreeNode  = record
    up    : PQuadTreeNode;
    ne    : PQuadTreeNode;
    nw    : PQuadTreeNode;
    se    : PQuadTreeNode;
    sw    : PQuadTreeNode;
    data  : typ_danych;
  end;
C++
struct QuadTreeNode
{
  QuadTreeNode * up;
  QuadTreeNode * ne;
  QuadTreeNode * nw;
  QuadTreeNode * se;
  QuadTreeNode * sw;
  typ_danych data;
};
Basic
Type QuadTreeNode
  up As QuadTreeNode Ptr
  ne As QuadTreeNode Ptr
  nw As QuadTreeNode Ptr
  se As QuadTreeNode Ptr
  sw As QuadTreeNode Ptr
  data As typ_danych
End Type

Gdzie:

up – wskazuje ojca danego węzła. Dla korzenia pole to zawiera wskazanie puste
ne – wskazuje syna północnowschodniego
nw – wskazuje syna północnozachodniego
se – wskazuje syna południowowschodniego
sw – wskazuje syna południowozachodniego
data – dane dowolnego typu przechowywane przez węzeł

Gdy liczba synów jest duża, to rezerwowanie w każdym węźle pól na wskaźniki przestaje być efektywne. Zamiast prostych pól możemy umieścić w każdym węźle tablicę dynamiczną o wymaganym rozmiarze, której każdy element jest wskaźnikiem do syna danego węzła. Do obsługi takiej struktury będzie potrzebna jeszcze informacja o liczbie elementów w tablicy. Dodatkowo musimy pamiętać o zwolnieniu tablic dynamicznych, gdy drzewo jest usuwane z pamięci.

Pascal
type
  PAnyTreeNode = ^AnyTreeNode;
  TAnyTreeNode = array of PAnyTreeNode;
  AnyTreeNode = record
    up    : PAnyTreeNode;
    child : TAnyTreeNode;
    n     : Integer;
    data  : typ_danych;
  end;
C++
struct AnyTreeNode
{
  AnyTreeNode * up;
  AnyTreeNode * child;
  int n;
  typ_danych data;
};
Basic
Type AnyTreeNode
  up As AnyTreeNode Ptr
  child As AnyTreeNode Ptr
  n As Integer
  data As typ_danych
End Type

Gdzie:

up – wskazuje ojca danego węzła. Dla korzenia pole to zawiera wskazanie puste
child – wskazuje tablicę zawierającą wskazania do węzłów synów
n – określa liczbę synów
data – dane dowolnego typu przechowywane przez węzeł

Alternatywnym rozwiązaniem jest zastosowanie listy jednokierunkowej, której elementy przechowują wskazania synów danego węzła. Wymaga to dołączenia do programu metod obsługi takiej listy, a najlepiej zastosowanie odpowiedniego obiektu.

Pascal
type
  PAnyTreeNode = ^AnyTreeNode;
  PslistEl = ^slistEl;
  slistEl =  record
    next : PslistEl;
    node : PAnyTreeNode;
  end;

  AnyTreeNode = record
    up    : PAnyTreeNode;
    child : PsListEl;
    data  : typ_danych;
  end;
C++
typedef struct AnyTreeNode PAnyTreeNode;

struct slistEl
{
  slistEl * next;
  PAnyTreeNode * node;
};

struct AnyTreeNode
{
  AnyTreeNode * up;
  slistEl * child;
  typ_danych data;
};
Basic
Type PAnyTreeNode As AnyTreeNode

Type slistEl
  next As slistEl Ptr
  node As PAnyTreeNode Ptr
End Type

Type AnyTreeNode
  up As AnyTreeNode Ptr
  child As slistEl Ptr
  data As typ_danych
End Type

Zwróć uwagę, że w powyższym rozwiązaniu występują tzw. odwołania krzyżowe. Polegają one na tym, iż element jednej struktury odwołuje się do innej struktury, która z kolei odwołuje sie do tej pierwszej. Elementy listy zawierają wskazania węzłów drzewa w polu node. Z kolei węzły drzewa w polu child wskazują listę. Obie struktury odwołują się do siebie nawzajem. Konieczne jest utworzenie pomocniczego typu danych PAnyTreeNode i użycie go w definicji listy. Nie można tutaj zastosować typu AnyTreeNode, ponieważ nie jest on w tym miejscu jeszcze zdefiniowany. Natomiast typ pomocniczy PAnyTreeNode informuje kompilator, że właściwa definicja zostanie podane później w programie.

Ostatnia metoda reprezentacji dowolnych drzew wykorzystuje drzewo binarne w pewien szczególny sposób. Zasada jest następująca:

Każdy węzeł drzewa dowolnego jest reprezentowany przez odpowiedni węzeł drzewa binarnego. Różnica występuje w interpretacji krawędzi. Otóż w drzewie binarnym lewy syn węzła oznacza pierwszego syna węzła w drzewie dowolnym. Prawy syn oznacza zawsze kolejnego brata, czyli węzeł posiadający tego samego ojca. Bracia tworzą listę prawych dowiązań. Prześledźmy krok po kroku przekształcenie drzewa dowolnego na drzewo binarne.
Drzewo dowolne Drzewo binarne Opis operacji
obrazek ? Konstruujemy drzewo binarne.
obrazek obrazek Korzeń drzewa dowolnego przechodzi w korzeń drzewa binarnego.
obrazek obrazek Lewy syn węzła A w drzewie dowolnym staje się lewym synem węzła A w drzewie binarnym.
obrazek obrazek Syn C węzła A staje się "prawym synem" węzła B w drzewie binarnym. jednakże tutaj prawi synowie są interpretowani jako bracia tego samego ojca, czyli węzła A.
obrazek obrazek Syn D węzła A staje się "prawym synem" węzła C, czyli jego bratem. Zwróć uwagę, że bracia tworzą w ten sposób listę jednokierunkową za pomocą prawych dowiązań.
obrazek obrazek Syn E węzła B w drzewie dowolnym staje się lewym synem węzła B w drzewie binarnym. Zwróć uwagę, że węzeł B wyczerpał już dostępne dla niego krawędzie wyjściowe, których może posiadać co najwyżej dwie.
obrazek obrazek Węzły F i G dołączamy kolejno jako prawych braci węzła E. Wszystkie trzy worzą listę jednokierunkową.
obrazek obrazek Węzeł H dołączamy jako lewego syna węzła C.
obrazek obrazek Węzeł I dołączamy jako lewego syna węzła D.
obrazek obrazek Węzeł J dołączamy jako prawego brata węzła I. Drzewo binarne zostało utworzone.
obrazek obrazek

Podana transformacja pozwala przedstawić dowolne drzewo jako drzewo binarne. Zmianie ulega tutaj interpretacja krawędzi:

Lewy syn jest pierwszym synem węzła. Jeśli węzeł nie posiada lewego syna, to jest liściem.

Prawy syn jest bratem węzła, czyli węzłem posiadającym tego samego ojca. W strukturze wszystkich węzłów dołączonych poprzez prawe krawędzie pole up wskazuje ten sam węzeł nadrzędny. Prawostronne dowiązania tworzą listę jednokierunkową braci. Pierwszy element tej listy jest zawsze lewym synem ojca wszystkich węzłów na liście. Jeśli węzeł nie posiada prawego syna, to jest on ostatnim węzłem na liście braci.

Wynika stąd prosty wniosek – skoro każde drzewo da się sprowadzić do drzewa binarnego, to wystarczy opracować dobre metody przetwarzania drzew binarnych, aby móc przetwarzać dowolne drzewa. Dlatego właśnie drzewa binarne są tak ważne w informatyce.


Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2024 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.