• Nie Znaleziono Wyników

w Krakowie AkademiaGórniczo-Hutnicza

N/A
N/A
Protected

Academic year: 2021

Share "w Krakowie AkademiaGórniczo-Hutnicza"

Copied!
69
0
0

Pełen tekst

(1)

Akademia

Górniczo-Hutnicza w Krakowie

Adrian Horzyk

(2)

STRUKTURY LINIOWE – SEKWENCJE

Struktury liniowe (sekwencje) to takie struktury danych, których każdy element poza pierwszym i ostatnim posiada swojego poprzednika i następnika.

Ostatni element sekwencji nie posiada następnika, a pierwszy nie posiada poprzednika.

Struktury liniowe różnią się:

• możliwością dynamicznego zwiększania lub zmniejszania swojej struktury,

• sposobem i szybkością dostępu do elementów struktury,

• zajętością pamięci niezbędnej do przechowywania elementów struktury.

Każda struktura liniowa w Pythonie składa się z elementów, a każdy element w Pythonie jest obiektem, a każdy obiekt jest klasą, która może dziedziczyć z

dowolnego typu.

Do podstawowych struktur liniowych (kolekcji) zaliczamy:

• tablice

• stosy

• kolejki

• listy

• krotki

• słowniki

• zbiory

Wszystkie struktury liniowe są wysoce zoptymalizowane pod kątem szybkości sortowania i przeszukiwania w Pythonie, a ich implementacja oparta jest o tablice.

(3)

STRUKTURY LINIOWE w PYTHONIE

Struktury liniowe (sekwencyjne) w Pythonie:

• Listy (list) – to tablice o zmiennej liczbie elementów z możliwością ich dodawania, usuwania i podmiany. [typ zmienny – mutable]

• Krotki (tuple) – to tablice o stałej liczbie elementów bez możliwości ich dodawania, usuwania i podmiany. [typ niezmienny – immutable]

• Łańcuchy i napisy (string) – to tablice o stałej długości zawierające dowolne znaki,

po których można iterować, lecz nie można ich zmieniać. [typ niezmienny – immutable]

• Słowniki (dictionary) – to struktury nieuporządkowane w postaci tablic asocjacyjnych, tzw. map, przyporządkowujące kluczom pewne wartości, gdzie klucze muszą być

typami niezmiennymi (obiektami haszowalnymi posiadającymi metodę __hash__()), umożliwiającymi zdefiniowanie funkcji haszującej w celu ich szybkiego odnajdywania.

[typ zmienny – mutable]

• Zbiory (set lub frozenset)– to tablice asocjacyjne, których elementy muszą być typami niezmiennymi (obiektami haszowalnymi posiadającymi metodę __hash__()),

umożliwiającymi zdefiniowanie funkcji haszującej w celu ich szybkiego odnajdywania.

[typ zmienny – mutable albo typ niezmienny – immutable w zależności od typu zbioru]

• Stosy (stack, FILO – first in last out)i kolejki (queue, FIFO – first in first out) w Pythonie implementowane są z wykorzystaniem list, których są szczególnym przypadkiem.

W Pythonie wszystkie te kolekcje są implementowane z wykorzystaniem tablic (bez wskaźników), wykorzystując naturalną strukturę sekwencyjnych pamięci RAM, które są tablicami komórek, zwykle tzw. słów, składających się z bajtów.

(4)

TABLICE I INDEKSY

(5)

TABLICE I TABELE

Tablica

to jednorodna, zwarta struktura danych o niezmiennej długości składająca się z komórek zawierających dane tego samego typu.

Tablice mogą być:

• jednowymiarowe (wektory),

• dwuwymiarowe (macierze)

• lub wielowymiarowe.

Tabele

to tablice rekordów, mogące zawierać dane różnych typów, lecz wszystkie rekordy tabel zawierają dane tych samych typów, które nazywamy atrybutami,

cechami lub parametrami obiektów. Ponadto dane rekordów w tablicach są

uporządkowane względem tych atrybutów tworząc kolumny danych tego samego typu.

(6)

LISTY

Lista to dynamiczna, liniowa struktura danych składająca się z elementów powiązanych ze sobą w taki sposób, iż można z łatwością dodawać, usuwać, modyfikować lub podmieniać poszczególne elementy. Listy są wobec tego zwykle implementowane z wykorzystaniem wskaźników, które wiążą ze sobą rekordy (obiekty, klasy), zawierające w sobie dane oraz wskaźniki lub indeksy wskazujące poprzedni oraz następny rekord w liście, za wyjątkiem elementu pierwszego i ostatniego, których odpowiednie wskaźniki nie są ustawione.

Lista posiada dwa wyróżnione wskaźniki, zwane głową (head) i ogonem (tail), które wskazują odpowiednio na początek i koniec listy, czyli pierwszy i ostatni element listy.

Wszystkie operacje na liście, tj. dodawanie, usuwanie, modyfikowanie i podmienianie elementów, mogą być wykonywane w dowolnym miejscu listy.

Powiązanie poszczególnych elementów w listach może być:

• jednokierunkowe

• lub dwukierunkowe,

aczkolwiek zwykle stosuje się powiązania dwukierunkowe, które umożliwiają swobodę w poruszaniu się po elementach listy w obu kierunkach, co niestety odbywa się kosztem dodatkowej pamięci związanej z przechowywaniem dwóch wskaźników lub indeksów w zależności od sposobu implementacji listy w pamięci.

(7)

OPERACJE NA LISTACH

Dodawanie elementu do listy w dowolnym jej miejscu:

Usuwanie elementu z listy w dowolnym wskazanym miejscu:

(8)

STOSY

Stos to dynamiczna, liniowa struktura danych składająca się z elementów

powiązanych

ze sobą w taki sposób, iż elementy można dodawać lub usuwać tylko z góry stosu.

Stos to szczególny przypadek Listy pojedynczo wiązanej, ograniczający operacje na niej.

Powiązanie poszczególnych elementów w stosie jest jednokierunkowe.

Stos posiada jeden wskaźnik sterujący, zwany górą stosu, który wskazuje

element na jego wierzchu.

Tylko na tym elemencie można położyć kolejny albo go zdjąć ze stosu.

W Pythonie stos implementujemy z wykorzystaniem listy.

(9)

KOLEJKI

Kolejka to dynamiczna, liniowa struktura danych składająca się z elementów

powiązanych ze sobą w taki sposób, iż elementy można z dodawać tylko na jej końcu, a usuwać je tylko z jej początku. Kolejka to szczególny przypadek listy pojedynczo

wiązanej, jednokierunkowej, ograniczający operacje na niej.

Dodawanie nowego elementu na końcu kolejki wiąże się ze zmianą wskaźnika końca kolejki i dotychczas ostatniego elementu kolejki, ustawiając je na nowo dodanym elemencie.

Usuwanie elementu z przodu kolejki powoduje przesunięcie wskaźnika początku kolejki na następny element.

Kolejkimogą być priorytetowe, wtedy elementy dodawane są na jej końcu, a usuwane są według największego priorytetu, więc są często implementowane za pomocą stogu, czyli kopca zupełnego, który w czasie logarytmicznym pozwala wykonać te operacje.

