Służą do wyznaczenia liczby operacji dominujących Określając pesymistyczną lub średnią złożoność chcemy podawać tylko najważniejszą część informacji pochodzących z teoretycznych wyliczeń, czyli.

Download Report

Transcript Służą do wyznaczenia liczby operacji dominujących Określając pesymistyczną lub średnią złożoność chcemy podawać tylko najważniejszą część informacji pochodzących z teoretycznych wyliczeń, czyli.

Służą do wyznaczenia liczby operacji dominujących
Określając pesymistyczną lub średnią złożoność chcemy podawać tylko najważniejszą
część informacji pochodzących z teoretycznych wyliczeń, czyli rząd wielkości.
Jest on określany dla danych o dużym rozmiarze, więc mówimy np. o asymptotycznym
czasie (złożoności czasowej).
Funkcja taka musi więc przyjmować wartości dodatnie przynajmniej dla dostatecznie
dużych argumentów. Jest to tzw. funkcja asymptotycznie dodatnia.
Argumenty = liczby naturalne lub zero
Funkcja f(n) jest pomijalna względem g,
gdy n dąży do nieskończoności.
Gdy zwiększymy zakres argumentów „pomijalność” funkcji logarytmicznej względem
pierwiastkowej dla dużych argumentów jest jeszcze bardziej widoczna.
Definicja 15 ma szersze zastosowanie niż twierdzenie 16 (choć tw. jest
wygodniejsze).
Przykład:
Istotnie, połóżmy
Wówczas dla dowolnego
zachodzi nierówność:
Twierdzenia 16 nie można jednak zastosować, bo nie istnieje granica
Uwaga!
Własności 3 i 5 dotyczących dodawania i mnożenia rzędów wielkości
nie można przenieść na operacje odejmowania i dzielenia.
Przykład:
Przykład:
Rozważmy ponownie zbiór danych ZDWn jako n-wyrazowych ciągów
uporządkowanych liczb naturalnych. Rozważmy dalej typowy algorytm w
rodzaju „dziel i zwyciężaj” sprawdzenia, czy liczba naturalna x jest
elementem ciągu zdwZDWn.
Przypomnijmy:
Tmax(n)=2*(log2n)
Tśr(n)=1/2+log2n
=2*(log2n)-1
 ( n) 
1
12
(4(log 2 n) 2  1)
Łatwo zauważyć, licząc odpowiednie granice, że (n) jest rzędu
log2n/√3, jest więc mniejsza niż Tśr(n)=1/2+log2n , ale obie są rzędu
logarytmicznego.
Rachunek na tablicy
Poprawność
składniowa
Wypisywanie
zdań z języka
poprawnych
składniowo
Poprawność
semantyczna
Poprawność
algorytmu
Poprawne wartościowanie zdań języka, np.
w języku programowania – skutki
wystąpienia wyróżnionych części
programu, jak definicje typów, deklaracje
stałych i zmiennych, zmian wartościowania
zmiennych będących instrukcjami
programu.
Algorytm jako napis może być poprawny składniowo, ale
niepoprawny semantycznie, co jest równoznaczne z
niemożnością poprawnego deklarowania i wartościowania
zmiennych.
Powody:
w wyrażeniu występuje argument nie należący do typu związanego z
tym wyrażeniem (np. w wyniku działania funkcji otrzymujemy liczbę
rzeczywistą, a funkcja miała zwracać wartości całkowite);

wystąpienie
w algorytmie pętli o wyrażeniu logicznym stale
wartościowanym jako prawdziwe (pętla będzie wykonywana bez końca);
końcowe
wartościowanie zmiennych nie odpowiada oczekiwaniom
(program poprawnie kończy obliczenia, ale wyniki nie rozwiązują
postawionego zadania).
Niech
A –oznacza algorytm-program,
 -warunek (warunki), jakie powinny spełniać dane wejściowe (początkowe
wartościowania zmiennych) algorytmu A,
 -warunek, jaki powinny spełniać końcowe wartościowania zmiennych
(własności danych wyjściowych i ich związek z danymi wejściowymi)
Semantyczna poprawność nazywana jest też inaczej
pełną poprawnością. Dowodzenie jest trudne. Niekiedy
zadowalamy się sprawdzeniem elementów tej definicji.
Częściowa
semantyczna
poprawność
Własność
określoności
dla warunku
początkowego

