Programowanie obiektowe i C++ dla matematyków

Podobne dokumenty
Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe i C++ dla matematyków

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

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

Zadanie 2: Arytmetyka symboli

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

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

Drzewo binarne BST. LABORKA Piotr Ciskowski

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

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

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

ZASADY PROGRAMOWANIA KOMPUTERÓW

Porządek symetryczny: right(x)

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

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

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

Przeciążanie operatorów

Algorytmy i język C++

Programowanie komputerowe. Zajęcia 4

Programowanie obiektowe i C++ dla matematyków

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

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

Drzewa poszukiwań binarnych

Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Podstawy programowania skrót z wykładów:

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

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:

Podstawy Informatyki. Metody dostępu do danych

Drzewa poszukiwań binarnych

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

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

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

Typy wyliczeniowe Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Drzewa wyszukiwań binarnych (BST)

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

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

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

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

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

Abstrakcyjne struktury danych w praktyce

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Podstawy algorytmiki i programowania - wykład 4 C-struktury

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

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

Programowanie i struktury danych

Programowanie w C++ Wykład 12. Katarzyna Grzelak. 28 maja K.Grzelak (Wykład 12) Programowanie w C++ 1 / 27

C-struktury wykład. Dorota Pylak

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Wstęp do programowania obiektowego, wykład 7

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Drzewa czerwono-czarne.

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

Programowanie w C++ Wykład 11. Katarzyna Grzelak. 13 maja K.Grzelak (Wykład 11) Programowanie w C++ 1 / 30

Wstęp do programowania

Algorytmy i Struktury Danych

Programowanie obiektowe i C++ dla matematyków

Wykład 3 Składnia języka C# (cz. 2)

Programowanie obiektowe - Przykładowe zadania egzaminacyjne (2005/2006)

Programowanie obiektowe W3

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Programowanie w języku C++

Struktury Struktura polami struct struct struct struct

Teoretyczne podstawy informatyki

1 Podstawy c++ w pigułce.

Listy powiązane zorientowane obiektowo

Wstęp do programowania

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

C++ Przeładowanie operatorów i wzorce w klasach

Algorytmy i Struktury Danych

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

Wstęp do Programowania 2

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

Dynamiczne struktury danych

Programowanie - wykład 4

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

Strategia "dziel i zwyciężaj"

C-struktury wykład. Dorota Pylak

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

> C++ wskaźniki. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki 26 kwietnia 2017

Jak napisać listę jednokierunkową?

Podstawy programowania w języku C++

Lab 9 Podstawy Programowania

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Poprawność semantyczna

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 16 kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 27

Algorytmy i Struktury Danych, 9. ćwiczenia

Kontenery i iteratory. Wykorzystanie kontenerów w praktyce.

Listy, kolejki, stosy

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

Techniki Programowania wskaźniki

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

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

Algorytmy i struktury danych. wykład 5

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

Transkrypt:

Programowanie obiektowe i C++ dla matematyków Bartosz Szreder szreder (at) mimuw... X 0 Typy złożone Oczywiście w C++ możemy definiować własne typy złożone (struktury i klasy), tak jak w Pascalu poprzez mechanizm rekordów i obiektów. Zdefiniujemy sobie typ ułamka, tzn. parę liczb, w których pierwsza oznacza licznik, a druga mianownik: 00 struct ulamek { 00 int licznik, mianownik; 003 }; Istotne jest zapisanie średnika po klamrze zamykającej definicję struktury jest to jedno z bardzo nielicznych miejsc w C++, gdzie takie umieszczenie średnika jest niezbędne. Teraz możemy używać słowa ulamek na oznaczanie typu zmiennych: 00 #include <iostream> 00 003 using namespace std; 004 00 struct ulamek { 006 int licznik, mianownik; 00 }; 00 00 ulamek mnoz(ulamek x, ulamek y) 00 { 0 ulamek result; 0 result.licznik = x.licznik * y.licznik; 03 result.mianownik = x.mianownik * y.mianownik; 04 0 return result; 06 } 0 0 int main() 0 {

