Wykład 8 - Drzewa i algorytmy ich przetwarzania

Podobne dokumenty
Wysokość drzewa Głębokość węzła

ZASADY PROGRAMOWANIA KOMPUTERÓW ZAP zima 2014/2015. Drzewa BST c.d., równoważenie drzew, kopce.

Drzewa binarne. Drzewo binarne to dowolny obiekt powstały zgodnie z regułami: jest drzewem binarnym Jeśli T 0. jest drzewem binarnym Np.

Struktury danych: stos, kolejka, lista, drzewo

Wykład 2. Drzewa zbalansowane AVL i 2-3-4

Drzewo. Drzewo uporządkowane ma ponumerowanych (oznaczonych) następników. Drzewo uporządkowane składa się z węzłów, które zawierają następujące pola:

Wstęp do programowania

Podstawy programowania 2. Temat: Drzewa binarne. Przygotował: mgr inż. Tomasz Michno

Algorytmy i struktury danych

WSTĘP DO INFORMATYKI. Drzewa i struktury drzewiaste

Teoretyczne podstawy informatyki


Algorytmy i Struktury Danych

Przypomnij sobie krótki wstęp do teorii grafów przedstawiony na początku semestru.

Sortowanie - wybrane algorytmy

Kolejka priorytetowa. Często rozważa się kolejki priorytetowe, w których poszukuje się elementu minimalnego zamiast maksymalnego.

Teoretyczne podstawy informatyki

Stos LIFO Last In First Out

Wykład 8. Drzewa AVL i 2-3-4

Wykład 6. Drzewa poszukiwań binarnych (BST)

Listy, kolejki, stosy

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Programowanie obiektowe

STRUKTURY DANYCH I ZŁOŻONOŚĆ OBLICZENIOWA STRUKTURY DANYCH I ZŁOŻONOŚĆ OBLICZENIOWA. Część 3. Drzewa Przeszukiwanie drzew

Podstawowe pojęcia dotyczące drzew Podstawowe pojęcia dotyczące grafów Przykłady drzew i grafów

Algorytmy i Struktury Danych

liniowa - elementy następują jeden za drugim. Graficznie możemy przedstawić to tak:

Algorytmy i Struktury Danych

Programowanie obiektowe

prowadzący dr ADRIAN HORZYK /~horzyk tel.: Konsultacje paw. D-13/325

Wykład 3. Złożoność i realizowalność algorytmów Elementarne struktury danych: stosy, kolejki, listy

Algorytmy i struktury danych. Drzewa: BST, kopce. Letnie Warsztaty Matematyczno-Informatyczne

dr inż. Paweł Myszkowski Wykład nr 11 ( )

Wykład 2. Drzewa poszukiwań binarnych (BST)

Ogólne wiadomości o grafach

Drzewa czerwono-czarne.

ALGORYTMY I STRUKTURY DANYCH

Lista liniowa dwukierunkowa

Podstawy Informatyki. Wykład 6. Struktury danych

Algorytmy i Struktury Danych.

Algorytmy i Struktury Danych. Co dziś? Drzewo decyzyjne. Wykład IV Sortowania cd. Elementarne struktury danych

< K (2) = ( Adams, John ), P (2) = adres bloku 2 > < K (1) = ( Aaron, Ed ), P (1) = adres bloku 1 >

Sortowanie bąbelkowe

Algorytmy i. Wykład 5: Drzewa. Dr inż. Paweł Kasprowski

Porządek symetryczny: right(x)

Tadeusz Pankowski

Struktury Danych i Złożoność Obliczeniowa

dodatkowe operacje dla kopca binarnego: typu min oraz typu max:

Każdy węzeł w drzewie posiada 3 pola: klucz, adres prawego potomka i adres lewego potomka. Pola zawierające adresy mogą być puste.

Matematyka dyskretna - 7.Drzewa

. Podstawy Programowania 2. Drzewa bst - część pierwsza. Arkadiusz Chrobot. 22 maja 2016

Drzewa poszukiwań binarnych

Drzewo binarne BST. LABORKA Piotr Ciskowski

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Algorytmy i struktury danych Laboratorium 7. 2 Drzewa poszukiwań binarnych

ALGORYTMY I STRUKTURY DANYCH