(10)

ZALETY I WADY LIST I TABLIC

Klasyczna lista jest strukturą w pełni dynamiczną z punktu widzenia każdego jej elementu.

A więc umożliwia łatwe dodawanie, usuwanie, podmianę i przemieszczanie elementów.

Nie można jednak jej w szybki sposób przeglądać (np. w czasie logarytmicznym ani stałym), lecz niezbędne jest przeszukiwanie elementów po kolei, idąc po kolejnych wskaźnikach.

Niestety również taka implementacja listy zajmuje sporo więcej miejsca w pamięci, gdyż każdy jej element wymaga dwóch wskaźników oraz struktury, która zawiera te wskaźniki razem z danymi właściwymi przechowywanymi wewnątrz każdego jej elementu.

Tablicepod względem szybkości dostępu do dowolnego jej elementu są bezkonkurencyjne, gdyż indeks elementu jednoznacznie wskazuje miejsce elementu w pamięci, które może zostać bardzo szybko (w czasie stałym) wyznaczone, ponieważ pamięć RAM jest tablicą.

Z punktu widzenia efektywności wykorzystania pamięci są również bezkonkurencyjne, gdyż nie wymagają ekstra pamięci do powiązania poszczególnych elementów tablicy ze sobą.

Niestety w odniesieniu do operacji dodawania, usuwania i przemieszczania jej elementów w ramach sekwencji tablice są bardzo kosztownym i nieefektywnym rozwiązaniem,

szczególnie jeśli trzeba dokonywać tych operacji blisko jej początku, gdyż elementy trzeba w niej przesuwać. Podobnie sprawa ma się z możliwością zwiększanie lub zmniejszania jej rozmiaru, wymagając przekopiowania wszystkich jej elementów do nowej tablicy.

(11)

WYKORZYSTANIE ZALET

Powstaje więc pytanie, czy można wykorzystać pozytywne i wyeliminować negatywne cechy list i tablic, tworząc inny, nowy rodzaj struktury danych, bez ograniczeń i kosztu wykonywania nieefektywnych operacji na klasycznej tablicy i liście?

Odpowiedź na te pytania muszą zadawać sobie twórcy nowych języków programowania oraz kompilatorów – w tym również Pythona, którzy wyraźnie postawili na szybkość.

Żeby osiągnąć szybkość dostępu i oszczędność pamięci należałoby wykorzystać tablice, jeśli zaś łatwość dodawania, usuwania i przemieszczenia elementów właściwości list.

Pewnym kompromisem w stosunku dla braku możliwości rozszerzania tablic jest użycie tablicy nieco większej niż szacowana jej wielkość, a jeśli powiększenie to nie będzie przekraczało kosztu pamięciowego wskaźników listy, będzie rozwiązaniem dobrym.

To jednak nie rozwiązuje problemu nieefektywności wstawiania, usuwania i przemieszczania elementów w tablicy. Poszukano więc pewnych rozwiązań

kompromisowych łączących pozytywne cechy obu struktur danych, tworząc tablico-listę (ArrayList), która jest w zasadzie listą tablic o znanej wielkości, przyspieszającą operacje przeszukiwania listy stosującą indeksy, które umożliwiają wyznaczenie, w której tablicy znajduje się poszukiwany element:

(12)

WYKORZYSTANIE ZALET

Tablico-listy nie muszą jednak operować na tablicach tej samej wielkości, co w przeciwnym przypadku nie eliminowałoby problemu przesuwania elementów w

tablicach przy próbie ich dodania, usunięcia lub przemieszczenia, lecz jeśli zastosujemy zmienną ilość elementów w tablicy, wtedy podmianie będą podlegały tylko pewne części całej struktury. Takie rozwiązanie jednak komplikuje kwestię indeksowania i wyznaczania tablicy, w której znajduje się poszukiwany element.

(13)

WYKORZYSTANIE ZALET

Można jednak zastosować dodatkową tablicę wskaźników zawierającą zarówno ilości przechowywanych elementów w poszczególnych tablicach, jak również wskaźniki do nich, co umożliwia w miarę szybkie wyznaczenie tablicy, w której znajduje się element o podanym indeksie, jak również wykorzystanie wskaźnika, żeby bezpośrednio przejść do tej tablicy:

albo

Pozostaje jednak problem związany z dodawaniem nowych wartości do tablicy wskaźników, gdy dodana zostanie nowa tablica elementów do listy tablic.

Problemem jest też konieczność sumowania ilości zawartych w tablicach elementów, w celu wyznaczenia indeksu elementu.

tablica wskaźników

(14)

WYKORZYSTANIE ZALET

Przeszukiwanie dodatkowej tablicy ilości indeksów ze wskaźnikami również można przyspieszyć stosując w miejsce takiej tablicy wyważone drzewo binarne zawierające

ilości elementów w tablicach danych oraz sumy elementów w poszczególnych poddrzewach tych tablic, co przyspiesza do czasu logarytmicznego względem ilości sum elementów

w tablicach indeksów wyznaczenie tablicy docelowej, indeksu w niej poszukiwanego elementu oraz odnalezienie wskaźnika prowadzącego do takiej tablicy, szczególnie jeśli operujemy na dużych kolekcjach danych.

Powstają więc drzewa list tablic, które przy doborze odpowiedniej wielkości tablic danych umożliwiają bardzo szybkie wykonywanie wszystkich operacji na nich, stanowiąc pewien kompromis pomiędzy tablicami, listami i drzewami:

(15)

EFEKTYWNE WYSZUKIWANIE W SŁOWNIKU

Słownik jest standardowo strukturą liniową, ale czasami można skorzystać ze szczególnych

właściwości kluczy (hasła) stosowanych w słownikach w celu optymalizacji dostępu do wartości (treści).

Wyobraźmy sobie, iż kluczami słownika są słowa języka polskiego czy angielskiego.

Zależy nam na szybkim dostępie do opisu tego słowa lub jego tłumaczenia na inny język.

Możemy więc skorzystać ze standardowej struktury słownika opartej o wyszukiwanie

za pośrednictwem funkcji haszujące w Pythonie lub często stosowane wyszukiwanie połówkowe w innych językach programowania albo wykorzystać tą właściwość kluczy, zoptymalizować sposób ich przechowywania i dodatkowo zapewnić możliwość odnajdywania dowolnych wartości w czasie stałym:

(16)

DEFINICJA DRZEWA

Drzewo to struktura danych składająca się z wierzchołków (węzłów) i krawędzi, przy czym krawędzie łączą wierzchołki w taki sposób, iż istnieje zawsze

dokładnie jedna droga pomiędzy dowolnymi dwoma wierzchołkami.

Lista przekształcona w drzewo binarne:

Wierzchołki w drzewach przedstawiamy w postaci warstwowej, tzn. każdy wierzchołek w drzewie znajduje się na jakimś poziomie.

Poziom wierzchołka w drzewie jest równy długości drogi łączącej go z korzeniem. Korzeń drzewa jest na poziomie 0.

Wysokość drzewa równa jest maksymalnemu poziomowi drzewa, czyli długości najdłuższej spośród ścieżek prowadzących od korzenia do poszczególnych liści drzewa.