00 ulamek a, b, c; 0 0 a.licznik = 3; 03 a.mianownik = ; 04 b.licznik = ; 0 b.mianownik = ; 06 c = mnoz(a, b); 0 0 cout << "(" << a.licznik << " / " << a.mianownik 0 << ") * (" << b.licznik << " / " << b.mianownik 030 << ") = (" << c.licznik << " / " << c.mianownik << ")\n"; 03 return 0; 03 } Ćwiczenie: zaimplementować komplet podstawowych operacji arytmetycznych na ułamkach, tzn. oprócz pokazanej powyżej funkcji mnoz jeszcze podziel, dodaj i odejmij. Staraj się używać jak najmniejszej ilości powtarzanego kodu, np. dzielenie implementując za pomocą mnożenia i odejmowanie za pomocą dodawania. Zadbaj o to, aby wynikowe ułamki były nieskracalne (powyższa implementacja mnoz nie zapewnia tego). Przeciążanie funkcji W C++ można przeciążać nazwy funkcji, tzn. zdefiniować kilka funkcji o tej samej nazwie, ale różnej sygnaturze. Zdefiniowaliśmy mnożenie ułamka przez ułamek, teraz zdefiniujmy mnożenie ułamka przez liczbę całkowitą: 00 #include <iostream> 00 003 using namespace std; 004 00 struct ulamek { 006 int licznik, mianownik; 00 }; 00 00 ulamek mnoz(ulamek x, ulamek y) 00 { 0 ulamek result; 0 result.licznik = x.licznik * y.licznik; 03 result.mianownik = x.mianownik * y.mianownik; 04 0 return result; 06 } 0 0 ulamek mnoz(ulamek x, int y) 0 { Tutaj przez sygnaturę rozumiemy liczbę i rodzaj argumentów funkcji.

00 ulamek result; 0 result.licznik = x.licznik * y; 0 result.mianownik = x.mianownik; 03 04 return result; 0 } 06 0 void wypisz_ulamek(ulamek x) 0 { 0 cout << "(" << x.licznik << " / " << x.mianownik << ")\n"; 030 } 03 03 int main() 033 { 034 ulamek a; 03 036 a.licznik = 3; 03 a.mianownik = ; 03 wypisz_ulamek(a); 03 040 ulamek b = mnoz(a, 3); 04 wypisz_ulamek(b); 04 043 ulamek c = mnoz(a, b); 044 wypisz_ulamek(c); 04 046 return 0; 04 } 3 Przeciążanie operatorów Załóżmy, że chcemy wykonać działanie a + b c, gdzie zmienne a, b i c są ułamkami. Przy dotychczasowym definiowaniu zwyczajnych funkcji do wykonywania obliczeń na ułamkach, takie wyrażenie zapisalibyśmy mniej więcej tak: dodaj(a, mnoz(b, c));. Język C++ pozwala na przeciążanie operatorów arytmetycznych, co przy rozsądnym użytkowaniu poprawia czytelność kodu obliczeniowego: 00 #include <iostream> 00 003 using namespace std; 004 00 struct ulamek { 006 int licznik, mianownik; 00 }; 00 00 ulamek operator*(ulamek x, ulamek y) 3

00 { 0 ulamek result; 0 result.licznik = x.licznik * y.licznik; 03 result.mianownik = x.mianownik * y.mianownik; 04 0 return result; 06 } 0 0 ulamek operator*(ulamek x, int y) 0 { 00 ulamek result; 0 result.licznik = x.licznik * y; 0 result.mianownik = x.mianownik; 03 04 return result; 0 } 06 0 ulamek operator*(int y, ulamek x) 0 { 0 return x * y; 030 } 03 03 void wypisz_ulamek(ulamek x) 033 { 034 cout << "(" << x.licznik << " / " << x.mianownik << ")\n"; 03 } 036 03 int main() 03 { 03 ulamek a; 040 04 a.licznik = 3; 04 a.mianownik = ; 043 wypisz_ulamek(a); 044 04 ulamek b = a * 3; 046 wypisz_ulamek(b); 04 04 ulamek c = a * b; 04 wypisz_ulamek(c); 00 0 ulamek d = * c; 0 wypisz_ulamek(d); 03 04 return 0; 0 } 4