Grafy (3): drzewa. Wykłady z matematyki dyskretnej dla informatyków i teleinformatyków. UTP Bydgoszcz

ALGORYTMY I STRUKTURY DANYCH

Wykład X. Programowanie. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej. c Copyright 2016 Janusz Słupik

Drzewa poszukiwań binarnych

Algorytmy i struktury danych. wykład 5

Wykład 8. Drzewo rozpinające (minimum spanning tree)

Drzewa BST i AVL. Drzewa poszukiwań binarnych (BST)

TEORETYCZNE PODSTAWY INFORMATYKI

Zofia Kruczkiewicz, Algorytmu i struktury danych, Wykład 14, 1

Drzewa wyszukiwań binarnych (BST)

Sortowanie. Kolejki priorytetowe i algorytm Heapsort Dynamiczny problem sortowania:

Podstawy Informatyki. Metody dostępu do danych

TEORETYCZNE PODSTAWY INFORMATYKI

Metody Kompilacji Wykład 3

Abstrakcyjne struktury danych - stos, lista, drzewo

PLAN WYKŁADU BAZY DANYCH INDEKSY - DEFINICJE. Indeksy jednopoziomowe Indeksy wielopoziomowe Indeksy z użyciem B-drzew i B + -drzew

Wstęp do programowania. Drzewa podstawowe techniki. Piotr Chrząstowski-Wachtel

0-0000, , , itd

. Podstawy Programowania 2. Drzewa bst - część druga. Arkadiusz Chrobot. 12 maja 2019

BAZY DANYCH. Microsoft Access. Adrian Horzyk OPTYMALIZACJA BAZY DANYCH I TWORZENIE INDEKSÓW. Akademia Górniczo-Hutnicza

Wyszukiwanie w BST Minimalny i maksymalny klucz. Wyszukiwanie w BST Minimalny klucz. Wyszukiwanie w BST - minimalny klucz Wersja rekurencyjna

Sortowanie. Bartman Jacek Algorytmy i struktury

Wstęp do programowania

Drzewa podstawowe poj

Wykład 5. Sortowanie w czasie liniowologarytmicznym

Wstęp do programowania. Drzewa. Piotr Chrząstowski-Wachtel

Programowanie obiektowe i C++ dla matematyków

Indeksy. Wprowadzenie. Indeksy jednopoziomowe indeks podstawowy indeks zgrupowany indeks wtórny. Indeksy wielopoziomowe

Wykład 3. Drzewa czerwono-czarne

Wykłady z Matematyki Dyskretnej

Rekurencja. Dla rozwiązania danego problemu, algorytm wywołuje sam siebie przy rozwiązywaniu podobnych podproblemów. Przykład: silnia: n! = n(n-1)!

Kompresja danych Streszczenie Studia Dzienne Wykład 10,

7. Teoria drzew - spinanie i przeszukiwanie

Notacja RPN. 28 kwietnia wyliczanie i transformacja wyrażeń. Opis został przygotowany przez: Bogdana Kreczmera.

Koszt zamortyzowany. Potencjał - Fundusz Ubezpieczeń Kosztów Algorytmicznych

Ogólne wiadomości o drzewach

Algorytmy sortujące i wyszukujące

Wykład 5 Wybrane zagadnienia programowania w C++ (c.d.)

Laboratorium z przedmiotu Programowanie obiektowe - zestaw 04

Wstęp do programowania

Podstawowe algorytmy i ich implementacje w C. Wykład 9

PODSTAWY INFORMATYKI wykład 6.

Poprawność semantyczna

Programowanie w VB Proste algorytmy sortowania

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Transkrypt:

Algorytmy i struktury danych Wykład 8 - Drzewa i algorytmy ich przetwarzania Janusz Szwabiński Plan wykładu: Przykłady drzew Pojęcia i definicje Reprezentacje drzew Drzewa wyprowadzenia (ang. parse trees) Przechodzenie drzewa Kopiec binarny Kolejka priorytetowa Źródła: większość ilustracji i przykładów pochodzi z "Problem Solving with Algorithms and Data Structures using Python", http://interactivepython.org/runestone/static/pythonds/index.html Przykłady drzew Systematyka biologiczna

System plików