Wierzchołki mogą posiadać rodzica, który jest umieszczony na wyższym poziomie oraz dzieci,

które są umieszczone na niższym poziomie. Niektóre dzieci nie posiadają własnych dzieci i są liśćmi.

Dzieci jednego rodzica nazywamy rodzeństwem.

Wierzchołki, które nie posiadają ani jednego dziecka nazywamy liśćmi.

Przodkamisą rodzice oraz rekurencyjnie rodzice rodziców.

Potomkami są dzieci oraz rekurencyjnie dzieci dzieci.

Wierzchołki posiadające zarówno rodzica jak i przynajmniej jedno dziecko nazywamy wierzchołkami wewnętrznymi.

Każde drzewo posiada wyróżniony, nie posiadający rodzica wierzchołek, który nazywamy korzeniem.

Drzewa w informatyce rosną w dół!

KORZEŃ

LIŚCIE WIERCHOŁKI

WEWNĘTRZNE WYSOKOŚĆ

DRZEWA

POZIOM

WIERZCHOŁKA

0

1

2

3

(17)

PARAMETRY DRZEW

Drogę (ścieżkę) w drzewie pomiędzy dwoma wierzchołkami wyznaczamy poprzez wierzchołki pośrednie przechodząc po łączących je krawędziach.

Długość drogi pomiędzy dwoma wierzchołkami w drzewie wyznaczamy jako ilość krawędzi, po których trzeba przejść, żeby przejść z jednego wierzchołka do drugiego.

W drzewach nie istnieją cykle, czyli nietrywialna droga posiadająca początek i koniec

w tym samym wierzchołku. Drzewa nie posiadają też kilka alternatywnych dróg pomiędzy tymi samymi wierzchołkami, lecz tylko dokładnie jedną:

Jeśliby do drzewa dodano taką krawędź,

która by utworzyła cykl lub alternatywną drogę, wtedy drzewo staje się grafem:

(18)

WYWAŻANIE DRZEW

Drzewo binarne jest wyważone (zrównoważone), gdy wysokość lewego i prawego poddrzewa każdego jego wierzchołka nie różni się o więcej niż jeden.

Drzewo jest doskonale zrównoważone, gdy dodatkowo wszystkie jego liście znajdują się na maksymalnie dwóch poziomach.

Klasyczne drzewo poszukiwań BST nie posiada wbudowanych mechanizmów równoważenia.

Przykładem drzewa totalnie niezrównoważonego jest lista:

(19)

RODZAJE DRZEW

Drzewa mogą być między innymi:

Binarne– jeśli każdy węzeł posiada co najwyżej dwójkę dzieci.

k-Regularne– jeśli każdy węzeł posiada co najwyżej k dzieci.

Nieregularne– jeśli nie mają określonego maksymalnego stopnia węzła w drzewie.

Zupełne (kompletne)– gdy ma wszystkie poziomy z wyjątkiem ostatniego całkowicie zapełnione, a ostatni jest spójnie zapełniony od strony lewej.

Kopiec – to drzewo binarne, w którego węzłach znajdują się elementy reprezentowanego multizbioru, w którym dzieci nie są większe od rodziców.

Decyzyjne– gdy węzły drzewa zawierają warunki, a liście decyzje.

BSTto binarne drzewo poszukiwań zawierające elementy o unikalnych kluczach, w którym klucze elementów w lewym poddrzewie są mniejsze od kluczy elementów w prawym poddrzewie

dla każdego wierzchołka drzewa. BST nie posiada mechanizmów wyważania.

Czerwono-czarne – to rodzaj samoorganizującego się binarnego drzewa poszukiwań o wysokości maks.

2 log (n+1), stosowanego np. do implementacji tablic asocjacyjnych.

AVL – to zrównoważone binarne drzewo poszukiwań (BST). Każdy węzeł drzewa AVL ma przypisaną wartość -1, 0 lub +1 wyrażającą różnicę w wysokości lewego i prawego poddrzewa. Jeśli operacja na drzewie miałaby doprowadzić tą różnicę do wartości niedopuszczalnej (czyli < -1 lub > +1), w drzewie wykonywana jest specjalna operacja rotacji, przywracająca zrównoważenie.

B-drzewa– to uporządkowane drzewa zawierające węzły mogące przechowywać k uporządkowanych kluczy, spośród których każdy jest medianą dla wartości kluczy korzeni poddrzew węzła tego klucza, stosowane np. do indeksacji atrybutów w tabelach bazodanowych i dostępu do nich w czasie O(log n).

B+drzewa– to zmodyfikowane B-drzewa, które przechowują wszystkie obiekty w liściach, duplikując niektóre wartości kluczy w węzłach służących do szybkiego wyszukiwania obiektów w czasie

logarytmicznym. Dodatkowo obiekty w liściach są łączone w porządku rosnącym, co tworzy graf.

(20)

DRZEWA REGULARNE

Drzewa regularne można w łatwy sposób reprezentować w tablicy . Jeśli drzewa te są dodatkowo zupełne, wtedy reprezentacja ta jest

optymalnie oszczędna z punktu widzenia wykorzystania pamięci, gdyż taka tablica jest spójnie wypełniona od strony lewej:

Przykład reprezentacji w tablicy drzewa binarnego oraz zależność umożliwiająca wyznaczenie

indeksów dzieci oraz rodzica dla węzła o indeksie k:

Dla drzew regularnych o większej ilości dzieci zależność tą

odpowiednio modyfikujemy:

3k+1, 3k+2, 3k+3 i 𝑘 − 1 /3

4k+1, 4k+2 , 4k+3 , 4k+4 i 𝑘 − 1 /4 itp.

(21)

KOPIEC

Kopiec to drzewo binarne, w którego węzłach znajdują się elementy reprezentowanego multizbioru, które spełniają warunek kopca.

Warunek kopca mówi, iż jeśli węzeł y jest następnikiem węzła x, to element w węźle y jest nie większy niż element w węźle x.

Drzewo ma uporządkowanie kopcowe, gdy wszystkie jego elementy zachowują porządek kopcowy, tzn. elementy na ścieżkach w drzewie od korzenia do liścia uporządkowane są w sposób nierosnący.

Kopiec zupełny to zupełne drzewo binarne o uporządkowaniu kopcowym, czyli takie, którego wszystkie poziomy są wypełnione całkowicie za wyjątkiem co najwyżej ostatniego, który jest spójnie wypełniony od strony lewej.

Każde drzewo binarne można w łatwy sposób reprezentować w tablicy, a indeksy określone są przy pomocy

następującej zależności:

(22)

BINARNE DRZEWA POSZUKIWAŃ

Binarne drzewo poszukiwań (BST – binary search tree) to binarne drzewo reprezentujące elementy multizbioru w taki sposób, iż każdy wierzchołek ma po lewej elementy mniejsze, a po prawej większe od reprezentowanej wartości klucza przez ten wierzchołek