Semantyczna
poprawność
Własność
stopu
Definicje elementów:
Dowodzenie
Indukcja
Metoda
niezmienników
Własność
stopu
Definicja 1
Częściowa
poprawność
W dowodzeniu indukcyjnym semantycznej poprawności algorytmu
przeprowadza się zwykle indukcję względem liczby powtórzeń instrukcji
iteracyjnej lub poziomu zagnieżdżenia realizacji procedury rekurencyjnej.
Przykład. Rozważmy algorytm przeszukiwania drzewa binarnego DB zadanego
przez wskaźnik na korzeń DB określonego przez zmienną typu el_drzewa
zdefiniowanego następująco:
struct element
{
int wartosc;
element *pien;
element *konar_lewy;
element *konar_prawy;
}
typedef element, *el_drzewa;
struct element
{
int wartosc;
element *pien;
element *konar_lewy;
element *konar_prawy;
}
typedef element, *el_drzewa;
Rozważmy następujący
algorytm przeszukiwania
drzewa binarnego:
int preorder (el_drzewa korzen, int x)
{
//{ : korzen !=NULL}
int pom=0;
if ((*korzen).wartosc==x) return 1;
if ((*korzen).konar_lewy!=NULL)
//2
pom=preorder((*korzen).konar_lewy,x);
if (pom) return 1;
//3
else
if ((*korzen).konar_prawy!=NULL)
//4
pom=preorder((*korzen).konar_prawy,x);
if (pom) return 1;
//5
else return 0;
//{ : funkcja zwraca 1, gdy "x jest elementem drzewa"},
// zwraca 0, gdy "x nie jest elementem drzewa".}
}
Dowód przeprowadzimy indukcyjnie względem parametru określającego wysokość
drzewa.
Oznaczmy wysokość drzewa przez wd.
Dla dowodu poprawności semantycznej algorytmu preorder sformułujmy własność:
Własność.
Dla dowolnego drzewa binarnego o wysokości wd będącej liczbą
naturalną wd>0, algorytm preorder dla danych spełniających  w
skończonej liczbie kroków dochodzi do wartościowania końcowego
i to wartościowanie spełnia .
Dowód:
Przeprowadzimy go metodą indukcji matematycznej.
Krok 1 – sprawdzenie poprawności algorytmu dla początkowej wartości. Należy
pokazać, że preorder(korzen) poprawnie określa wynik końcowy dla dowolnego
drzewa binarnego o określonym adresie korzenia i wysokości wd=1.
Istotnie, jeśli (*korzen).wartosc==x, to nastąpi koniec wartościowania funkcji i
wartością funkcji będzie 1, co będzie oznaczać zajście .
Jeśli (*korzen).wartosc!=x, to wobec  i założenia, że wd=1 mamy
(*korzen).konar_lewy==NULL oraz (*korzen).konar_prawy==NULL. Zatem wobec
początkowego wartościowania zmiennej pom=0, nie wykona się żadna z pięciu
instrukcji warunkowych i funkcja zwróci 0, co będzie oznaczało zajście .
Ponieważ jedynym miejscem – elementem w drzewie, gdzie może znajdować się pole
wartościujące równe x jest pole korzenia, zatem dla wd=1 program jest semantycznie
poprawny.
Krok2 – założenie i teza indukcyjna z dowodem.
Zał. ind.
Załóżmy, że algorytm preorder jest poprawnie określony dla drzew binarnych o
wysokości wd<=n.
Teza. ind.
Udowodnimy, że jest wtedy poprawnie określony dla drzew binarnych o
wysokości wd=n+1.
Istotnie.
Rozważmy drzewo binarne o wysokości wd=n+1.
Jeśli (*korzen).wartosc==x, to nastąpi koniec wartościowania funkcji i wartością
będzie 1, co oznacza zajście .
Jeśli (*korzen).wartosc!=x, to nastąpi ewentualne wykonanie kolejnych instrukcji
programu (2,3,4,5).
Wobec założenia indukcyjnego, ponieważ lewe i prawe poddrzewo drzewa o
wysokości n+1, będą drzewami o wysokościach mniejszych lub równych n, więc
instrukcje 2,3,4 zostaną wykonane poprawnie i spowodują wartościowanie funkcji
spełniające .
Instrukcja 5 dokona wartościowania funkcji jako 0, jeśli x nie będzie elementem
lewego i prawego poddrzewa, czyli nie będzie elementem drzewa.
Zatem wartością funkcji będzie 1, gdy x jest elementem drzewa, 0 – gdy nie jest
elementem drzewa, co oznacza zajście .
Zatem na mocy zasady indukcji matematycznej ma miejsce teza własności
dla drzew binarnych o dowolnej wysokości wd, co jest równoważne
całkowitej poprawności semantycznej algorytmu preorder wobec definicji 1.
Przykład. Algorytm obliczający wartość wyrażenia 2n+1
int Fibonaci(int n)
{
//: n>=0
if((n==0)||(n==1)) return n+2;
return 3*Fibonaci(n-1)-2*Fibonaci(n-2);
}
//: wartością funkcji jest 2n+1.
Przykład algorytmu rekurencyjnego (poprzedni też taki był)
Dowód przeprowadzimy indukcyjnie względem parametru n określającego wykładnik
potęgi 2.
Dla dowodu poprawności semantycznej algorytmu Fibonaci sformułujmy własność:
Własność.
Dla dowolnego wykładnika potęgi o podstawie 2 będącego liczbą
naturalną n>=0, algorytm Fibonaci dla danych spełniających  w
skończonej liczbie kroków dochodzi do wartościowania końcowego
i to wartościowanie spełnia .
Dowód:
Przeprowadzimy go metodą indukcji matematycznej.
Krok 1 – sprawdzenie poprawności algorytmu dla początkowej wartości. Należy
pokazać, że Fibonaci(n) poprawnie określa wynik końcowy dla potęgi o podstawie 2 i
wykładniku n=0 lub n=1.
Istotnie, jeśli n=0, to zachodzi warunek if((n==0)||(n==1)) i nastąpi koniec
wartościowania funkcji i wartością funkcji będzie n+2, czyli 2, co będzie oznaczać
zajście .
Jeśli natomiast n=1, to również zachodzi warunek if((n==0)||(n==1))
i nastąpi koniec wartościowania funkcji i wartością funkcji będzie również n+2, czyli
3, co także będzie oznaczać zajście .
Zatem dla n=0 lub n=1 program jest semantycznie poprawny.
Krok2 – założenie i teza indukcyjna z dowodem.
Zał. ind.
Załóżmy, że algorytm Fibonaci jest poprawnie określony dla potęgi o podstawie
2 i wykładniku k<=n, n=1,2,3….
Teza. ind.
Udowodnimy, że jest wtedy poprawnie określony dla potęgi o wykładniku n+1.
Istotnie.
Rozważmy potęgę o podstawie 2 i wykładniku n+1.
Ponieważ n+1>1, to Fibonaci(n+1) zwróci wartościowanie funkcji spełniające
3*Fibonaci((n+1)-1)-2*Fibonaci((n+1)-2).
Policzmy:
3*Fibonaci((n+1)-1)-2*Fibonaci((n+1)-2)=3*Fibonaci(n)-2*Fibonaci(n-1).
Wobec założenia indukcyjnego, ponieważ Fibonaci(n) i Fibonaci(n-1) będą funkcjami
obliczającymi wartość potęgi dla wykładników mniejszych lub równych n, więc zostaną
wykonane poprawnie i spowodują wartościowanie funkcji spełniające .
Zatem Fibonaci(n) zwróci 2n+1, a Fibonaci(n-1) zwróci 2n-1+1.
Możemy więc napisać:
3*Fibonaci((n+1)-1)-2*Fibonaci((n+1)-2)=3*Fibonaci(n)-2*Fibonaci(n-1) =
=3*(2n+1)-2*(2n-1+1) =3*2n+3-2n-2=2*2n+1=2n+1+1,
co oznacza zajście .
Zatem na mocy zasady indukcji matematycznej ma miejsce teza własności
dla algorytmu Fibonaci dla dowolnego wykładnika potęgi n, co jest
równoważne całkowitej poprawności semantycznej algorytmu Fibonaci
wobec definicji 1.