Z przeciążaniem operatorów nie należy przesadzać, bo niejako zmieniamy w ten sposób semantykę języka, co zamiast poprawić czytelność kodu może ją pogorszyć. Łatwość zamieszania, jakie można wprowadzić przeciążając operatory jest głównym źródłem kontrowersji związanych z używaniem tej funkcjonalności języka C++. Należy zauważyć, że konieczne jest oddzielne zdefiniowanie operatorów o sygnaturze (int, ulamek) oraz (ulamek, int), czyli mnożenie zależne od kolejności argumentów. Motywacja: działania, które nie są przemienne. Ćwiczenie: zaimplementować jak najbardziej pełną arytmetykę ułamków, w szczególności operatory o następujących sygnaturach: ulamek operator+(ulamek, ulamek), ulamek operator+(ulamek, int) i ulamek operator+(int, ulamek), analogicznie dla operator-, operator*, operator/, bool operator<(ulamek, ulamek), bool operator<(ulamek, int), bool operator<(int, ulamek), analogicznie dla operator>, operator<=, operator>=, operator== i operator!=. Standardowo należy zadbać o możliwie najmniejszą duplikację kodu. 4 Wskaźniki Nazwy zmiennych są udogodnieniem dla programisty: pozwalają poprzez tekstową informację rozpoznawać komórki pamięci, w których trzymane są isotne informacje robocze. Bez nich należałoby operować bezpośrednio na adresach w pamięci, co uczyniłoby programowanie dużo trudniejszym i znacznie bardziej podatnym na drobne pomyłki. Tym niemniej na pewnym poziomie nie uniknie się operowania na adresach i do tego służą specjalne zmienne, zwane wskaźnikami, które przechowują wartości liczbowe będące znaczącymi adresami w pamięci. Najczęstszym zastosowaniem wskaźników jest alokacja pamięci w obszarze sterty (ang. heap). Tak zaalokowana pamięć ma trwałość ograniczoną jedynie czasem życia programu lub dokonanym jawnie zwolnieniem, w przeciwieństwie do zmiennych tymczasowych tworzonych w pamięci stosu (ang. stack), których życie kończy się w chwili zamknięcia bloku kodu, w którym zostały zadeklarowane. 4. Znaczkologia stosowana Zmienne wskaźnikowe deklaruje się z gwiazdką przy nazwie zmiennej, czyli taki zapis: 00 int a; 00 int *pa; 003 int *pb, b, *pc; deklaruje kolejno liczbę całkowitą a, wskaźnik na liczbę całkowitą pa, wskaźnik na liczbę całkowitą pb, liczbę całkowitą b, wskaźnik na liczbę całkowitą pc. Adres danej zmiennej można poznać stawiając przed jej nazwą symbol &. Przypisując taki adres do zmiennej wskaźnikowej uzyskujemy możliwość grzebania we wskazywanej zmiennej zarówno poprzez tę zmienną jak i wskaźnik: 00 #include <iostream> 00 003 using namespace std; 004

00 int main() 006 { 00 int a; 00 int *pa; 00 00 a = ; 0 pa = &a; 0 03 cout << "a: " << a << "\n"; 04 cout << "pa: " << pa << "\n"; 0 cout << "*pa: " << *pa << "\n"; 06 0 a = ; 0 cout << "a: " << a << "\n"; 0 cout << "pa: " << pa << "\n"; 00 cout << "*pa: " << *pa << "\n"; 0 0 *pa = 3; 03 cout << "a: " << a << "\n"; 04 cout << "pa: " << pa << "\n"; 0 cout << "*pa: " << *pa << "\n"; 06 0 return 0; 0 } Alokację pamięci na nowe zmienne przeprowadzamy za pomocą operacji new (dla pojedynczych egzemplarzy zmiennych) albo new[] (dla tablic), a zwalniamy wywołując na wskaźnikach pamiętajacych przydzielone adresy odpowiednio delete albo delete[]. Specjalną wartością dla wskaźnika jest NULL oznacza wskazywanie na nic. Przykład użycia: 00 int main() 00 { 003 int *p = new int; 004 int *tab = new int[0]; 00 006 *p = ; 00 for (int i = 0; i < 0; ++i) 00 tab[i] = i; 00 00 delete p; 0 delete[] tab; 0 03 return 0; 04 } Jeśli odwołujemy się do pól składowych typu złożonego, to możemy robić to na dwa sposoby: 6

