Wyklad9_studentx

Download Report

Transcript Wyklad9_studentx

Definicja.
Parę (V,E), gdzie V jest skończonym zbiorem wierzchołków, a E jest
zbiorem krawędzi łączących wierzchołki nazywamy grafem.
Graf skierowany
•wierzchołki są uporządkowane
Graf nieskierowany •wierzchołki są nieuporządkowane
pętla
1
4
2
5
3
6
Graf skierowany, niespójny,
cykliczny
1
4
2
5
Graf nieskierowany,
niespójny, cykliczny
3
6
Graf jest drzewem (o n węzłach/wierzchołkach), jeśli spełniony jest dowolny
spośród warunków:
 ma n-1 krawędzi i nie ma cykli,
 ma n-1 krawędzi i jest spójny,
 każde dwa wierzchołki łączy dokładnie jedna ścieżka prosta (tzn. w ciągu
krawędzi między dwoma węzłami, żaden nie pojawia się dwukrotnie),
 graf jest spójny, ale przestanie być spójny, jeśli zostanie usunięta dowolna
krawędź.
Drzewo swobodne (wolne) = spójny, acykliczny graf nieskierowany
Las = zbiór rozłącznych drzew = graf nieskierowany, acykliczny, niekoniecznie
spójny.
Różne typy drzew (od najbardziej do najmniej ogólnego):
 drzewa swobodne,
 drzewa z korzeniem = drzewo wolne z jednym wyróżnionym wierzchołkiem
(korzeniem),
 drzewa uporządkowane = drzewa z korzeniem, w których następniki każdego
węzła są uporządkowane,
 m-drzewa = drzewa, w których każdy węzeł musi mieć określoną liczbę
potomków uporządkowanych w określony sposób (np. drzewa trójkowe = każdy
węzeł drzewa zawiera łącze do lewego, środkowego i prawego poddrzewa),
 drzewa binarne = 2-drzewa.
Drzewo można zdefiniować rekurencyjnie w następujący sposób:
Pojęcia: przodek węzła, potomek węzła, poprzednik węzła (ojciec),
następnik węzła (syn), bracia – węzły o tym samym poprzedniku, korzeń –
nie ma poprzednika, liść – węzeł zewnętrzny, węzeł wewnętrzny.
Pojęcia:
 ścieżka – jednoznacznie wyznaczony ciąg krawędzi łączący wierzchołek
z korzeniem;
 długość ścieżki – liczba krawędzi na ścieżce;
 poziom węzła – długość ścieżki od korzenia do węzła plus 1, co
stanowi liczbę węzłów na ścieżce;
 wysokość niepustego drzewa – największy z poziomów węzłów w