In [7]:!tree -d -L 2 /home/szwabin/dropbox/zajecia/ /home/szwabin/dropbox/zajecia/ AlgorytmyIStrukturyDanych Lab Materiały Cwiczenia PythonIntro PythonIntro PWr UWr WstepDoProgramowania Cwiczenia Egzamin Laboratoria Przykłady Wyklady 14 directories

Drzewo znaczników HTML <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>simple</title> </head> <body> <h1>a simple web page</h1> <ul> <li>list item one</li> <li>list item two</li> </ul> <h2> <a href="http://wmat.pwr.edu.pl/index.dhtml">wydział Matematyki PWr </a> <h2> </body> </html> Drzewa składniowe

Pojęcia i definicje Węzeł lub wierzchołek (ang. node) - podstawowy element drzewa: może mieć nazwę, czyli klucz (ang. key) może zawierać dodatkowe dane (ang. payload) dane te nie mają z reguły znaczenia dla algorytmów dotyczących drzew, są jednak bardzo ważne w zastosowaniach Krawędź (ang. edge) - kolejny ważny element drzewa: łączy ze sobą wierzchołki, aby zaznaczyć relację między nimi każdy wierzchołek (oprócz korzenia) ma jedną krawędź wchodzącą, łączącą go z rodzicem każdy wierzchołek może mieć wiele krawędzi wychodzących Korzeń (ang. root) - jedyny wierzchołek, który nie ma krawędzi wchodzącej (nie ma rodzica) Ścieżka (ang. path) - uporządkowana lista wierzchołków z łączącymi ich krawędziami: Mammal Carnivora Felidae Felis Domestica Dziecko (ang. child, child node) - węzeł połączony krawędzią wchodzącą z innym węzłem jest jego dzieckiem Rodzic (ang. parent) - węzeł połączony krawędzią wychodzącą z innym węzłem jest jego rodzicem Rodzeństwo (ang. siblings) - wszystkie węzły mające tego samego rodzica Potomek (ang. descendant) - potomkami węzła nazywamy wszystkie węzły, do których prowadzi od niego ścieżka Poddrzewo (ang. subtree) - zbiór węzłów i krawędzi złożony z wybranego węzła (rodzica) i wszystkich jego potomków Liść (ang. leaf) - wierzchołek, który nie ma dzieci Długość (ang. length) - liczba krawędzi w ścieżce od korzenia do węzła Poziom (ang. level) - to inaczej długość od korzenia Wysokość (ang. height) - najwyższy poziom istniejący w drzewie

Drzewo - definicja 1 zbiór wierzchołków i krawędzi łączących pary węzłów własności: jeden wyróżniony węzeł - korzeń, który nie ma rodzica każdy węzeł n (z wyjątkiem korzenia) jest połączony dokładnie jedną krawędzią wchodzącą z węzłem p. Węzeł p jest rodzicem węzła n istnieje tylko jedna ścieżka łącząca dowolny węzeł z korzeniem jeśli każdy węzeł może mieć co najwyżej 2 dzieci, mówimy o drzewach binarnych Drzewo - definicja 2 drzewo jest albo puste albo zawiera korzeń i pewną liczbą poddrzew, z których każde jest drzewem korzeń poddrzewa jest połaczony z korzeniem drzewa głównego krawędzią