00 #include <iostream> 00 003 using namespace std; 004 00 struct ulamek { 006 int licznik, mianownik; 00 }; 00 00 int main() 00 { 0 ulamek u; 0 ulamek *pu = &u; 03 04 (*pu).licznik = ; //sposób 0 pu->mianownik = ; //sposób 06 cout << "(" << u.licznik << " / " << u.mianownik << ")\n"; 0 return 0; 0 } 4. Dobre maniery...a raczej zasady utrudniające rozwalenie sobie programu. Pamięć przydzieloną przez new zwalniamy przez delete, a przydzieloną new[] zwalniamy przez delete[]. Nie należy mieszać tych wywołań, bo dziwne rzeczy mogą się dziać (demony z nosa linkowane poniżej). Wskaźniki, na których wywołujemy delete warto dla bezpieczeństwa zaraz potem ustawić na NULL ponowne delete na już zwolnionej pamięci jest katastrofalne, ale na wskaźniku NULL nie robi niczego. Samo wywołanie delete na wskaźniku zwalnia pamięć, ale nie zeruje (nie ustawia na NULL) wskaźnika. Użycie wskaźników do niezainicjowanej pamięci lub wskazujących na pamięć już zwolnioną to proszenie się o kłopoty (albo demony z nosa). Każdą przydzieloną pamięć należy zwalniać gdy przestaje być potrzebna. Jeśli gdzieś zapomnimy zwolnić pamięć i zgubimy wskaźnik do niej (który np. był zmienną lokalną), to aż do zakończenia programu pamięć taka jest nieodzyskiwalna. W programach pracujących odpowiednio długo takie działanie powoduje całkowite zajęcie zasobów pamięciowych komputera (tzw. memory leak wyciek pamięci). 4.3 Warto przeczytać http://www.catb.org/~esr/jargon/html/n/nasal-demons.html http://en.wikipedia.org/wiki/nasal_demons http://dspace.dial.pipex.com/town/green/gfd34/art/

Drzewa wyszukiwań binarnych (BST) Drzewo wyszukiwań binarnych (ang. Binary Search Tree) to drzewiasta struktura danych o następujących własnościach. Przechowuje pewne dane identyfikowane według klucza, na którym istnieje liniowy porządek (np. całkowite, leksykograficzny porządek na napisach itp.). liczby Każdy wierzchołek drzewa jest korzeniem pewnego BST, być może pustego. Każdy wierzchołek niepustego drzewa ma dwóch potomków: lewego i prawego, będącymi oczywiście korzeniami pewnych BST (być może pustych). Lewe poddrzewo zawiera klucze w wartościach mniejszych względem klucza w aktualnym węźle, zaś prawe poddrzewo zawiera klucze o wartościach większych. Z powyższych własności wynika, że znalezienie minimalnego albo maksymalnego elementu (według klucza) w drzewie osiągamy poprzez znalezienie odpowiednio skrajnie lewej albo skrajnie prawej ścieżki, rozpoczynając w korzeniu całego drzewa. Drzewo binarne implementować będziemy jako typ złożony, pamiętający klucz (i ewentualne dodatkowe pola danych) oraz wskaźniki na lewego i prawego potomka. Wskaźnikom nadajemy wartość NULL jeśli reprezentują poddrzewo puste. Wyszukiwanie elementu o zadanym kluczu key implementujemy następująco: Zaczynamy w korzeniu drzewa. Jeśli węzeł, w którym się znajdujemy ma właściwy klucz, to zwracamy go. W przeciwnym wypadku rekurencyjnie szukamy w lewym albo prawym poddrzewie w zależności od tego, czy szukany klucz jest mniejszy czy większy od wartości klucza w aktualnym węźle. Jeśli w pewnym momencie wypadniemy poza drzewo (węzeł NULL), to znaczy, że poszukiwany klucz nie znajduje się w drzewie. 00 //tu siedzi definicja NULL-pointera 00 #include <cstddef> 003 004 struct BST_node { 00 int key; 006 BST_node *left, *right; 00 }; 00 00 BST_node * BST_search(BST_node *node, int key) 00 { 0 if (node == NULL) 0 return NULL; 03 if (node->key == key) 04 return node; 0 if (node->key > key) 06 return BST_search(node->left, key); 0 else