(https://www.cs.usfca.edu/~galles/visualization/BST.html).

Odnajdywanie miejsca wstawienia nowego elementu polega na przechodzeniu od korzenia po węzłach drzewa w taki sposób, iż jeśli wartość jest mniejsza, wtedy idzie się w lewo,

a jeśli większa lub równa w prawo.

Przechodzenie po drzewie jest dokonywane tak długo, dopóki nie natrafimy na sytuację, gdy węzłowi brak węzła lub liścia w pożądaną stronę. Tam jest dodawany nowy element.

Drzewa BST nie posiadają żadnego mechanizmu wyważania, więc w pesymistycznymi przypadku może zostać zbudowana lista – czyli trywialna postać drzewa.

< ≤

< <

<

<

(23)

DRZEWA CZERWONO-CZARNE

Czerwono-czarne drzewo to binarne drzewo poszukiwań, w którego węzłach

znajdują się elementy reprezentowanego multizbioru, pokolorowane na kolor czerwony lub czarny (https://www.cs.usfca.edu/~galles/visualization/RedBlack.html), przy czym:

Korzeń drzewa jest zawsze czarny.

Dzieci czerwonego rodzica muszą być czarni.

Każda droga prowadząca z ustalonego węzła do dowolnego liścia liczy tyle samo czarnych.

Po odnalezieniu miejsca algorytmem poszukiwań binarnych i wstawienia nowego elementu, zostaje on pokolorowany na czerwono, a następnie jeśli nie są spełnione w/w warunki, dokonywane są operacje rotacji w lewo lub w prawo oraz odpowiednio zmieniany jest kolor.

Drzewa te są jednak mniej efektywne niż drzewa AVL oraz B-drzewa, więc

szczegółowy algorytm działania nie będziemy tutaj rozważali.

(24)

DRZEWA I DIAGRAMY DECYZYJNE

Drzewa decyzyjne to specjalny rodzaj drzew, który w węzłach drzewa podejmuje decyzje na podstawie zdefiniowanych warunków i dostępnych opcji wyboru:

Diagram decyzyjny powstaje poprzez redukcję drzewa decyzyjnego na skutek:

• łączenia (agregacji) liści o tych samych wartościach,

• następnie poprzez redukcję węzłów, których wszystkie połączenia prowadzą do tego samego następnika.

Drzewo decyzyjne jest już grafem, gdyż zawiera alternatywne drogi pomiędzy węzłami oraz cykle, lecz jest oszczędniejszy niż analogiczne drzewo decyzyjne.

Jeśli każdy węzeł drzewa decyzyjnego posiada dokładnie dwa następniki, wtedy

w wyniku takiego przekształcenia otrzymujemy binarny diagram decyzyjny .

(25)

B-DRZEWA i B+DRZEWA

B-drzewa i B+drzewa stopnia k to specjalne k-regularne drzewa pozwalające reprezentować klucze obiektów w uporządkowany sposób, zapewniając w czasie logarytmicznym dodawanie obiektów i ich usuwanie, ponieważ drzewa te posiadają mechanizm automatycznego wyważania węzłów, nie kluczy.

Drzewa te mogą przechowywać k-1 kluczy/obiektów w jednym węźle oraz posiadać k dzieci (k ≥ 3).

Są to drzewa samoorganizujące się, a więc wykonujące operacje podziału i łączenia, przesuwania i tworzenia i usuwania wierzchołków, które przywracają odpowiednią strukturę drzewa oraz porządek po dodaniu lub usunięciu obiektu o określonym kluczu do i z tych drzew.

B-drzewa 3 stopnia są rozszerzeniem idei binarnych drzew poszukiwań, gdyż każde poddrzewo zawiera elementy odpowiednio mniejsze, pomiędzy pewnymi wartościami lub większe od wartości kluczy przechowywanych w poszczególnych węzłach. Jest to struktura bardzo efektywna, gdyż zapewnia dodawanie, usuwanie i odnajdywanie dowolnego elementu w czasie O(𝒍𝒐𝒈𝒌𝒏).

B+drzewasą nieco redundantne, bo przechowują duplikaty niektórych kluczy w węzłach decyzyjnych, a same obiekty przechowują w liściach na tym samym poziomie. Ponadto wzbogacają strukturę drzewa o krawędzie pomiędzy sąsiednimi liśćmi, które zapewniają bezpośredni dostęp do kolejnych

posortowanych elementów. Program w Pythonie: http://python-wiki.appspot.com/?p=5846034605408256

https://www.cs.usfca.edu/~galles/visualization/BTree.html

(26)

TWORZENIE B-DRZEWA 3. STOPNIA

Tworzenie B-drzewa dla ciągu kluczy jest złożonym procesem wymagającym wykonywanie operacji przywracających takie uporządkowanie drzewa (https://www.cs.usfca.edu/~galles/visualization/BTree.html):

Dodawanie nowego elementu do B-drzewa składa się z kilku kroków:

1. Idź od korzenia do odpowiedniego liścia w drzewie poruszając się po węzłach następująco:

w lewo po gałęziach, jeśli klucz jest mniejszy lub równy od lewej wartości klucza w węźle,

w prawo po gałęziach, jeśli klucz jest silnie większy od prawej wartości klucza w węźle,

w kierunku środkowej gałęzi, jeśli klucz jest silnie większy od lewej wartości i mniejszy lub równy od prawej wartości klucza.

2. Dodaj element do liścia w sposób uporządkowany, jeśli nie przechowuje jeszcze dwóch wartości.

3. Jeśli już zawiera 2 wartości, podziel go na dwa węzły, a środkową wartość przekaż do rodzica, a jeśli nie istnieje, utwórz go. Rodzic będzie wskazywał te dwa węzły, jeśli nie zawierał dwóch wartości.

4. Jeśli rodzic zawierał dwie wartości, również się dzieli i środkową wartość przekazuje do swojego rodzica, a jeśli takowy nie istnieje, utworzy go. Jeśli istnieje i jest pełny, rekurencyjnie do góry powtarza ten krok.

https://www.youtube.com/watch?v=coRJrcIYbF4 https://www.youtube.com/watch?v=C_q5ccN84C8

(27)

TWORZENIE B+DRZEWA 3. STOPNIA

Tworzenie B+drzewadla ciągu kluczy jest złożonym procesem wymagającym wykonywanie operacji przywracających takie uporządkowanie drzewa (https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html):

Dodawanie nowego elementu do B+drzewa składa się z kilku kroków: https://www.youtube.com/watch?v=_nY8yR6iqx4 1. Idź od korzenia do odpowiedniego liścia w drzewie poruszając się po węzłach następująco:

w lewo po gałęziach, jeśli klucz jest mniejszy lub równy od lewej wartości klucza w węźle,

w prawo po gałęziach, jeśli klucz jest silnie większy od prawej wartości klucza w węźle,

w kierunku środkowej gałęzi, jeśli klucz jest silnie większy od lewej wartości i mniejszy lub równy od prawej wartości klucza.

2. Dodaj element do liścia w sposób uporządkowany, jeśli nie przechowuje jeszcze dwóch wartości.

3. Jeśli już zawiera 2 wartości, podziel go na dwa węzły, lewy zawierający mniejszą wartość od środkowego, a prawy zawierający środkowy i większy element. Połącz krawędzią te dwa węzły. Ponadto środkową wartość klucza skopiuj do rodzica, a jeśli nie istnieje utwórz go. Rodzic wskazuje te dwa węzły, jeśli nie zawierał dwóch wartości.

4. Jeśli rodzic zawierał dwie wartości, również się dzieli i środkową wartość przekazuje do swojego rodzica, a jeśli takowy nie istnieje, tworzy go. Jeśli istnieje i jest pełny, rekurencyjnie do góry powtarza ten krok.

B+drzewo jest umożliwia szybki

dostęp w czasie logarytmicznym do elementów listy!

(28)

AVB+DRZEWA 3. STOPNIA

AVB+drzewałączą koncepcje binarnych drzew wyszukiwania (BST), B-drzew, B+drzew oraz sortujących list podwójnie wiązanych. Są samo-wyważającą i samo-sortującą się złożoną strukturą (grafem), który zapewnia szybki dostęp do wszystkich kluczy (pesymistycznie logarytmiczny

w przypadku braku duplikatów, optymistycznie stały) przechowywanych w drzewie oraz stały dostęp do kluczy poprzednich i następnych

według wybranej metryki. Dodawanie i usuwanie elementów do AVB+drzew jest procesem złożonym składającym się z kilku kroków (o złożoności maksymalnie logarytmicznej), gdyż te operacje wymagają czasami przywrócenie doskonałego

wyważenia drzewa: http://home.agh.edu.pl/~horzyk/lectures/ci/CI-AGDSandAVB+trees.pdf

(29)

DEFINICJA GRAFU

Graf to struktura danych składająca się z wierzchołkówi krawędzi, łączących dwa wierzchołki.

Grafem nieskierowanymnazywamy parę G = (V, E), gdzie V jest niepustym i skończonym zbiorem wierzchołków (węzłów), a E jest dowolnym skończonym zbiorem krawędzi E  P2(V)

rozpoczynających się w jednym a kończących się w drugim wierzchołku, reprezentowanych w postaci nieuporządkowanych par elementów zbioru V.

Wierzchołkigrafu mogą być etykietowane, kolorowane, krawędzie ważone, niosąc dodatkową informację.

Grafem pełnym nazywamy graf, w którym każde dwa wierzchołki są ze sobą połączone krawędzią.

Graf pełny zawierający k wierzchołków nazywamy k-kliką.

Grafem skierowanym nazywamy parę G = (V, D), gdzie V jest niepustym i skończonym zbiorem

wierzchołków, a D jest dowolnym skończonym zbiorem krawędzi skierowanych D  P(V, V), reprezentowanych w postaci uporządkowanych par elementów zbioru V.

Grafem ważonym nazywamy trójkę G = (V, E, w), gdzie G = (V, E) jest grafem, a w: E → R jest

funkcją określającą wagękrawędzi.

Multigrafem nazywamy trójkę G = (V, E, ϕ), gdzie G = (V, E)

jest grafem, a ϕ: E → N jest funkcją określającą krotność krawędzi.

W multigrafie dopuszcza się istnienie pętlii krawędzi wielokrotnie łączących

4 5

3 2

1 4

5 3

2 1

4 5

3 2 1

4

5 3

2

1 20

46 17 15

12

34 16 21

3 2

4

5 3

2 1

4

5 3

2 1

(30)

WŁAŚCIWOŚCI GRAFU

Graf jest spójny, gdy istnieje droga w grafie pomiędzy każdą parą wierzchołków.

Lasem nazywamy graf, którego żaden podgraf nie zawiera cyklu.

Wierzchołki nazywamy sąsiednie, jeśli istnieje krawędź (skierowana lub nieskierowana) pomiędzy nimi.

Wierzchołeknazywamy izolowanym, jeśli nie jest połączony żadną krawędzią z innym wierzchołkiem.

Wierzchołki incydentne to wierzchołki, do których prowadzi krawędź z danego wierzchołka.

Stopień wierzchołka w grafie G określony jest poprzez jego ilość wierzchołków incydentnych, tzn. krawędzi wychodzących z niego.

Ścieżką (drogą) w grafie nazywamy ciąg krawędzi, po których należy przejść od wierzchołka startowego do wierzchołka końcowego poprzez ew. wierzchołki pośrednie.

W grafie może istnieć wiele różnych ścieżek pomiędzy tymi samymi wierzchołkami.

Długość ścieżki (drogi) w grafie określona jest przez ilość krawędzi na tej ścieżce (drodze).

Ścieżkę nazywamy prostą, jeśli przez każdą krawędź na ścieżce przechodzimy tylko raz.

Najkrótsza ścieżka w grafie pomiędzy dwoma wierzchołkami to ścieżka o minimalnej ilości krawędzi spośród dostępnych ścieżek prowadzących pomiędzy tymi wierzchołkami.

Najkrótsza ścieżka w grafie ważonym określana jest przez minimalną sumę wag krawędzi na ścieżkach prowadzących pomiędzy dwoma wierzchołkami.

(31)

WŁAŚCIWOŚCI GRAFU

Ścieżką Hamiltona nazywamy ścieżkę prostą przechodzącą przez wszystkie

wierzchołki grafu dokładnie raz.

Ścieżką Eulera nazywamy ścieżkę prostą przechodzącą przez wszystkie

krawędzie grafu dokładnie raz.

Cyklem w grafie nazywamy ścieżkę (drogę) zamkniętą, czyli taką, która

rozpoczyna się i kończy w tym samym wierzchołku grafu.

Cyklem Hamiltona nazywamy cykl prosty przechodzący przez wszystkie

wierzchołki grafu dokładnie raz (rozpoczynający się i kończący w tym samym wierzchołku).

Cyklem Eulera nazywamy cykl prosty przechodzący przez wszystkie krawędzie

grafu dokładnie raz (rozpoczynający się i kończący w tym samym wierzchołku).

Graf nazywamy acyklicznym, jeśli nie posiada żadnych cykli.

Graf nazywamy planarnym, jeśli da się go narysować na płaszczyźnie tak, aby żadne jego krawędzie nie przecinały się.

Pętlą w grafie nazywamy krawędź łączącą wierzchołek sam ze sobą.

(32)

REPREZENTACJA GRAFU

Grafy nie są strukturą hierarchiczną ani liniową, ich wierzchołki mogą mieć różną ilość krawędzi wychodzących (różną ilość incydentnych wierzchołków), więc

struktura grafu nie odzwierciedla w naturalny sposób budowy pamięci komputerowych RAM.

Implementując grafy wykorzystujemy tablice lub listy do przechowywania informacji na temat sąsiednich (incydentnych) wierzchołków lub łączących je krawędzi.

Grafy reprezentujemy i implementujemy zwykle w postaci:

• Listy krawędzi (list of edges)

• Macierzy sąsiedztwa (adjacency matrix)

• Macierzy incydencji (incidence matrix)

• Listy sąsiedztwa (adjacency list)

Przy założeniu, iż żaden wierzchołek w grafie nie jest izolowany,

lista krawędzi składa się z dwójek lub trójek, zawierających:

• etykietę wierzchołka, z którego krawędź wychodzi,

• etykietę wierzchołka, do którego krawędź dochodzi,

• wagę krawędzi (opcjonalnie).

PRZYKŁAD: [(1,3) , (1,4) , (2,1) , (2,3) , (2,4) , (2,5) , (3,2) , (3,4) , (3,5) , (4,1) , (4,5) , (5,1)]

4

5 3

2 1

(33)

MACIERZ SĄSIEDZTWA

Macierz sąsiedztwa jest macierzą kwadratową o stopniu równym ilości wierzchołków grafu.

Każdy wierzchołek grafu jest indeksowany jedną z kolejnych liczb od 0 do N-1, gdzie N określa ilość wierzchołków takiego grafu.

Indeksy wierszy macierzy sąsiedztwa odpowiadają indeksom wierzchołków startowych vi. Indeksy kolumn macierzy sąsiedztwa odpowiadają indeksom wierzchołków końcowych vj.

Każda komórka takiej macierzy reprezentuje jedno możliwe połączenie pomiędzy wierzchołkami vioraz vj:

0 – oznacza brak krawędzi 1 – oznacza istniejącą krawędź albo w - oznacza wagę krawędzi

i-ty wiersz takiej macierzy jest listą indeksów wierzchołków, do których prowadzą krawędzie z wierzchołka o indeksie i, gdy wartości w komórce są niezerowe.

j-ty wiersz takiej macierzy jest listą indeksów wierzchołków, z których prowadzą krawędzie do wierzchołka

o indeksie j, gdy wartości w komórce są niezerowe.

Macierz sąsiedztwa dla grafu nieskierowanego jest symetryczna względem głównej przekątnej, ponieważ jeśli istnieje krawędź (vi , vj),

to istnieje również krawędź (vj,vi).

4

5 3

2 1

(34)

MACIERZ SĄSIEDZTWA

Stopień i wierzchołka grafu nieskierowanego wyznaczymy zliczając liczbę niezerowych komórek w i-tym wierszu macierzy sąsiedztwa.

Stopień i wierzchołka grafu skierowanego wyznaczymy zliczając liczbę niezerowych komórek w i-tym wierszu oraz i-tej kolumnie macierzy sąsiedztwa. Możemy też osobno policzyć ilość krawędzi wychodzących (w wierszach) oraz ilość krawędzi wchodzących (w kolumnach).

Indeksy sąsiadów wierzchołka i-tego znajdziemy w i-tym wierszu, jeśli wartość pola jest niezerowa.

i-ty wierzchołek jest izolowany, jeśli zarówno i-ty wiersz jak również i-ta kolumna zawiera same zera.

Wierzchołki zawierające pętle posiadają niezerowe wartości na przekątnej macierzy sąsiedztwa.

Sprawdzenie istnienia krawędzi pomiędzy wierzchołkami w macierzy sąsiedztwa jest operacją o złożoności O(1).

Wadą takiej reprezentacji jest

kwadratowa złożoność pamięciowa.

Wiele pól takich macierzy zawiera zera, poza przypadkiem grafów pełnych (klik).

4

5 3

2 1

(35)

MACIERZ INCYDENCJI

Macierz incydencji jest macierzą o wymiarze N x K, gdzie N – oznacza ilość wierzchołków grafu, a K liczbę jego krawędzi. Każdy wiersz odwzorowuje jeden wierzchołek grafu, a każda kolumna jedną jego krawędź. Zawartość komórki takiej macierzy określa powiązanie i-tego wierzchołka z k-tą krawędzią w następujący sposób dla grafu skierowanego:

0 – oznacza, że i-ty wierzchołek nie jest ani początkiem ani końcem k-tej krawędzi,

1 albo w – oznacza, że i-ty wierzchołek jest początkiem k-tej krawędzi (o wadze w - opcjonalnie),

-1 – oznacza, że i-ty wierzchołek jest końcem k-tej krawędzi.

Jeśli graf jest nieskierowany, wtedy początek i koniec oznaczony jest wartością 1.

Jeśli graf zwiera pętle, wtedy początek i koniec wypada w tym samym wierzchołku, co zaznaczamy w takim grafie wartością 2. Krawędzie wielokrotne możemy reprezentować więc bez trudu.

Ze względu na to, iż każda krawędź ma dokładnie jeden początek i jeden koniec, w każdej kolumnie takiej macierzy występuje dokładnie jedna 1 i jedna -1, a pozostałe wartości są 0.

Każdy wiersz takiej macierzy może natomiast zawierać wiele 1 i -1, gdyż może być początkiem lub końcem wielu krawędzi.

i-ty wiersz dla wierzchołka izolowanego zawiera same zera.

Złożoność pamięciowa: O(N x K) 4

5 3

2 1

(36)

LISTA SĄSIEDZTWA

Lista sąsiedztwa implementowana jest jako tablica list (w Pythonie jako lista list), tzn. każdy element listy wierzchołków startowych zawiera listę wierzchołków końcowych, czyli listę sąsiadów, z którymi jest połączony krawędzią.

Nieco trudniej w grafie skierowanym znajdujemy wierzchołki, od których prowadzi krawędź do danego wierzchołka, gdyż trzeba przeszukać wszystkie listy zawierające indeks wierzchołka końcowego O(K), lecz częściej interesuje nas lista sąsiadów, czyli do których wierzchołków prowadzi krawędź z danego wierzchołka.

Problem ten nie występuje w przypadku grafów nieskierowanych.

To zdecydowanie najefektywniejszy (pod względem pamięciowym) i najczęściej stosowany sposób implementacji grafu, gdyż nie zawiera

niewykorzystanych komórek pamięci (za wyjątkiem wierzchołków izolowanych).

Chcąc zapisać wagę krawędzi, należy wykorzystać listę list obiektów (klas), których kluczem jest indeks (etykieta) wierzchołka docelowego, a wewnątrz klasy zapisana jest waga.

4

5

3

2

1

(37)

PRZECHODZENIE PO WIERZCHOŁKACH GRAFU

Po wierzchołkach grafu nie sposób przechodzić w sposób liniowy, więc najczęściej stosowane są drzewa rozpięte na grafie oraz algorytmy wykorzystywane do przeszukiwania drzew.

W grafach jednak mogą występować cykle i pętle, co mogłoby spowodować zapętlenie się takiego algorytmu i kręcenie się po tych samych wierzchołkach wielokrotnie.

Dla uniknięcia tego stosujemy dodatkowy parametr typu boolowskiego, w którym zapisujemy, czy wierzchołek został już odwiedzony/przeszukany czy też nie.

Jeśli więc wzbogacimy algorytmy przeszukiwania drzew o taki parametr, będziemy mogli wykorzystać algorytmy:

DFS – depth first search – przeszukiwanie wgłąb, BFS – breadth first search – przeszukiwanie wszerz również do przeszukiwania grafów.

Przejście grafu (graph traversal) algorytmem DFS wykonujemy następująco:

1. Przeszukujemy bieżący wierzchołek.

2. Zaznaczamy bieżący wierzchołek jako przeszukany.

3. Przechodzimy do pierwszego nie przeszukanego jeszcze wierzchołka incydentnego bieżącego wierzchołka.

4. Jeśli wszystkie incydentne wierzchołki zostały już przeszukane, wtedy wracamy do wierzchołka, z którego przeszliśmy do tego wierzchołka i wykonujemy w nim punkt 3.

5. Kończymy przeszukiwać graf, gdy wierzchołek nie posiada poprzednika, 4

5 3

2 1

(38)

PRZECHODZENIE PO GRAFIE ALGORYTMEM DFS

1. Rozpoczynamy przeszukiwanie grafu od wierzchołka nr 1.

2. Przechodzimy do pierwszego jego incydentnego i jeszcze nie odwiedzonego wierzchołka, czyli do wierzchołka nr 3.

3. Z wierzchołka nr 3. przechodzimy do jego pierwszego incydentnego, nie odwiedzonego wierzchołka nr 2.

4. Przeszukując listę incydentnych wierzchołków wierzchołka nr 2, pomijamy wierzchołek nr 1 i nr 3, gdyż one były już odwiedzone, znajdując wierzchołek nr 4 jako pierwszy jeszcze nieodwiedzony.

5. W wierzchołku nr 4 na liście incydencji pomijamy odwiedzony wcześniej wierzchołek nr 1 i dochodzimy do nieodwiedzonego jeszcze wierzchołka nr 5 i przechodzimy do niego.

6. Na liście incydencji w wierzchołku nr 5 nie znajduje się już

żaden jeszcze nieodwiedzony wierzchołek, więc wycofujemy się.

7. Podobnie postępujemy w wierzchołkach nr 4, 2, 3, 1, gdzie też nie znajdujemy już żadnego nieodwiedzonego wierzchołka.

8. Kończymy przeszukiwanie grafu.

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

(39)

PRZECHODZENIE PO GRAFIE ALGORYTMEM BFS

Przejście grafu algorytmem BFS wykonujemy następująco:

1. Tworzymy pustą kolejkę przeszukiwanych wierzchołków.

2. Dodajemy do kolejki wierzchołek, od którego rozpoczynamy przeszukiwanie grafu, i zaznaczamy go jako uwzględniony w przeszukiwaniu (odwiedzony).

3. Ściągamy z kolejki pierwszy wierzchołek i go przeszukujemy.

4. Do kolejki przeszukiwanych wierzchołków wpisujemy wszystkie jego incydentne wierzchołki, które nie zostały dotąd jeszcze

przeszukane i zaznaczamy je jako uwzględnione w przeszukiwaniu.

5. Przechodzimy do punktu 3.

6. Kończymy przeszukiwać graf, gdy kolejka zostanie opróżniona.

Ilość potencjalnie dodawanych elementów do kolejki może jest O(N), a więc algorytm BFS jest obarczony liniową złożonością pamięciową.

Zwykle dodawanych jest mniej elementów do kolejki, lecz w przypadku grafu pełnego, zostanie dodanych N-1 elementów (sąsiadów).

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

4

5 3

2 1

(40)

ZNAJDYWANIE DROGI W LABIRYNCIE

Załóżmy, że mamy określony dowolny labirynt jako macierz, w której każde pole jest wierzchołkiem grafu. Wierzchołki są ze sobą połączone, jeśli istnieje bezpośrednie przejście pomiędzy nimi.

Oznacza to, iż wierzchołki reprezentujące ściany będą wierzchołkami izolowanymi. Najkrótszą drogę w grafie możemy znaleźć algorytmem BFS, który będzie przechodził od startu (korzenia drzewa BFS) do mety szukając przejścia na najwyższym poziomie w drzewie przeszukiwań, co zapewni

znalezienie najkrótszej ścieżki w grafie. Do zaznaczenia odwiedzonych wierzchołków oraz drogi w wierzchołkach zapisuje się informację, skąd algorytm dotarł do danego wierzchołka (L – z lewa, P – z prawa, G – z góry, D – z dołu). Przykład demonstruje typowe przekształcenie zadania na problem grafowy i szukanie rozwiązania z wykorzystaniem teorii grafów.

(41)

ZAGADNIENIA GRAFOWE

Można badać różne ciekawe i potrzebne właściwości grafów:

• Spójność (umożliwiająca znalezienie drogi w grafie pomiędzy wierzchołkami)

• Znajdywanie spójnych podgrafów

• Znajdywanie silnie spójnych podgrafów

• Znajdywanie drzew rozpinających

• Znajdywanie mostów (krawędzi) i punktów artykulacji (wierzchołków), których usunięcie spowoduje zwiększenie liczby spójnych podgrafów w grafie.

Graf/Podgraf nazywamy spójnym (connected), gdy dla każdych dwóch wierzchołków istnieje ścieżka, która je ze sobą łączy (nie koniecznie dwustronnie – w przypadku grafu/podgrafu skierowanego). Graf/Podgraf jest niespójny w przeciwnym wypadku.

Graf/Podgraf nazywamy silnie spójnym (strongly connected), gdy istnieją ścieżki pomiędzy każdymi dwoma wierzchołkami tego grafu/podgrafu, czyli można z każdego wierzchołka przejść do każdego innego.

Drzewo rozpinające (spanning tree) to drzewo zawierające wszystkie wierzchołki grafu i część jego krawędzi. Węzły w takim drzewie połączone są istniejącymi krawędziami grafu.

Może istnieć wiele różnych drzew rozpinających dla danego grafu. Drzewa te możemy znaleźć np. wcześniej omówionymi algorytmami DFS lub BFS.

Mostem (bridge) w grafie nazywamy każdą krawędź, której usunięcia spowoduje zwiększenie liczby spójnych podgrafów.

Punktem artykulacji (articulation point / cut vertex) jest każdy wierzchołek w grafie, którego usunięcie spowoduje zwiększenie liczby spójnych podgrafów.

(42)

GRAFY DWUDZIELNE

Dowolny nieskierowany, spójny grafnazywamy dwudzielnym (bigraph, bipartiteness), jeśli jego wierzchołki możemy podzielić na dwa rozłączne zbiory w taki sposób,

iż wierzchołki są połączone tylko pomiędzy wierzchołkami tych zbiorów,

natomiast nie istnieją połączenia pomiędzy wierzchołkami wewnątrz tych zbiorów.

Jeśli krawędź grafu dwudzielnego łączy ze sobą wierzchołki z dwóch różnych zbiorów,

wtedy graf będzie dwudzielny, jeśli uda nam się pokolorować (poetykietować) wszystkie jego wierzchołki dwoma kolorami tak, aby żaden z jego sąsiadów nie był tego samego koloru, co wierzchołek, którego sąsiedztwo rozważamy.

Szukanie grafu dwudzielnego polega na przechodzeniu po grafie algorytmem BFS lub DFS i naprzemiennym kolorowaniu kolejnych wierzchołków. Jeśli na drodze napotkamy na pokolorowany już węzeł tym samym kolorem, tzn. że graf nie jest dwudzielny.

Jeśli natomiast uda nam się w taki sposób

pokolorować cały graf, wtedy graf jest dwudzielny.

Przykład i algorytm można znaleźć:

http://eduinf.waw.pl/inf/alg/001_search/0131.php

(43)

PROBLEM KOJARZENIA MAŁŻEŃSTW

Zagadnienie kojarzenia małżeństw polega na dobraniu w pary n panien

i n kawalerów, przy czym każda panien jest skłonna zaakceptować tylko jednego z k ≤ n kawalerów.

Jest to typowy przykład zagadnienia grafowego, które możemy rozwiązać przy pomocy grafu dwudzielnego, gdyż wierzchołki jednego zbioru reprezentują kawalerów, drugiego zbioru panny, a krawędzie określają preferencję panien.

Graf jest dwudzielny przy założeniu związków heteroseksualnych, czyli żadne dwie panny ani dwóch kawalerów nie łączy się ze sobą.

Jeśli każdy podzbiór k panien akceptuje co najmniej k kawalerów, wtedy problem jest zawsze rozwiązywalny (twierdzenie Philipa Halla). Rozważamy ścieżki alternatywne.

Przykład i algorytm można znaleźć:

ANIA ELA IRENA OLA ULA ZOSIA

ADRIAN JAN LESZEK PIOTR STEFAN TOMEK

(44)

CYKLICZNOŚĆ GRAFÓW

Cykl jest ścieżką zamkniętą prowadzącą przez wierzchołki grafu w taki sposób, iż rozpoczyna się i kończy w tym samym wierzchołku.

Cykle nie muszą przechodzić po wszystkich wierzchołkach grafu, aczkolwiek często takie cykle rozważamy (np. cykl Hamiltona, cykl Eulera).

W celu sprawdzenia, czy graf zawiera jakiś cykl, wystarczy przejść po nim algorytmem DFS i jeśli natrafimy na wierzchołek wcześniej odwiedzony, tzn. że graf jest cykliczny.

Jeśli nie znajdziemy wcześniej odwiedzonego wierzchołka, wtedy graf jest acykliczny.

Przykładami grafów acyklicznych są drzewa i lasy.

Chcąc wyznaczyć cykl w grafie, kolejno przechodzone wierzchołki należy zapisywać na stosie.

Jeśli znajdziemy odwiedzony już wierzchołek, wtedy rozbierając stos wyznaczymy cykl rozpoczynający się i kończący w tym wierzchołku.

Cykl Eulera jest ścieżką zamkniętą (cyklem), która przechodzi dokładnie jeden raz przez każdą krawędź grafu, tzn. kończy się w wierzchołku, w którym się rozpoczęła.

Cykl Hamiltona jest ścieżką zamkniętą (cyklem), która przechodzi dokładnie jeden raz przez każdy wierzchołek grafu, tzn. kończy się w wierzchołku, w którym się rozpoczęła.

Ścieżka Eulera jest ścieżką, która przechodzi dokładnie jeden raz przez każdą krawędź grafu.

Ścieżka Hamiltona jest ścieżką, która przechodzi dokładnie jeden raz przez każdy wierzchołek grafu.

Przykłady i algorytmy można znaleźć:

Cykle: http://eduinf.waw.pl/inf/alg/001_search/0132.php, http://eduinf.waw.pl/inf/alg/001_search/0133.php Cykl Eulera: http://eduinf.waw.pl/inf/alg/001_search/0135.php

Cykl Hamiltona: http://eduinf.waw.pl/inf/alg/001_search/0136.php

(45)

ALGORYTMY NAJKRÓTSZEJ ŚCIEŻKI W GRAFIE WAŻONYM DIJKSTRY, BELLMANA-FORDA, FLOYDA-WARSHALLA, A*

Szukanie najkrótszej ścieżki w grafie ważonym prowadzącej od wybranego wierzchołka do wskazanego lub wszystkich pozostałych jest jednym z podstawowych zagadnień w teorii grafów, mających wiele praktycznych zastosowań, np. w nawigacji satelitarnej, przesyłania wiadomości przez sieć routerów, w sieciach telekomunikacyjnych,

wyznaczanie połączeń samolotowych o najniższym koszcie lub czasie przelotu itd.

Jak dotrzeć z wierzchołka S do wierzchołka C przy najmniejszym koszcie wyznaczonym przez wagi połączeń pomiędzy wierzchołkami grafu?

4 3

2 1

6 0

3 1

9

8

4 3

7

8 7 6 2 4

9 5

3

S

C

(46)

4 3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0

3 4

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0

1

8 7 3 4

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0

1

8 6

4

4 3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0 1

7 6

4 13

8

4 3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0 1

7 6

4 13

4 8

3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0 1

7 6

4 12

4 8

3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0 1

7 6

4 12

4 8

3

2 1

6 0

3

1 9

8

4 3

7

8 7 5 2 4

9 5

3

S

C

0 1

7 6

4 12

8

ALGORYTM DIJKSTRY

W algorytmie Dijkstry każdy wierzchołek posiada cechę odległości stałą lub tymczasową, określającą najkrótszą

(w sensie sumy dodatnich wag) ścieżkę prowadzącą od wierzchołka początkowego S do wierzchołka końcowego (celu) C. Na początku cechy wszystkich wierzchołków poza startowym są tymczasoweo wartości +, natomiast wierzchołek startowy ma cechę stałąo wartości 0. W każdym kroku algorytmu wybieramy wierzchołek u o najmniejszej wartości tymczasowej, przechodzimy po wychodzących z niego krawędziach do pozostałych wierzchołków tymczasowych v i jeśli droga dojścia do tego wierzchołka dist(u) plus wartość krawędzi dist(u,v) jest mniejsza od wartości tymczasowej tego wierzchołka dist(v), wtedy aktualizujemy tą wartość oraz poprzednika Succ(v)=u tego wierzchołka.

Gdy wierzchołek docelowy C przyjmie wartość stałą, wtedy możemy przerwać algorytm i odczytać wstecz drogę dojścia do tego wierzchołka od wierzchołka startowego S:

4 3

2 1

6 0

3

1 9

8

7

8 7 5 2 4

9 5

3

S

C

0

1 4 12

Przykłady i algorytmy można znaleźć na:

http://eduinf.waw.pl/inf/alg/001_search/0138.php

(47)

ALGORYTM FLEURY’EGO

Cytaty

Powiązane dokumenty

Każdy węzeł drzewa powinien zawierać: dane właściwe (lub wskaźnik na nie), wskaźnik na rodzica, wskaźniki na lewego i prawego potomka.. Samo drzewo powinno przechowywać wskaźnik

Drzewo wyrażeń arytmetycznych jest to drzewo binarne, w którym każdy wierzchołek ma albo dwóch synów albo wcale. W takim drzewie liście etykietowane są stałymi

[r]

Binarne drzewo poszukiwań (BST – binary search tree) to binarne drzewo reprezentujące elementy multizbioru w taki sposób, iż każdy wierzchołek ma po lewej elementy mniejsze, a

drzewo – graf reprezentujący regularną strukturę wskaźnikową, gdzie każdy element zawiera dwa lub więcej wskaźników (ponumerowanych) do takich samych elementów; węzły

wysokość drzewa – długość najdłuższej ścieżki drzewa waga drzewa – całkowita liczba węzłów w drzewie... Jakie są wady reprezentacji napisu w postaci ze

wysokość drzewa – długość najdłuższej ścieżki drzewa waga drzewa – całkowita liczba węzłów w

wysokość drzewa – długość najdłuższej ścieżki drzewa waga drzewa – całkowita liczba węzłów w