Szczególnym elementem drzewa jest korzeń (ang. root) czyli element nie posiadający rodzica (elementu nadrzędnego). Dodatkowe pojęcia związane z drzewem to liść (element nie posiadający dzieci), stopień węzła (liczba potomków węzła), elementy siostrzane węzła (elementy mające tego samego rodzica co dany węzeł), wysokość węzła (długość najdłuższej ścieżki od danego węzła do liścia), poziom węzła (długość ścieżki od korzenia do danego węzła). Patrząc na powyższy diagram możemy ustalić, że każdy węzeł:
- przechowuje jakieś dane,
- posiada referencję do węzła będącego jego rodzicem (w przypadku korzenia będzie to wartość null),
- ma listę swoich dzieci (czyli listę referencji do węzłów będących jego potomkami).
Wykorzystując typy generyczne (pisałem o nich wcześniej: Typy generyczne) oraz listę powiązaną (też wspominałem: LinkedList przykład wykorzystania) jesteśmy w stanie stosunkowo łatwo zbudować klasę odpowiadającą pojedynczemu węzłowi:
class Node<T> { // dane przechowywane w węźle private T data; // referencja do rodzica private Node<T> parent; // lista dzieci private LinkedList<Node<T>> children; }
Dodajemy potrzebne konstruktory, domyślny (węzeł bez rodzica i z pustą listą dzieci), jednoparametrowy (węzeł z ustawionym rodzicem) i dwuparametrowy (węzeł z rodzicem i danymi):
public Node() { // brak referencji do rodzica parent = null; // tworzymy pustą listę dzieci children = new LinkedList<Node<T>>(); } public Node(Node<T> parent) { // wywołujemy konstruktor domyślny this(); // ustawiamy rodzica this.parent = parent; } public Node(Node<T> parent, T data) { // wywołujemy konstruktor jednoparametrowy this(parent); // ustawiamy dane this.data = data; }
Dodajmy również do klasy podstawowe metody dostępowe i modyfikujące dla danych i rodzica:
public Node<T> getParent(){ // zwracamy referencję do rodzica return parent; } public void setParent(Node<T> parent) { // ustawiamy rodzica this.parent = parent; } public T getData() { // zwracamy przechowywane dane return data; } public void setData(T data) { // ustawiamy dane this.data = data; }
Stopień węzła, czyli liczbę jego potomków możemy łatwo określić zwracając rozmiar listy przechowującej referencję dzieci.
public int getDegree() { // zwracamy rozmiar listy przechowującej potomków węzła return children.size(); }
Tak samo sprawdzenie czy dany węzeł jest liściem (ang. leaf). Jeśli jego lista dzieci jest pusta, jest liściem.
public boolean isLeaf(){ // jeśli lista dzieci jest pusta zwracamy true (węzeł jest liściem) return children.isEmpty(); }
Do węzła możemy dodawać dzieci. Jeden sposób to dodanie do listy potomków referencji węzła przekazanego jako parametr. Musimy pamiętać o ustawieniu w "dziecku" naszego "ojcostwa" (węzeł będący na liście naszych dzieci musi wiedzieć, że jesteśmy jego rodzicem).
public Node<T> addChild(Node<T> child) { // ustawiamy w dziecku rodzica (wskazujemy na nas) child.setParent(this); // dodajemy dziecko do listy naszych dzieci children.add(child); // zwracamy dziecko return child; }
Drugi sposób wygodniejszy podczas tworzenia struktury drzewa to dodatnie do węzła dziecka z określonymi danymi. Nasza metoda utworzy na podstawie przekazanych danych nowy węzeł oraz doda go do listy jego dzieci.
public Node<T> addChild(T data) { //tworzymy nowy węzeł (ustawiamy od razu rodzica i dane) Node<T> child = new Node<T>(this, data); // dopisujemy węzeł do listy naszych dzieci children.add(child); // zwracamy dziecko return child; }
Dodatkowe operacje związane z dziećmi to pobranie i-tego dziecka z listy, usunięcia konkretnego dziecka, oraz usunięcie wszystkich dzieci węzła. Wykorzystamy metody dostępne w klasie LinkedList.
public Node<T> getChild(int i){ // zwracamy referencję do i-tego dziecka (metoda get(int) z klasy LinkedList) return children.get(i); } public Node<T> removeChild(int i) { // usuwamy i-te dziecko (metoda remove(int) z klasy LinkedList) return children.remove(i); } public void removeChildren() { // usuwamy wszystkie nasze dzieci, czyścimy listę(metoda clear() z klasy LinkedList) children.clear(); }
Możemy również napisać metodę dostępową zwracającą listę dzieci węzła.
public LinkedList<Node<T>> getChildren() { // zwracamy referencję do listy naszych dzieci return children; }
Pozostają nam jeszcze dwie metody, które przydadzą się do implementacji sposobów trawersowania drzewa. Pierwsza z nich zwróci nam najbardziej wysunięte w lewo dziecko węzła (czyli pierwszego potomka z listy o ile tylko taki istnieje).
public Node<T> getLeftMostChild() { // jeśli nie mamy dzieci zwrócimy null if (children.isEmpty()) return null; // w przciwnym wypadku pierwsze dziecko z listy naszych dzieci return children.get(0); }
Druga zwraca prawy element siostrzany (o ile istnieje) węzła.
public Node<T> getRightSibling() { // jeśli nie jesteśmy korzeniem if (parent != null) { // pobieramy listę dzieci naszego rodzica LinkedList<Node<T>> parentsChildren = parent.getChildren(); // szukamy się na tej liście int pos = parentsChildren.indexOf(this); // sprawdzamy czy są jeszcze jakieś dzieci za nami if (pos < (parentsChildren.size()-1)) // jesli tak zwracamy kolejne dziecko return parentsChildren.get(pos+1); } // zwracamy null jak węzeł jest korzeniem, albo nie ma już elementu siostrzanego po prawej return null; }
Ostatnią częścią naszej klasy będzie przesłonięcie metody toString(). Zwrócimy po prostu dane przechowywane w węźle.
public String toString() { // zwracamy dane w formie łańcucha tekstowego return data.toString(); }
Poniżej cała gotowa klasa Node<T> opisująca węzeł naszego drzewa (komentarze zostały pominięte).
/** * Klasa Node<T> - węzeł drzewa * @author kodatnik.blogspot.com */ class Node<T> { private T data; private Node<T> parent; private LinkedList<Node<T>> children; public Node() { parent = null; children = new LinkedList<Node<T>>(); } public Node(Node<T> parent) { this(); this.parent = parent; } public Node(Node<T> parent, T data) { this(parent); this.data = data; } public Node<T> getParent(){ return parent; } public void setParent(Node<T> parent) { this.parent = parent; } public T getData() { return data; } public void setData(T data) { this.data = data; } public int getDegree() { return children.size(); } public boolean isLeaf(){ return children.isEmpty(); } public Node<T> addChild(Node<T> child) { child.setParent(this); children.add(child); return child; } public Node<T> addChild(T data) { Node<T> child = new Node<T>(this, data); children.add(child); return child; } public Node<T> getChild(int i){ return children.get(i); } public Node<T> removeChild(int i) { return children.remove(i); } public void removeChildren() { children.clear(); } public LinkedList<Node<T>> getChildren() { return children; } public Node<T> getLeftMostChild() { if (children.isEmpty()) return null; return children.get(0); } public Node<T> getRightSibling() { if (parent != null) { LinkedList<Node<T>> parentsChildren = parent.getChildren(); int pos = parentsChildren.indexOf(this); if (pos < (parentsChildren.size()-1)) return parentsChildren.get(pos+1); } return null; } public String toString() { return data.toString(); } }
Ostatnią czynnością będzie zbudowanie konkretnego drzewa. Sam proces sprowadza się do utworzenia odpowiednich węzłów, połączenie ich w strukturę hierarchiczną oraz wskazania węzła będącego korzeniem. Nasza klasa będzie posiadała tylko jedno pole, referencję do węzła będącego korzeniem. Dwa konstruktory, domyślny puste drzewo i jednoparametrowy ustawiający referencję do korzenia.
/** * Klasa Tree<T> - drzewo * @author kodatnik.blogspot.com */ class Tree<T> { // referencja do węzła będącego korzeniem private Node<T> root; public Tree() { // brak korzenia root = null; } public Tree(Node<T> root) { // ustawiamy korzeń this.root = root; } }
Wykorzystajmy gotowe klasy Node<T> oraz Tree<T> do budowy drzewa z diagramu.
public class NaszeDrzewo { public static void main(String args[]) { // tworzymy węzeł będący korzeniem Node<String> korzen = new Node<String>(null, "Adam"); // dodajemy do niego kolejne węzły Node<String> n1 = korzen.addChild("Ewa"); Node<String> n2 = korzen.addChild("Jarek"); Node<String> n3 = korzen.addChild("Marta"); n1.addChild("Jurek"); n1.addChild("Max"); n1.addChild("Iza"); n2.addChild("Iwona"); n3.addChild("Rafał"); n3.addChild("Ola"); // tworzymy drzewo i wskazujemy, który węzeł jest korzeniem Tree<String> drzewo = new Tree<String>(korzen); } }
Zbudowaliśmy strukturę drzewa. Pozostaje wyświetlenie naszego drzewa na ekranie oraz przedstawienie sposobów poruszania się po drzewie. To wszystko w następnej części...
4 Komentarze - Drzewo - ogólna implementacja
Szukając implementacji drzewiastej warto zajrzeć jeszcze tutaj: http://java.sun.com/docs/books/tutorial/uiswing/components/tree.html
Tak, tylko, że klasa JTree jest głównie wykorzystywana w projektowaniu GUI (Swing), brakuje paru elementów, nie wszystko jest tam proste i oczywiste, chociaż przyznaje, można ją wykorzystać do utworzenia struktury drzewa :)
Zacny artykuł, ułatwi mi pracę nad jednym z projektów i pozwoli zaoszczędzić czas który musiałbym inaczej spożytkować na tworzenie i wymyślanie tej klasy od podstaw. Pozdrawiam
swietny blog dzieki
Prześlij komentarz
Możesz użyć niektórych tagów HTML, takich jak <b>, <i>, <u>, <a> Nie spamuj :)