Reprezentacje drzew Lista list na podstawie definicji rekurencyjnej wbudowane listy w Pythonie pierwszy element listy to korzeń, drugi to lewe poddrzewo, trzeci - prawe poddrzewo In [3]: mytree = ['a', #root ['b', #left subtree ['d', [], []], #left subtree of the left subtree ['e', [], []] ], #right subtree of the left subtree ['c', #right subtree ['f', [], []], #left subtree of the right subtree [] ] #right subtree of the right subtree (empty) ] In [4]: mytree Out[4]: ['a', ['b', ['d', [], []], ['e', [], []]], ['c', ['f', [], []], []]] do poszczególnych elementów odnosimy się przy pomocy standardowych indeksów list korzeń to mytree[0] lewe poddrzewo to mytree[1], prawe - mytree[2] In [5]: print(mytree) print('left subtree = ', mytree[1]) print('root = ', mytree[0]) print('right subtree = ', mytree[2]) ['a', ['b', ['d', [], []], ['e', [], []]], ['c', ['f', [], []], []]] left subtree = ['b', ['d', [], []], ['e', [], []]] root = a right subtree = ['c', ['f', [], []], []] poddrzewa odczytujemy w ten sam sposób reprezentacja ma strukturę rekurencyjną In [6]: print('root of left subtree = ', mytree[1][0]) print('left subtree of the left subtree = ',mytree[1][1]) print('right subtree of the left subtree = ',mytree[1][2]) root of left subtree = b left subtree of the left subtree = ['d', [], []] right subtree of the left subtree = ['e', [], []] podrzewo, które ma korzeń i dwie puste listy to po prostu liść reprezentację można uogólnić do większej liczby potomków - każde kolejne poddrzewo to następna lista

Konstruktor In [18]: def BinaryTree(r): return [r, [], []] Wstawianie elementów aby dodać lewe poddrzewo, musimy wstawić nową listę w miejsce drugiego elementu listy reprezentującej drzewo jeśli w liście nadrzędnej jest już jakiś obiekt na drugiej pozycji: ściągamy go z listy i zapamiętujemy wartość wstawiamy poddrzewo zapamiętany element wstawiamy jako lewe dziecko tego poddrzewa tak zdefiniowana funkcja pozwoli nam wstawić poddrzewo w dowolnym wierzchołku drzewa nadrzędnego In [19]: def insertleft(root,newbranch): t = root.pop(1) if len(t) > 1: root.insert(1,[newbranch,t,[]]) root.insert(1,[newbranch, [], []]) return root funkcja wstawiająca prawe poddrzewo jest bardzo podobna: In [20]: def insertright(root,newbranch): t = root.pop(2) if len(t) > 1: root.insert(2,[newbranch,[],t]) root.insert(2,[newbranch,[],[]]) return root Inne przydatne funkcje In [21]: def getrootval(root): return root[0] def setrootval(root,newval): root[0] = newval def getleftchild(root): return root[1] def getrightchild(root): return root[2]

In [31]: r = BinaryTree(3) print("initial tree: ",r) insertleft(r,4) insertleft(r,5) insertright(r,6) insertright(r,7) print("tree after insertions: ",r) l = getleftchild(r) print("left subtree: ",l) setrootval(l,9) print("left subtree after change of root: ",l) insertleft(l,11) print("tree after insertions: ",r) print("right child of right child: ",getrightchild(getrightchild(r))) Initial tree: [3, [], []] Tree after insertions: [3, [5, [4, [], []], []], [7, [], [6, [], []]]] Left subtree: [5, [4, [], []], []] Left subtree after change of root: [9, [4, [], []], []] Tree after insertions: [3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]] Right child of right child: [6, [], []] Reprezentacja przy pomocy węzłów i referencji reprezentacja ta przypomina nieco omawianą wcześniej listę jednokierunkową tworzymy klasę, która przechowuje korzeń oraz referencje do poddrzew podobnie, jak w poprzedniej reprezentacji, struktura rekurencyjna In [32]: class BinaryTree: def init (self,rootobj): self.key = rootobj self.leftchild = None self.rightchild = None atrybuty leftchild i rightchild staną się referencjami do poddrzew rootobj w konstruktorze to referencja do dowolnego obiektu korzystając z rekurencyjnej definicji drzewa, przykład powyżej wymagałby stworzenia 6 instancji klasy BinaryTree Wstawianie elementów aby dodać lewe dziecko do drzewa, stworzymy nową instancję drzewa binarnego, a następnie wykorzystamy atrybut leftchild, aby odnieść się do niego

In [33]: class BinaryTree: def init (self,rootobj): self.key = rootobj self.leftchild = None self.rightchild = None def insertleft(self,newnode): if self.leftchild == None: self.leftchild = BinaryTree(newNode) t = BinaryTree(newNode) t.leftchild = self.leftchild self.leftchild = t uwzględniliśmy tutaj dwa przypadki: 1. korzeń drzewa nadrzędnego nie ma lewego dziecka - wtedy po prostu je wstawiamy 2. wstawiamy nowe poddrzewo w miejsce istniejącego, przesuwając to ostatnie na wyższy poziom prawe poddrzewo wstawiamy podobnie: In [34]: class BinaryTree: def init (self,rootobj): self.key = rootobj self.leftchild = None self.rightchild = None def insertleft(self,newnode): if self.leftchild == None: self.leftchild = BinaryTree(newNode) t = BinaryTree(newNode) t.leftchild = self.leftchild self.leftchild = t def insertright(self,newnode): if self.rightchild == None: self.rightchild = BinaryTree(newNode) t = BinaryTree(newNode) t.rightchild = self.rightchild self.rightchild = t Funkcje pomocnicze