0 return BST_search(node->right, key); 0 } 00 0 bool key_in_tree(bst_node *root, int key) 0 { 03 return BST_search(root, key)!= NULL; 04 } Wstawianie elementu do drzewa będzie wyglądało podobnie. Załóżmy, że wstawiamy element o niewystępującym jeszcze w drzewie kluczu. Wtedy szukamy w identyczny sposób jak wyżej miejsca, w którym należy stworzyć nowy wierzchołek tak, aby nie naruszyć porządku na kluczach. Poniższa przykładowa implementacja radzi sobie w elegancki sposób z różnymi przypadkami specjalnymi w rodzaju pustego korzenia drzewa. 00 #include <cstddef> 00 003 struct BST_node { 004 int key; 00 BST_node *left, *right; 006 }; 00 00 BST_node * BST_insert(BST_node *node, int key) 00 { 00 if (node == NULL) { 0 BST_node *temp = new BST_node; 0 temp->key = key; 03 temp->left = temp->right = NULL; 04 return temp; 0 } 06 0 if (key < node->key) 0 node->left = BST_insert(node->left, key); 0 else 00 node->right = BST_insert(node->right, key); 0 return node; 0 } 03 04 int main() 0 { 06 BST_node *root = NULL; 0 root = BST_insert(root, 0); 0 root = BST_insert(root, ); 0 root = BST_insert(root, ); 030 root = BST_insert(root, ); 03 root = BST_insert(root, ); 03 root = BST_insert(root, 3); 033 root = BST_insert(root, ); 034 root = BST_insert(root, );

03 root = BST_insert(root, ); 036 root = BST_insert(root, 4); 03 root = BST_insert(root, ); 03 root = BST_insert(root, 0); 03 root = BST_insert(root, ); 040 root = BST_insert(root, 00); 04 root = BST_insert(root, ); 04 043 return 0; 044 } Powyższy ciąg operacji powinien wyprodukować takie drzewo: 0 3 0 4 00 Ćwiczenie (łatwe): napisać funkcję o sygnaturze void BST_print(BST_node *) wypisującą w kolejności rosnącej wszystkie klucze drzewa przekazanego w argumencie. Ćwiczenie (trochę trudniejsze): napisać funkcję o sygnaturze void BST_delete(BST_node *, int) usuwającą z BST węzeł o zadanym kluczu. Jeśli drzewo nie zawiera wierzchołka o podanym kluczu, to niczego nie rób. W poniższych punktach zawiera się propozycja (acz niekoniecznie jedynie słuszna metoda) działania. Jeśli znaleziony węzeł nie ma potomków, to możemy go po prostu usunąć (delete). Oczywiście należy w jego rodzicu ustawić wartość odpowiedniego wskaźnika (left albo right) na NULL, aby poprawnie odzwierciedlić usunięcie wierzchołka potomnego. Jeśli znaleziony węzeł ma jednego potomka, to jeszcze jest dość łatwo. Możemy tego potomka zwyczajnie przepisać w miejsce usuwanego węzła. Łatwo pokazać, że takie przepisanie jedynego potomka (który oczywiście ma potencjalnie całkiem spore poddrzewo pod sobą) nie zaburzy ustalonego wcześniej porządku na kluczach. Trochę więcej roboty jest z usuwaniem wierzchołka posiadającego obu potomków. Zauważmy, że jeśli wstawimy na jego miejsce węzeł o kluczu najmniejszym spośród większych od niego (jeden krok w prawo w drzewie, a potem skrajnie lewa ścieżka) albo węzeł o kluczu największym spośród mniejszych od niego (jeden krok w lewo w drzewie, a potem skrajnie prawa scieżka), to porządek BST zostanie zachowany. Zostaje zastanowienie się jak w cywilizowany sposób wyciąć z drzewa tak znaleziony węzeł zastępczy, ale z tym sobie potrafimy radzić, bo on ma co najwyżej jednego potomka. Przykład działania takiego algorytmu usuwania oraz wcześniej zdefiniowanych operacji można znaleźć na załączonych rysunkach. 0

0 3 0 4 00 BST_search() 0 3 0 4 00 NULL BST_search(4) Rysunek : Wyszukiwanie w przykładowym BST. 0 3 0 4 00 4 BST_insert(4) Rysunek : Wstawianie do BST.

0 3 0 4 00 4 BST_delete(0) 3 0 4 00 4 BST_delete() 3 0 4 00 4 BST_delete(0) Rysunek 3: Usuwanie z BST.