drzewie.
Przykłady:
• drzewo puste jest drzewem o wysokości 0;
• pojedynczy węzeł jest drzewem o wysokości 1 (jest to jedyny przypadek, kiedy
węzeł jest jednocześnie liściem i korzeniem).
0
1
2
3
4
h=4
Jako uporządkowane są to 2 różne drzewa, a jako ukorzenione - te
drzewa są jednakowe.
Przykład.
Rozważmy n-elementową listę. Aby odnaleźć element, poszukiwanie trzeba rozpocząć od
początku listy i przeglądać ją aż do znalezienia elementu lub osiągnięcia końca listy. Nawet,
jeśli lista jest uporządkowana, to przeszukiwanie musi zawsze zaczynać się od pierwszego
rekordu.
Jeśli wszystkie elementy przechowujemy w drzewie uporządkowanym (elementy są ułożone w
pewnym porządku), to liczba sprawdzań może być znacząco zmniejszona, nawet kiedy
szukamy najdalszego elementu.
Czy tu jest dobre kryterium uporządkowania? Np. do sprawdzenia, czy 31 jest
na liście trzeba wykonać 8 porównań.
Rozwiązanie
Drzewa binarne
Reprezentacja drzewa uporządkowanego (określona jest kolejność potomków
każdego węzła)
Lista synów – prawe łącze
Pierwszy węzeł listy synów – lewe łącze
Definicja.
Drzewo binarne jest to węzeł zewnętrzny lub wewnętrzny dołączony do
pary drzew binarnych, które nazywa się odpowiednio lewym i prawym
poddrzewem tego węzła.
Inaczej, każdy węzeł ma co najwyżej dwóch synów, a każdy syn jest opisany jako
lewy albo prawy.
struct wezel
{
int info; //lub dowolny inny typ danych
struct wezel *lewy, *prawy;
}
Reprezentacja
drzewa
binarnego
Własności drzew binarnych
•Drzewo binarne zawierające n węzłów wewnętrznych ma n+1 węzłów zewnętrznych.
•Drzewo binarne zawierające n węzłów wewnętrznych ma 2n łączy, z których n-1
prowadzi do węzłów wewnętrznych, a n+1 do węzłów zewnętrznych.
•Poziom węzła drzewa jest o 1 większy od poziomu jego ojca (poziom korzenia
wynosi 0).
•Wysokość drzewa to największa liczba spośród poziomów wszystkich węzłów
drzewa.
•Długość ścieżki drzewa równa się sumie poziomów wszystkich węzłów drzewa.
Własności drzew binarnych – cd.
•Długość ścieżki wewnętrznej jest równa sumie poziomów wszystkich węzłów
wewnętrznych drzewa (analogicznie dla ścieżki zewnętrznej).
•Długość ścieżki zewnętrznej dowolnego drzewa binarnego zawierającego n węzłów
wewnętrznych jest o 2n większa od długości ścieżki wewnętrznej.
•Wysokość drzewa binarnego zawierającego n węzłów wewnętrznych wynosi co
najmniej lnn, a co najwyżej n-1.
•Długość ścieżki wewnętrznej drzewa binarnego zawierającego n węzłów
wewnętrznych wynosi co najmniej nln(n/4), a co najwyżej n(n-1)/2 .
Własności drzew binarnych – cd.
W myśl definicji poziomu węzła, korzeń jest na poziomie I, jego synowie na poziomie
II, itd. Jeśli wszystkie węzły na wszystkich poziomach, oprócz ostatniego, miałyby po
dwóch synów, to mielibyśmy 20=1 węzłów na poziomie I, 21=2 węzłów na poziomie
II, itd. Ogólnie : 2n węzłów na poziomie n+1.
Drzewo spełniające ten warunek nazywamy pełnym drzewem binarnym.
W drzewie tym wszystkie węzły wewnętrzne mają obydwu swoich synów, a wszystkie
liście znajdują się na tym samym poziomie. Zatem każde drzewo binarne ma co
najwyżej 2i węzłów na i+1 poziomie.
Drzewa poszukiwań binarnych (BST) – drzewa binarne, w których na
lewo od danego wierzchołka są elementy równe lub mniejsze, a na
prawo większe.
Można łatwo stwierdzić, czy element jest
umieszczony w takim drzewie, bo
przeszukujemy jedną ścieżkę.
Inne przykłady:
Algorytm znajdowania elementu w drzewie c) jest następujący. W
każdym węźle porównujemy przechowywaną w nim wartość z
kluczem, którego szukamy. Jeśli klucz jest mniejszy niż ta wartość, to
przechodzimy do lewego poddrzewa i próbujemy dalej. Jeśli większy,
to próbujemy w prawym poddrzewie.
Drzewa binarne można implementować jako tablice lub struktury wiązane.
struct wezel
{
int info; //lub dowolny inny typ danych
struct wezel *lewy, *prawy;
};
typedef struct wezel *drzewo_typ;
Drzewa BST zostały stworzone w celu przyspieszenia wyszukiwania. Proces ten
może wyglądać następująco:
drzewo_typ szukaj1 (drzewo_typ wezel, int info)
{
while (wezel)
{
if (info == wezel->info) return wezel;
else if (info < wezel->info) wezel =wezel->lewy;
else wezel = wezel->prawy;
}
return NULL;
}
Pętli while możemy się pozbyć, stosując rekurencyjną implementację
procedury wyszukiwania:
drzewo_typ szukaj2 (drzewo_typ wezel, int info)
{
if (wezel)
{
if (info == wezel->info) return wezel;
else if (info < wezel->info) return szukaj2 (wezel->lewy, info);
else return szukaj2 (wezel->prawy, info);
}
else return NULL;
}
Przechodzenie drzewa – proces odwiedzania każdego węzła w drzewie
dokładnie raz.
Nie mamy narzuconej kolejności odwiedzania węzłów, zatem teoretycznie jest
tyle sposobów, ile permutacji węzłów: dla drzewa o n węzłach jest n! sposobów.
Dwa wyróżnione sposoby:
• przechodzenie wszerz,
• przechodzenie w głąb.
Przechodzenie wszerz – odwiedzamy każdy węzeł, zaczynając od korzenia i
posuwając się w dół poziom za poziomem, odwiedzając węzły na każdym
poziomie od strony lewej do prawej.
Otrzymamy ciąg: 13, 10, 25, 2, 12, 20, 31, 29
Implementacja za pomocą kolejki.
Przechodzenie w głąb – wędrujemy najdalej jak się da w lewo (prawo), a
potem cofamy się do najbliższego rozgałęzienia, robimy jeden krok w prawo
(lewo) i znowu idziemy jak najdalej w lewo (prawo), itd..
Mamy 3!=6 sposobów odwiedzania węzłów.
Trzy są standardowe.
Kolejność preorder
•węzeł, lewe poddrzewo, prawe poddrzewo
Kolejność inorder
•lewe poddrzewo, węzeł, prawe poddrzewo
Kolejność postorder
•lewe poddrzewo, prawe poddrzewo, węzeł
Przechodzenie przez drzewo:
Kolejność preorder
•węzeł, lewe poddrzewo, prawe poddrzewo
Kolejność inorder
•lewe poddrzewo, węzeł, prawe poddrzewo
Kolejność postorder
•lewe poddrzewo, prawe poddrzewo, węzeł
preorder (drzewo_typ wezel)
{
if(wezel)
{
visit(wezel);
preorder(wezel->lewy);
preorder(wezel->prawy);
}
}
inorder (drzewo_typ wezel)
{
if(wezel)
{
inorder(wezel->lewy);
visit(wezel);
inorder(wezel->prawy);
}
}
postorder (drzewo_typ wezel)
{
if(weze)
{
postorder(wezel->lewy);
postorder(wezel->prawy);
visit(wezel);
}
}
Zapamiętywanie danych nieuporządkowanych – kolejka, stos.
Nowa struktura danych:
Kolejka priorytetowa: jest wyposażona w funkcję wstawiania nowego
elementu i usuwania maksymalnego elementu.
Kolejki priorytetowe realizujemy za pomocą sterty (kopca) – czyli
pełnego drzewa binarnego o taj własności, że dla każdego węzła,
który nie jest korzeniem zachodzi:
przodek węzła >= węzeł
1
2
3
4
5
6
7
8
9
10
16
14
10
8
7
9
3
2
4
1
1
2
3
4
5
6
7
8
9
10
11
12
20
17
17
17
15
10
13
7
3
12
10
7
sterta.h
Jako klasa
class Sterta
{
public:
Sterta (int nMax)
{t=new int[nMax+1];
L=0;
}
void wstaw(int x);
int obsluz();
void DoGory(); //przywraca porzadek w stercie po dolaczeniu
//na koniec tablicy t nowego elementu
void NaDol();
void pisz();
private:
int *t;
int L;
//ilosc elementow
};
void Sterta::wstaw(int x)
{
t[++L]=x;
DoGory();
}
void Sterta::DoGory()
{
int temp=t[L];
int n=L;
while((n!=1)&&(t[n/2]<=temp))
{
t[n]=t[n/2];
n=n/2;
}
t[n]=temp;
}
int Sterta::obsluz()
{
int x=t[1];
t[1]=t[L--];
NaDol();
return x;
}
void Sterta::NaDol()
{
int i=1;
while(1)
{
int p=2*i; //lewy potomek wezla
// 'i'=p, prawy=p+1
if(p>L) break;
if(p+1<=L) //prawy potomek
//istnieje?
if(t[p]<t[p+1]) p++;
//przesuwamy sie do
// nastepnego elementu
if(t[i]>=t[p]) break;
int temp=t[p]; //zamiana element
t[p]=t[i];
t[i]=temp;
i=p;
}
}
//Wykorzystanie sterty do sortowania danych (metoda heapsort)
#include "sterta.h"
#include <iostream>
using namespace std;
int main()
{
int tab[14]={12,3,-12,9,34,23,1,81,45,17,9,23,11,4};
Sterta s(14);
for(int i=0;i<14;i++) s.wstaw(tab[i]);
//cout<<"sterta:\n";s.pisz();
for(int i=14;i>0;i--) tab[i-1]=s.obsluz();
cout<<"tablica posortowana:\n";
for(int i=0;i<14;i++) cout<<" "<<tab[i];
cout<<"\n\nNacisnij <Enter>";
cin.get();
return 0;
}