In [35]: class BinaryTree: def init (self,rootobj): self.key = rootobj self.leftchild = None self.rightchild = None def insertleft(self,newnode): if self.leftchild == None: self.leftchild = BinaryTree(newNode) t = BinaryTree(newNode) t.leftchild = self.leftchild self.leftchild = t def insertright(self,newnode): if self.rightchild == None: self.rightchild = BinaryTree(newNode) t = BinaryTree(newNode) t.rightchild = self.rightchild self.rightchild = t def getrightchild(self): return self.rightchild def getleftchild(self): return self.leftchild def setrootval(self,obj): self.key = obj def getrootval(self): return self.key In [40]: r = BinaryTree('a') print("root key = ",r.getrootval()) print("left child = ",r.getleftchild()) r.insertleft('b') print("left child after insertion (reference) = ",r.getleftchild()) print("left child after insertion (value) = ",r.getleftchild().getrootval()) r.insertright('c') print("right child after insertion (reference) = ",r.getrightchild()) print("right child after insertion (value) = ",r.getrightchild().getrootval()) r.getrightchild().setrootval('hello') print("root key after update = ",r.getrightchild().getrootval()) Root key = a Left child = None Left child after insertion (reference) = < main.binarytree object at 0x7f813039a828> Left child after insertion (value) = b Right child after insertion (reference) = < main.binarytree object at 0x7f813039ac18> Right child after insertion (value) = c Root key after update = hello Drzewa wyprowadzenia (ang. parse trees) drzewa wyprowadzenia mogą być stosowane do reprezentowania: zdań w języku naturalnym, np. "Homer hit Bart"

wyrażeń matematycznych, np. ((7 + 3) (5 2)) wykorzystanie drzew pozwala osobno przetwarzać poszczególne części tych struktur (reprezentowane przez poddrzewa) Tworzenie drzew wyprowadzenia wyrażeń matematycznych Co wiemy o wyrażeniach matematycznych?: nawiasy, operatory i operandy każdy lewy nawias rozpoczyna nowe działanie (nowe poddrzewo) każdy prawy nawias kończy działanie operandy powinny być liśćmi operator to rodzic operandów każdy operator ma dwoje dzieci Sposób postępowania: 1. Przekształć wyrażenie na listę znaków. 2. Przetwarzaj wyrażenie znak po znaku: jeśli aktualny znak to (, dodaj lewe dziecko do aktualnego węzła i przejdź do niego, jeśli aktualny znak jest na liście ['+','-','*','/'], przypisz go do atrybutu key aktualnego węzła. Dodaj nowy węzeł jako prawe dziecko i przejdź do niego, jeśli znak to liczba, przypisz go do atrybutu key aktualnego węzła i wróć do jego rodzica, jeśli aktualny znak to ), wróć do rodzica aktualnego węzła. Przykład wyrażenie (3 + (4 5)) lista znaków `['(', '3', '+', '(', '4', '*', '5',')',')']` krok 1 - drzewo z pustym korzeniem

krok 2 - wczytujemy znak ( tworzymy lewe dziecko i przechodzimy do niego (aktualny węzeł jest szary) krok 3 - wczytujemy 3 wstawiamy wartość do klucza aktualnego węzła wracamy do rodzica krok 4 - wczytujemy + wstawiamy wartość do klucza aktualnego węzła dodajemy nowy węzeł jako prawe dziecko przechodzimy do niego krok 5 - wczytujemy ( tworzymy lewe dziecko aktualnego węzła i przechodzimy do niego krok 6 - wczytujemy 4 wstawiamy wartość do klucza aktualnego węzła i wracamy do rodzica

krok 7 - wczytujemy '*' wstawiamy wartość do klucza aktualnego węzła dodajemy nowy węzeł jako prawe dziecko przechodzimy do niego krok 8 - wczytujemy 5 wstawiamy wartość do klucza aktualnego węzła i wracamy do rodzica krok 9 - wczytujemy ) przechodzimy do rodzica aktualnego węzła krok 10 - wczytujemy ) przechodzimy do rodzica aktualnego węzła lub...... kończymy, jeśli jesteśmy w korzeniu całego drzewa, który nie ma rodzica aby zaimplementować tę procedurę, musimy śledzić rodzica i dziecko każdego węzła: w przypadku dzieci sprawa jest prosta - nasza klasa zawiera już odpowiednie narzędzia w przypadku rodziców - możemy wykorzystać stos: za każdym razem, kiedy chcemy wrócić do rodzica, pobieramy go ze stosu Implementacja

In [42]: from asd import Stack def buildparsetree(fpexp): fplist = fpexp.split() pstack = Stack() etree = BinaryTree('') pstack.push(etree) currenttree = etree for i in fplist: if i == '(': currenttree.insertleft('') pstack.push(currenttree) currenttree = currenttree.getleftchild() elif i not in ['+', '-', '*', '/', ')']: currenttree.setrootval(int(i)) parent = pstack.pop() currenttree = parent elif i in ['+', '-', '*', '/']: currenttree.setrootval(i) currenttree.insertright('') pstack.push(currenttree) currenttree = currenttree.getrightchild() elif i == ')': currenttree = pstack.pop() raise ValueError return etree In [43]: pt = buildparsetree("( ( 10 + 5 ) * 3 )") Wykonywanie działań reprezentowanych w postaci drzew każdy nawias reprezentowany jest poddrzewem możemy wykonać działania rekurencyjnie, wykonując oddzielnie każde z poddrzew przypadek bazowy: węzeł zawierający liczbę (czyli liść) nie wymaga dalszego przetwarzania funkcja wykonująca działania zwraca po prostu jego wartość wyniki dwóch rekurencyjnych wywołań na dzieciach są operandami działania zdefiniowanego w ich rodzicu In [45]: import operator def evaluate(parsetree): opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} leftc = parsetree.getleftchild() rightc = parsetree.getrightchild() if leftc and rightc: fn = opers[parsetree.getrootval()] return fn(evaluate(leftc),evaluate(rightc)) return parsetree.getrootval() In [46]: evaluate(pt) Out[46]: 45

Przechodzenie drzewa przechodzenie drzewa to proces odwiedzania wszystkich węzłów drzewa większość zastosowań drzew wymaga takiej operacji w zależności od kolejności odwiedzania rozróżniamy przejście wzdłużne (ang. preorder) - najpierw odwiedzamy korzeń, a potem rekursywnie przechodzimy jego lewe poddrzewo, a potem tak samo prawe podrzewo przejście poprzeczne (ang. inorder) - rekursywne wykonanie przechodzenia na lewym poddrzewie, potem odwiedzamy korzeń i wykonujemy rekursywne przechodzenie prawego poddrzewa przejście wsteczne (ang. postorder) - rekursywnie przechodzimy lewe poddrzewo, potem prawe, a na końcu odwiedzamy korzeń![przechodzenie drzewa](tree_traversal.png) preorder: F, B, A, D, C, E, G, I, H inorder: A, B, C, D, E, F, G, H, I postorder: A, C, E, D, B, H, I, G, F In [47]: def preorder(tree): if tree: print(tree.getrootval()) preorder(tree.getleftchild()) preorder(tree.getrightchild()) In [49]: preorder(pt) * + 10 5 3 In [50]: def postorder(tree): if tree!= None: postorder(tree.getleftchild()) postorder(tree.getrightchild()) print(tree.getrootval()) In [51]: postorder(pt) 10 5 + 3 * wykonanie działania reprezentowanego przez drzewo to nic innego jak przejście postorder: In [56]: def postordereval(tree): opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} res1 = None res2 = None if tree: res1 = postordereval(tree.getleftchild()) res2 = postordereval(tree.getrightchild()) if res1 and res2: return opers[tree.getrootval()](res1,res2) return tree.getrootval()

In [57]: postordereval(pt) Out[57]: 45 In [52]: def inorder(tree): if tree!= None: inorder(tree.getleftchild()) print(tree.getrootval()) inorder(tree.getrightchild()) In [53]: inorder(pt) 10 + 5 * 3 przechodzenie w porządku inorder zwraca wejściowe wyrażenie bez nawiasów możemy wykorzystać to do wypisania wyrażenia na ekranie: In [54]: def printexp(tree): sval = "" if tree: sval = '(' + printexp(tree.getleftchild()) sval = sval + str(tree.getrootval()) sval = sval + printexp(tree.getrightchild())+')' return sval In [55]: printexp(pt) Out[55]: '(((10)+(5))*(3))' Kopiec binarny struktura danych oparta na drzewie binarnym wartości węzła są w stałej relacji z wartością rodzica (np. wartość rodzica jest nie mniejsza niż wartości jego potomka) Operacje na kopcu BinaryHeap() - tworzy nowy, pusty kopiec binarny insert(k) - dodaj element do kopca findmin() - zwraca wartość najmniejszego elementu, pozostawia go na kopcu delmin() - ściąga najmniejszą wartość z kopca isempty() - sprawdza, czy kopiec jest pusty size() - zwraca liczbę elementów w kopcu buildheap(list) - tworzy kopiec z listy elementów Własności kopca Struktura kopca aby przetwarzanie drzewa było wydajne (w czasie logarytmicznym), musi być ono zrównoważone zrównoważone drzewo posiada mniej więcej tę samą liczbę węzłów w obu poddrzewach zupełne drzewo binarne: każdy poziom drzewa (z wyjątkiem ostatniego czyli liści) musi być pełny liście wstawiamy sukcesywnie od lewej do prawej takie drzewo można reprezentować pojedynczą listą: jeśli rodzic jest na pozycji p, jego lewe dziecko ma indeks 2p, a jego prawe dziecko - 2p + 1

węzeł na pozycji n ma rodzica na pozycji n / / 2 korzeń musi mieć indeks 1 (zerowy element listy nie jest wykorzystywany) Porządek w kopcu klucz węzła x jest większy bądź równy kluczowi jego rodzica p Implementacja Konstruktor do reprezentacji kopca używamy pythonowych list w konstruktorze wstawiamy 0 jako pierwszy element listy element ten nie będzie później używany In [58]: class BinHeap: def init (self): self.heaplist = [0] self.currentsize = 0

Wstawianie elementów najbardziej wydajna metoda wstawiania elementów do listy to dołączanie ich do jej końca w ten sposób utrzymana zostanie struktura kopca, tzn.: drzewo pozostanie prawie pełne (liście tylko na ostatnim i ewentualnie przedostatnim poziomie) liście na ostatnim poziomie wstawiane od lewej najprawdopodobniej naruszymy w ten sposób porządek kopca łatwo jest jednak wymyślić metodę naprawienia porządku: przesuwając węzeł do góry, przywracamy odpowiedni porządek między węzłem i jego rodzicem porządek pozostałych potomków nie zostaje naruszony jeśli nowy element jest bardzo mały, konieczne może być wykonanie wielu takich zamian

In [59]: class BinHeap: def init (self): self.heaplist = [0] self.currentsize = 0 def percup(self,i): while i // 2 > 0: if self.heaplist[i] < self.heaplist[i // 2]: tmp = self.heaplist[i // 2] self.heaplist[i // 2] = self.heaplist[i] self.heaplist[i] = tmp i = i // 2 def insert(self,k): self.heaplist.append(k) self.currentsize = self.currentsize + 1 self.percup(self.currentsize) Znajdowanie minimum ze względu na porządek w kopcu, to po prostu klucz korzenia In [ ]: class BinHeap: def init (self): self.heaplist = [0] self.currentsize = 0 def percup(self,i): while i // 2 > 0: if self.heaplist[i] < self.heaplist[i // 2]: tmp = self.heaplist[i // 2] self.heaplist[i // 2] = self.heaplist[i] self.heaplist[i] = tmp i = i // 2 def insert(self,k): self.heaplist.append(k) self.currentsize = self.currentsize + 1 self.percup(self.currentsize) def findmin(self): return self.heaplist[1] Usuwanie najmniejszego elementu po usunięciu najmniejszego elementu kopca (czyli korzenia drzewa), przywracamy jego własności w dwóch krokach: 1. wstawiamy ostatni element z listy w miejsce korzenia 2. jeśli porządek został zburzony, przywracamy go, zamieniając korzeń z mniejszym z jego dzieci 3. kontunuujemy zamiany do odzyskania porządku kopca

In [60]: class BinHeap: def init (self): self.heaplist = [0] self.currentsize = 0 def percup(self,i): while i // 2 > 0: if self.heaplist[i] < self.heaplist[i // 2]: tmp = self.heaplist[i // 2] self.heaplist[i // 2] = self.heaplist[i] self.heaplist[i] = tmp i = i // 2 def insert(self,k): self.heaplist.append(k) self.currentsize = self.currentsize + 1 self.percup(self.currentsize) def findmin(self): return self.heaplist[1] def percdown(self,i): while (i * 2) <= self.currentsize: mc = self.minchild(i) if self.heaplist[i] > self.heaplist[mc]: tmp = self.heaplist[i] self.heaplist[i] = self.heaplist[mc] self.heaplist[mc] = tmp i = mc def minchild(self,i): if i * 2 + 1 > self.currentsize: return i * 2 if self.heaplist[i*2] < self.heaplist[i*2+1]: return i * 2 return i * 2 + 1 def delmin(self): retval = self.heaplist[1] self.heaplist[1] = self.heaplist[self.currentsize] self.currentsize = self.currentsize - 1 self.heaplist.pop() self.percdown(1) return retval Generowanie kopca z listy traktujemy listę wejściową jako kopiec dokonujemy ewentualnych zmian w celu przywrócenia porządku

In [74]: class BinHeap: def init (self): self.heaplist = [0] self.currentsize = 0 def percup(self,i): while i // 2 > 0: if self.heaplist[i] < self.heaplist[i // 2]: tmp = self.heaplist[i // 2] self.heaplist[i // 2] = self.heaplist[i] self.heaplist[i] = tmp i = i // 2 def insert(self,k): self.heaplist.append(k) self.currentsize = self.currentsize + 1 self.percup(self.currentsize) def findmin(self): return self.heaplist[1] def percdown(self,i): while (i * 2) <= self.currentsize: mc = self.minchild(i) if self.heaplist[i] > self.heaplist[mc]: tmp = self.heaplist[i] self.heaplist[i] = self.heaplist[mc] self.heaplist[mc] = tmp i = mc def minchild(self,i): if i * 2 + 1 > self.currentsize: return i * 2 if self.heaplist[i*2] < self.heaplist[i*2+1]: return i * 2 return i * 2 + 1 def delmin(self): retval = self.heaplist[1] self.heaplist[1] = self.heaplist[self.currentsize] self.currentsize = self.currentsize - 1 self.heaplist.pop() self.percdown(1) return retval def buildheap(self,alist): i = len(alist) // 2 self.currentsize = len(alist) self.heaplist = [0] + alist[:] while (i > 0): self.percdown(i) i = i - 1 def size(self): return self.currentsize def isempty(self): return self.currentsize == 0 def str (self): txt = "{}".format(self.heaplist[1:]) return txt In [75]: bh = BinHeap() bh.isempty() Out[75]: True

In [76]: bh.buildheap([9,5,6,2,3]) print(bh) [2, 3, 6, 5, 9] In [77]: bh.isempty() Out[77]: False In [80]: print(bh.delmin()) print(bh.delmin()) print(bh.delmin()) print(bh.delmin()) print(bh.delmin()) 2 3 5 6 9 In [81]: bh.isempty() Out[81]: True In [82]: print(bh) []

Kolejka priorytetowa przypomina kolejkę, jednak jej elementy mają dodatkowy porządek (priorytet) elementy ściągane są z początku kolejki im wyższy priorytet ma element, tym bliżej początku kolejki się znajduje możliwa do zaimplementowania przy pomocy funkcji sortujących i list: wstawianie do listy jest operacją O(n) sortowanie listy to O(nlogn) nie jest to najwydajniejsza metoda kopiec binarny pozwala na pobieranie i wstawianie elementów ze złożonością O(logn)