• Nie Znaleziono Wyników

Algorytmy i struktury danych - ćwiczenia

N/A
N/A
Protected

Academic year: 2021

Share "Algorytmy i struktury danych - ćwiczenia"

Copied!
78
0
0

Pełen tekst

(1)

Wydział Matematyki i Nauk Informacyjnych Politechnika Warszawska

Algorytmy

i struktury danych - ćwiczenia

Anna Maria Radzikowska

(2)

Spis treści

1 Stosy i kolejki 7

1.1 Stosy . . . . 7 1.2 Kolejki . . . . 9 1.3 Zadania . . . . 11

2 Częściowa poprawność algorytmów 13

2.1 Zadania . . . . 16

3 Poprawność algorytmów I 19

3.1 Zadania . . . . 23

4 Poprawność algorytmów II 25

4.1 Zadania . . . . 28

5 Złożoność algorytmów iteracyjnych 31

5.1 Zadania . . . . 33

6 Złożoność algorytmów rekurencyjnych 37

6.1 Zadania . . . . 39

7 Sortowanie 41

7.1 Elementarne algorytmy sortowania . . . . 41 7.2 Zadania . . . . 46

8 Eliminacja rekursji 47

8.1 Zadania . . . . 52

9 Drzewa BST i AVL-drzewa 53

9.1 Drzewa poszukiwań binarnych . . . . 53 9.2 Drzewa AVL . . . . 55

3

(3)

9.3 Zadania . . . . 58

10 B–drzewa i drzewa PATRICIA 59

10.1 B–drzewa . . . . 59 10.2 Drzewa Patricia . . . . 61 10.3 Zadania: . . . . 63

11 Projektowanie algorytmów 65

11.1 Zadania . . . . 67

12 Projektowanie struktur danych 69

12.1 Zadania . . . . 71

13 Podsumowanie 73

13.1 Przykładowe zestawy zadań testowych . . . . 74

(4)

Wprowadzenie

Niniejsze opracowanie stanowi materiał omawiany na ćwiczeniach z Algoryt- mów i Struktur Danych prowadzonych przez autorkę dla studentów drugiego roku kierunku Matematyki na Wydziale Matematyki i Nauk Informacyjnych Politechniki Warszawskiej. Jest to kurs semestralny obejmujący 30 godzin ćwiczeń, czyli 15 zajęć po 2 godziny tygodniowo (2 zajęcia poświęcone są te- stom sprawdzającym). W kolejnych częściach opracowania, odpowiadających kolejnym zajęciom ćwiczeniowym, przypomniane są istotne fakty z wykła- dów wraz z przykładami ilustrującymi omawiane zagadnienia. Przedstawione są także zestawy zadań, które są rozwiązywane na ćwiczeniach lub stano- wić mogą przedmiot samodzielnej pracy studenta. Strona implementacyjna prezentowanego materiału oparta jest na nieskomplikowanym pseudokodzie, wprowadzonym doładnie na wykładzie.

Celem niniejszego opracowania jest ułatwienie studentom przygotowań do zajęć ćwiczeniowych oraz stworzenie możliwości głębszego zrozumienia i przyswojenia materiału z zakresu kursu z Algorytmów i Struktur Danych.

5

(5)
(6)

Ćwiczenia 1

Stosy i kolejki

1.1 Stosy

Stos jest skończonym ciągiem elementów tego samego typu zorganizowanym według zasady LIFO (ang. Last In First Out ). Jest to struktura dynamicz- na z bezpośrednin dostępem do ostatnio wstawionego elementu, zwanego wierzchołkiem (czubkiem) stosu.

Standardowymi operacjami stosowymi są:

• InitStack (s) : inicjalizacja pustego stosu

• Push(s, x) : dodaj element x do stosu s; adres tego elementu jest no- wym wierzchołkiem stosu

• Pop(x) : zdejmij wierzchołek stosu s; jest to funkcja, która zwraca zdej- mowany element, a nowym wierzchołkiem stosu jest adres poprzednio wstawionego elementu

• Top(s) : odczytaj wierzchołek stosu; jest to funkca, która zwraca ten element i nie zmienia struktury stosu

• Empty(s) : sprawdzenie, czy stos s jest pusty; jest to funkcja logiczna, która zwraca true, jeśli stos s jest pusty, false w.p.p., i nie zmienia struktury stosu.

Najpopularniejsze reprezentacje stosów:

• tablicowa : stos reprezentujemy parą (table, topElem), gdzie table ta- blicą N –elementową tablicą jednowymiarową, zaś topElem jest liczbą całkowitą (wskaźnikiem wierzchołka stosu). Procedura push wstawia element na pozycję t+1–szą, natomiast procedura pop usuwa ostatni element tablicy (zmniejsza o 1 wartość zmiennej t).

7

(7)

• listowa : stos reprezentujemy listą s (wskaźnik początku listy). Proce- dura push wstawia elemet na początek listy, zaś pop usuwa pierwszy jej element.

Jako przykład omówimy implementację stosów przy pomocy tablic.

Przykład 1.1 (Tablicowa realizacja stosu) Załóżmy, że dany jest typ danych Data. Poniżej przedstawiono strukturę danych reprezentującą stos.

Jest to rekord o dwóch polach. Pole pierwsze, table, to tablica jednowymia- rowa o długości N (ustalona stała cakowita), w której będą przechowywane elementy stosu. Drugie pole, topElem, to wierzchołek stosu – zmienna całko- wita, której wartością jest indeks tablicy odpowiadający górnemu, ostatnio wstawionemu, elementowi stosu.

struct Stack =record

table : array[1..N ] of Data;

topElem : int end;

Poniżej przedstawiono implementacje operacji stosowych.

Algorytm 1.1: Push 1 Push(var s : stos; x : Data);

2 begin

3 if Empty(s) then Error(Stack overflow) fi;

4 s.topElem:=s.topElem+1;

5 s.table[s.topElem]:=x;

6 end Push;

Algorytm 1.2: Pop 1 Pop(var s : stos) : Data;

2 var x : Data;

3 begin

4 if Empty(s) then Error(Stack empty) fi;

5 x:=s.table[s.topElem];

6 s.topElem:=s.topElem−1;

7 return(x) 8 end Pop;

(8)

1.2. KOLEJKI 9 Algorytm 1.3: Empty

1 Empty(s : stos) : Bool;

2 begin

3 return(s.topElem = 0) 4 end Empty;

Algorytm 1.4: Top 1 Top(s : stos) : Data;

2 begin

3 return(s.table[s.topElem]) 4 end Top;

1.2 Kolejki

Kolejka jest skończonym ciągiem elementów tego samego typu zorganizo- wanym według zasady FIFO (ang. First In First Out ). Jest to struktura dynamiczna, z której usuwany może być tylko najwcześniej wstawiony ele- ment. W kolejce mamy zazwyczaj dostęp do dwóch jej elementów: pierwszego (najwcześniej wstawionego) i ostatniego (najpóźniej wstawionego).

Standardowe operacje kolejkowe:

• InitQueue(Q) : inicjalizacja pustej kolejki

• EnQueue(Q, x) : wstawienie elementu x do kolejki Q

• DeQueue(Q) : usuwanie pierwszego elementu z kolejki; jestto funkcja, która zwraca usuwany element

• Empty(Q) : sprawdzanie, czy kolejka Q jest pusta; jest to funkcja lo- giczna, która zwraca true, jeśli kolejka jest pusta i false w przeciw- nym przypadku

• Front (Q) : odczyt pierwszego elementu kolejki Q; jest to funkcja, która zwraca ten element jednocześnie nie zmieniając struktury kolejki.

Najpopularniejsze reprezentacje kolejki to

• reprezentacja tablicowa: kolejka realizowana jest jednowymiarową ta- blicą K[1..N ] wraz z dwoma indeksami, first i last, odpowiadającymi początkowemu i końcowemu elementowi kolejki.

(9)

• listowa: kolejka realizowana jest listą jednokierunkową wraz z dwoma wskaźnikami, first i last (są to wskaźniki na pierwszy i ostatni element listy).

Przykład 1.2 (Realizacja listowa kolejki) Struktura danych reprezentująca kolejkę Q:

struct List =↑element element = record;

info : Data;

next : List ; end;

queue=record

first, last : List end;

Poniżej przedstawiono implementację w pseudokodzie podstawowych opera- cji kolejkowych.

Algorytm 1.5: InitQueue 1 InitQueue(var q : queue);

2 begin

3 q.first :=NULL 4 end InitQueue;

Algorytm 1.6: EnQueue 1 EnQueue(var q : queue, x : Data);

2 var rf : List ; 3 begin

4 new (rf );

5 rf ↑.info:= x;

6 rf ↑.next :=NULL;

7 q.last ↑ .next :=rf ; 8 q.last :=rf

9 end EnQueue;

(10)

1.3. ZADANIA 11 Algorytm 1.7: DeQueue

1 DeQueue(var s : queue) : Data;

2 var rf : List ; elem ; Data 3 begin

4 if Empty(q) then Error(Queue is empty) fi;

5 rf :=q.first ;

6 q.first :=q.first ↑.next ; 7 elem:=rf ↑.info;

8 kill (rf );

9 return(elem) 10 end DeQueue;

Algorytm 1.8: EmptyQueue 1 EmptyQueue(q : queue) : Bool;

2 begin

3 return(q.first = NULL) 4 end EmptyQueue;

Algorytm 1.9: FrontQueue 1 FrontQueue(q : queue) : Data;

2 begin

3 return(q.first ↑.info) 4 end FrontQueue;

Szczególnym przykładem kolejek są kolejki priorytetowe. Z każdym ich elementem związany jest pewien atrybut, zwany priorytetem, którego war- tości pochodzą ze zbioru liniowo uporządkowanego. a usunięty może być jedynie element o najwyższym priorytecie (niekoniecznie najwcześniej wsta- wiony). Najczęściej kolejki priorytetowe implementowane są za pomocą kop- ca. O tej strukturze będzie mowa na kolejnych zajęciach niniejszego kursu.

1.3 Zadania

1. Zaimplementować strukturę stosu (tj. jego reprezentacje wraz z pod- stawowymi operacjami) przy pomocy list jednokierunkowych realizo-

(11)

wanch przy pomocy zmiennych dynamicznych. Elementami stosu są pary liczb rzeczywistych (x, y) ∈ R.

2. Opracować i zaimplementować algorytm scalający dwa uporządkowane rosnąco ciągi (A1, . . . , An) i (B1, . . . , Bn) umieszczone na stosach od- powiednio stosA i stosB. Wynikiem działania algorytmu ma być stos stosC zawierający elementy uporządkowane

• rosnąco

• malejąco

3. Zaimplementować strukturę kolejki przy pomocy tablicy jednokierun- kowej. Elementami kolejki są pary napisów imię i nazwisko.

4. Opracować implementację kolejki priorytetowej realizowanej przy po- mocy listy uporządkowanej.

(12)

Ćwiczenia 2

Częściowa poprawność algorytmów

Niech dany będzie algorytm A. Z każdym algorytmem związana jest para warunków, warunek początkowy (w skrócie w.p.) α i warunek końcowy (w.k.) β. Specyfikacją algorytmu A jest układ

{α}A{β}.

Mówimy, że algorytm A jest częściowo poprawny względem warunku początko- wego α i warunku końcowego β, jeśli dla każdych danych początkowych speł- niających warunek α, jeśli obliczenie algorytmu A dochodzi do jego punktu końcowego, to dane wynikowe spełniają warunek końcowy β.

Metoda niezmienników jest jedną z najpopularniejszych technik dowo- dzenia pprawnośći częsciowej algorytmów. Metoda ta polega na:

• wyznaczeniu w algorytmie pewnych miejsc i związanie z nimi warunków wyrażających zależności między wartościami zmiennych

• udowodnienie spełnialności kolejnych warunków dla każdych danych wejściowych speniających w.p. α

Istotnym pojęciem jest tu pojęcie niezmiennika instrucji – jest to warunek logiczny, który jest spełniony przed wykonaniem tej instrukcji jak i po jej wykonaniu. Najważniejsze są niezmienniki pętli. Rozważmy pętlę

while ϕ do I od;

13

(13)

Mówimy, że γ jest niezmiennikiem pętli wtedy i tylko wtedy, gdy jest on spełniony przed każdym sprawdzeniem warunku sterującego ϕ. Tym sa- mym warunek niezmienniczy γ zachodzi przed pierwszych obrotem pętli, jest spełniony w trakcie jej działania (po k–tym obrocie pętli, k ≥ 1), oraz po zakończeniu działania pętli (czyli po otatnim obrocie pętli).

Metodę niezmienników zilustrujmy przykładem. Najpierw wprowadźmy następujące oznaczenia. Niech x będzie liczbą naturalną.

• bxc to największa liczba naturalna nie większa niż x, zaś dxe to naj- mniejsza liczba naturalna nie mniejsza od x

• lg n to log2n.

Zauważmy, że

x−1 < bxc 6 x (2.1)

x 6 dxe < x+1. (2.2)

Przykład 2.1 Wykazać częściową poprawność następującego algorytmu wzglę- dem w.p. α i w.k. β:

begin

{α : n > 0}

p := 0; i := 1;

while 2i6 n do p := p + 1;

i := 2i;

od;

{β : p = blog nc}

end;

Po pierwsze, określmy postać równoważną dla warunku β. Korzystając z zależności (2.1) mamy:

β ⇔ lg n−1 < p 6 lg n ⇔ (lg n<p+1) ∧ (2p6 n) ⇔ (n < 2p+1) ∧ (n > 2p) Ostatecznie więc

β ⇔ 2p6 n<2p+1 (2.3)

Weźmy teraz przykładowe dane wejściowe, n = 55, by prześledzić dzia- łanie algorytmu.

(14)

15

p 0 1 2 3 4 5

i 1 2 4 8 16 32

Łatwo zauważyć, że wartości zmiennej p to kolejne liczby naturalne, zaś wa- ryości zmiennej i są kolejnymi potęgami 2. Zatem mamy podejrzenie, że własnością niezmienniczą naszej pętli jest równość γ1 : i = 2p. Ponadto, za- uważmy, że wartości zmiennej i nie przekraczają wartości danej wejściowej n.

Tym samym przypuszczamy, że γ2: i 6 n też jest własnością niezmienniczą.

Wobec tego warunek

γ(i, p) : (i = 2p) ∧ (i 6 n) jest przypuszczalnie niezmiennikiem pętli.

Zauważmy, że algorytm dochodzi do punktu końcowego, jeśli nie jest speł- niony warunek sterujący pętlą, tj. 2i > n. Jeśli γ jest niezmiennikiem pętli, to po ostatnim jej obrocie γ też zachodzi, czyli w szczególności i = 2p. Stąd 2p+1> n, więc mamy drugą nierówność w (2.3). Ponadto, z γ, 2p 6 n, co da- je pierwszą nierówność w (2.3). Tym samym, udowadniając niezmienniczość warunku γ wykażemy częściową poprawność podanego algorytmu.

Udowodnimy teraz, że γ jest niezmiennikiem pętli w podanym algoryt- mie.

Krok 1: Wyznaczmy w algorytmie miejsca szczególne:

begin

{α : n > 0}

E0: p := 0; i := 1;

E1: while 2i6 n do {γ(i, p)}

p := p + 1;

i := 2i;

od;

E2: {β : p = blg nc}

end;

Krok 2: Załóżmy, że dana wejściowe n spełnia warunek α, czyli n>0.

Rozważmy przejście E0 → E1. Po wykonaniu obu instrukcji przypisań ma- my: p = 0, i = 1. Sprawdzamy, czy te wartościowania zmiennych p oraz i spełniają γ(i, p).

γ1(i, p) : 2p= 20 = 1 = i γ2(i, p) : i = 1

(α)

6 n.

Zatem γ(i, p) zachodzi.

(15)

Krok 3: Rozważmy obrót pętli, czyli przejście E1 → E1. W tym celu za- kładamy, że:

(z1) γ(i, p) (z2) 2i6 n.

Założenie (z1) mówi, iż po k > 0 obrotach pętli zachodzi warunek γ (dla zmiennych i oraz p). Założeni (z2) mówi natomiast, że warunek sterujący pętlą jest spełniony, zatem “wchodzimy” do pętli i wykonywana jest k+1–szy raz instrukcja iterowana. Mamy udowodnić, że po k + 1–szym obrocie pętli warunek γ będzie spełniony dla nowych wartości zmiennych i oraz p, które oznaczamy odpowiednio i0 oraz p0. Zatem mamy dowieść tezy

(T) γ(i0, p0).

Po wykonaniu obu przypisać (instrukcji iterowanej) nowe wartości zmiennych to i0 = 2i, p0 = p+1. Sprawdzamy warunek γ.

γ1 : 2p0 = 2p+1= 2p· 2(z1)= 2i = i0 γ2 : 2i

(z2)

6 n =⇒ i0 6 n.

Zatem γ zachodzi dla nowych wartości zmiennych i oraz p. Na mocy zasady indukcji matematycznej wnosimy, że γ jest niezmienikiem pętli.

Krok 4: Załóżmy, że po pewnym (tj. k > 0) obrocie “wychodzimy z pętli”.

Rozważamy więc przejście E1→ E2.

Skoro obliczenie algorytmu osiągnęło punkt E2, warunek sterujący pętlą nie był spełniony, czyli 2i > n. Ponadto, jak pokazano w poprzednim punkcie, γ jest niezmiennikiem pętli, więc jest spełniony także po ostatnim obrocie pętli. Stąd i = 2p oraz i 6 n. Z warunków 2i > n, i = 2p mamy 2p+1 > n, zaś z warunków i = 2p, i6 n wynika 2p 6 n. Zatem 2p 6 n < 2p+1, co jest równoważne β.

2.1 Zadania

Udowodnić częściową poprawność następujących algorytmów względem w.p.

α i w.k. β:

(16)

2.1. ZADANIA 17 1. begin

{α : n ≥ 0}

z := x; y := 1 m := n;

while m > 0 do

if odd(m) then y := y · z; fi;

m := bm2c;

z := z · z;

od

{β : y = xn} end;

2. begin

{α : x ≥ 0}

l := 0;

r := x+1;

while l+1<r do p := b(l+r)

2 c;

if p2>x then r := p else l := p fi;

od;

{β : l = b xc}

end;

3. begin;

{α : x ≥ 0}

p := 0;

q := 1;

r := 1;

while a ≤ x do p := p+1 r := r+2;

q := q+r;

od;

{β : p2 ≤ x<(p+1)2} end;

(17)

4. begin

{α : x ≥ 0}

p := 0;

a := b := c := 1;

while a ≤ x do p := p+1;

b := b+2;

c := c+b;

a := a+3 ∗ (c − p) − 2 od;

{β : p3≤ x<(p+1)3} end;

(18)

Ćwiczenia 3

Poprawność algorytmów I

Niech dana będzie specyfikacja algorytmu A:

{α} A {β}.

Przypomnijmy, że algorytm A ma własność stopu względem w.p. α wtedy i tylko wtedy, gdy dla każdych danych wejściowych algorytmu speniających warunek α obliczenie algorytmu jest skończone. Własność stopu pętli bada- my jedną z dwóch następujących metod:

• metodą liczników iteracji (uogólnioną metodą liczników)

• metodą malejących wartości.

Pierwsza z metod, metoda liczników iteracji (ML), polega na:

(ML1) dodaniu do algorytmu nowej zmiennej całkowitej zwanej licznikiem pętli, powiedzmy zmiennej l,

(ML2) wstawienia do ciała tej pętli instrukcji l :=l +1,

(ML3) wyznaczeni wyrażenia arytmetycznego τ , które w ciele pętli nie zmie- nia wartości, oraz

(ML4) wykazaniu, że warunek l6 τ jest własnością niezmieniczą rozważa- nej pętli.

Uogólniona metoda liczników (UML) polega na

(UML1) wyznaczeniu w algorytmie wyrażenia w, które przy każdym obrocie rozważanej pętli zwiększa wartość (czyli ciąg wartościowań wyrażenia w jest ciągiem ściśle rosnącym),

19

(19)

(UML2) określeniu wyrażenia arytmetycznego τ o wartościach całkowitych, które w ciele pętli nie zmienia wartości

(UML3) wykazaniu, że warunek w6 τ jest własnościa niezmienniczą pętli.

Metodą dualną do uogólnionej metody liczników jest metoda malejących wartości (MW). Idea tej metody polega na:

(MW1) wyznaczeniu w algorytmie wyrażenia arytmetycznego w o warto- ściach całkowitych, którego wartość zmniejsza się przy każdym obrocie pętli

(MW2) określeniu wyrażenia arytmetycznego τ , którego wartość nie zmie- nia się przy każdym obrocie rozważanej pętli

(MW3) wykazaniu, że warunek τ > w jest własnościa niezmienniczą pętli.

W metodach UML i MW ciąg wartościowań wyrażenia w jest ciągiem ściśle monotonicznym i ograniczonym. Istotnym jest, by był to ciąg liczb całkowi- tych. Zauważmy bowiem, że ciąg liczb całkowitych, ograniczony z dołu i ściśle malejący (albo ograniczony z góry i ściśle rosnący) jest ciągiem skończonym.

Stąd rozważana pętla ma skończoną liczbę obrotów. Niestety ciągi liczb rze- czywistych, ograniczone od doły i ściśle malejące (odpowiednio: ograniczone z góry i ściśle rosnące) nie muszą być skończone. Przykładowo ciąg an=n1 spełnia powyższe warunki, ale jest nieskończony.

Przykład 3.1 Udowodnić poprawność następującego algorytmu względem warunku początkowego α i końcowego β:

begin

{α : x ≥ 0 ∧ y > 0}

q := y;

while q ≤ x do q := 2q;

od;

r := x; p := 0;

while q > y do p := 2p q := bq2c;

if q ≤ r then r := r − q; p := p+1 fi;

od

{β : x = py + r ∧ 0 ≤ r < y}

end;

(20)

21 Najpierw wyróżnijmy pewne charakterystyczne punkty algorytmu:

begin

{α : x ≥ 0 ∧ y > 0}

E0 : q := y;

E1 : while q ≤ x do q := 2q;

od;

E2 : r := x; p := 0;

E3 : while q > y do p := 2p q := bq2c;

if q ≤ r then r := r − q; p := p+1 fi;

od

E4 : {β : x = py + r ∧ 0 ≤ r < y}

end;

Załóżmy, że α jest spełniony.

(1) Częściowa poprawność:

Niech x = 57 i y = 7. Rozważmy pierwszą pętlę. Kolejne wartościowania zmiennej q: (7, 14, 28, 54, 108). Przypuszczamy, że niezmiennikiem tej pętli jest

γ1 : (∃k ∈ N0q = y2k) ∧ (x > y ⇒ q 6 2x).

Udowodnimy ten fakt.

E0 ⇒ E1 : q = y.

Teraz q = y = y20. Zatem k = 0. Jeśli x 6 y, to drugi warunek oczywiście spełniony. Załóżmy, że x > y. Zatem x > 0, bo y > 0 z α. Wtedy q = y < x, stąd q6 2x.

E1 ⇒ E1 : Załóżmy, że:

(z1) γ1(q) (z2) q6 x Wykażemy, że:

(t) γ1(q0).

q0= 2q. Teraz: q0= 2q(z1)= 2y2k= y2k+1. Z (z2), q6 x, więc 2q 6 2s, czyli q06 2x. Tym samym γ1 jest spełniony. Na mocy zasady indukcji matematycznej

(21)

γ1 jest niezmiennikiem pętli E1. E1 ⇒ E2 : γ1∧ q > x.

Rozważmy drugą pętlę.

q 108 54 28 14 7

r 57 3 3 3 3

p 0 0,1 2 4 8

Przypuszczamy, że niezmiennikiem drugiej pętli jest

γ2 : (x = pq+r) ∧ (0 6 r < q) ∧ (∃k ∈ N0q = y2k).

E2 ⇒ E3 : r = x, p = 0, q = y2k, q > x.

Teraz: pq + r = 0 · q+x = x, r = x

(α)

> 0, r = x < q. Z γ1, q = y2kdla pewnego k ∈ N0. Zatem γ2 zachodzi.

Załóżmy, że (z1) γ2(p, q, r) (z2) q > y.

Wykażemy, że (z1) γ2(p0, q0, r0)

p0 = 2p, q0 = bq2c. Ponieważ q > y i q = y2k, więc k > 0, czyli q jest liczbą parzystą. Zatem q0 = q2. Ponadto, q0 = y2k−1 i k> 0, czyli istnieje k0= k−1 ∈ N0 takie, że q = y2k0.

Rozważmy dwa przypadki.

(P1) q06 r. Wtedy r0 = r−q2, p00= 2p+1.

p00q + r = (2p + 1)q2 + (r−q2) = pq +q2 + r −q2 = pq + r(z1)= x.

Z założenia (P1), q26 r, więc q2q26 r −q2, stąd 06 r0. Podobnie, z γ2, r < q, więc r−q2< q−q2, czyli r0< q0. Wobec tego γ2(p00, q0, r0) spełniony.

(P2) q0> r. Wtedy r0= r.

p0q0+r0 = 2pq2+r = pq+r = x. Ponadto, z γ2) 2, 06 r = r0. Z założenia (P2), r0= r < q0. Zatem γ2(p0, q0, r0) także spełniony. Na mocy zasady indukcji matematycznej γ2 jest niezmiennikiem pętli E3.

E3 ⇒ E4 : γ2∧ q 6 y. Ponieważ q = y2k> y i q 6 y, q = y. Z γ2 otrzymujemy x = pq + r = py + r oraz 0 6 r < y. Zatem β jest spełniony.

(22)

3.1. ZADANIA 23 (2) Własność stopu:

Pierwsza pętla: jeśli x < y, to nie “wchodzimy” do pętli, więc pętla obróci się 0 razy – tym samym jest skończona. Jeśli zaś x> y, to z γ1, q6 2x. Zmienna x nie zmienia sę w pętli, ciąg wartościowań zmiennej q jest ciągiem liczb całkowitych ściśle rosnącym i z γ1 ograniczonym z góry. Zatem jest to ciąg skończony.

Pętla druga: podobnie, jeśli x < y, pętla nie obróci się ani razu, więc jest skońcona. Jeśli x> y, to ciąg wartościowań zmiennej q jest ciągiem liczb całkowitych, ściśle malejącym (instrukcja q := bq2c) i z γ2 ograniczonym z dołu (bo q = y2k, k ∈ N0, więc q> y). Tym samym jest to ciąg skończony, więc i pętla jest skończona.

(3) Określoność obliczeń:

Wszystkie działania są określone w ciele liczb całkowitych. Są one wyko- nywane na zmiennych o wartościach określonych (zainicjowanych), są więc określone.

Z (1), (2) i (3) wynika, że program jest poprawny, co kończy dowód.

3.1 Zadania

Udowodnić poprawność następujących algorytmów względem warunku po- czątkowego α i warunku końcowego β:

1. begin

{α : x ≥ 0}

q := 1;

while q3 ≤ x do q := 2q od;

p := 0;

while q>1 do q := bq2c;

if (p+q)2≤ x then p := p+q fi;

od;

{β : p2 ≤ x<(p+1)2} end;

(23)

2. begin

{α : x ≥ 0 ∧ y > 0}

q := y;

while q ≤ x do q := 2q od;

a := 0; r := x; q := bqyc;

while q > 1 do a := ba2c;

q := b2qcq;

if qy ≤ r then r := r−qy; a := a+q2 fi;

od

{β : x = ay+r ∧ 0 ≤ r < y}

end;

3. begin

{α : x ≥ 0}

q := 1;

while q ≤ x do q := 4q od;

p := 0;

r := x;

while q>1 do q := bq4c;

p := bp2c;

if 2p+q ≤ r then r := r−2p−q; p := p+q fi;

od;

od;

{β : p2 ≤ x<(p+1)2} end;

(24)

Ćwiczenia 4

Poprawność algorytmów II

Niniejsze zajęcia poświęcone są analizie poprawności algorytmów z pętlami zagnieżdżonymi. Rozważmy następujący przykład.

Przykład 4.1 Udowodnić poprawność następującego algorytmu względem w.p. α i w.k. β:

begin

{α : a > 1 ∧ b ≥ 1}

E0 : x := 0; y := 1;

E1 : while y<b do x := x+1;

E2 : i := 1; z := y;

E3 : while i<a do i := i+1;

y := y+z od

E4 : od

E5 : {β : x = dlogabe}

end;

(1) Częściowa poprawność

Rozważmy pętlę wewnętrzną E3. Dla a = 5 i y = z mamy:

i 1 2 3 4 5

y z 2z 3z 4z 5z Przypuszczamy, że warunek

γw : (y = iz) ∧ (i 6 a) 25

(25)

jest niezmiennikiem pętli wewnętrznej. Dowód indukcyjny przeprowadzamy podobnie jak na poprzednich zajęciach.

E3 ⇒ E4 : y = iz ∧ (i6 a) ∧ (i > a). Zatem i = a oraz y = az. Ponieważ y = z (w instukcji o etykiecie E2), więc fragment programy

E2 : i := 1; z := y;

E3 : while i<a do i := i+1;

y := y+z od

możemy zastąpić instrukcją y := ay.

Program równoważny:

begin

{α : a > 1 ∧ b ≥ 1}

E0 : x := 0; y := 1;

E1 : while y<b do x := x+1;

y := ay E4 : od

E5 : {β : x = dlogabe}

end;

Prześledźmy działanie tego algorytmu dla danych, np. a = 4 i b = 517. Wyniki obliczeń zapisujemy w tabelce.

x 0 1 2 3 4 5

y 1 4 16 64 256 1024

Zauważmy, że y = ax. Warunek końcowy β ⇔ ax−1<b 6 ax. Warunek wyjścia z pętli to y> b, czyli przypuszczalnie y > ax – to daje prawą nierówność z β.

Potrzebna jest jeszcze lewa nierówność w β. Łatwo zauważyć, że dla naszych danych istotnie zachodzi ax−1<b. Przypuszczamy więc, że niezmiennik pętli ma postać

γz : (y = ax) ∧ (ax−1<b).

Udowodnimy ten fakt.

E0 ⇒ E0 : Po wykonaniu instrukcji w pierwszej linii programu równoważ- nego mamy: x = 0, y = 1.

ax = a0= 1 = y.

(26)

27 ax−1 = a−1= 1a<b, ponieważ z α, a>1, więc 1a jest ułamnkiem właściwym.

Wobec tego γ zachodzi.

E1 ⇒ E0 : Zakładamy, że (z1) γz(x, y)

(z2) y<b Wykażemy, że (T) γz(x0, y0).

Z postaci instrukcji iterowanej wynika, że x0= x+1, y = ay.

ax0 = ax+1 = a · ax (z1)= ay = y0 ax0−1 = ax (z1)= y(z2)< b.

Zatem γz zachodzi. Z zasady indukcji matematycznej wynika, że γz jest nie- zmiennikiem pętli.

E1 ⇒ E5 : Mamy teraz: y = ax, ax−1<b, y > b. Stąd ax−1<b 6 ax, co jest równoważne β.

(2) Własność stopu

Pierwsza pętla: i jest licznikiem ograniczony z γw przez a

Druga pętla: ciąg wartościowań wyrażenia ax−1jest ściśle rosnący (z α, a>1, zaś x jest licznikiem) i z γz ograniczony przez b.

(3) Określoność obliczeń

Wszystkie działania są określone w ciele liczb całkowitych. Wszystkie opera- cje są wykonywane na zmiennych o wartościach określonych.

Z (1), (2) i (3) wynika, że algorytm jest poprawny względem w.p. α i w.k.

β, co należało udowodnić.

(27)

4.1 Zadania

Udowodnić poprawność następujących algorytmów względem w.p. α i w.k.

β:

1. begin

{α : x > 0 ∧ n ≥ 0}

i := 0;

z := 1;

while i < n do i := i+1;

y := z;

j := 1;

while j<x do j := j + 1;

y := y+z;

od;

z := y;

od;

{β : z = xn} end;

2. begin

{α : n ≥ 0}

x := y := 1;

while y<n do y = y+1;

z := x;

w := 1;

while w<y do w := w+1;

x := x+z od

od

{β : x = n!}

end;

(28)

4.1. ZADANIA 29 3. begin

{α : n ≥ 2}

p := 1;

x := 0;

while p ≤ n do {γ1: 2(x−1)2 ≤ n ∧ p = 2x2} w := x;

x := x+1;

y := x2; p := 1;

i := 0;

while i<y do {γ2: i ≤ y ∧ p = 2i} i := i+1;

p := p+p od

od

{β : w = b log nc}

end;

4. begin

{α : n>0}

p := 1;

k := 0;

while k<n do k := k+1;

p := pk;

y := a := 1;

while y<p do a := a+1;

y := a;

i := 1;

while i<k do y := ya;

i := i+1 od

od od

{β : a = dn n!e}

end;

(29)
(30)

Ćwiczenia 5

Złożoność algorytmów iteracyjnych

Rozważać będziemy teraz algorytm A o zbiorze danych wejściowych D i własności stopu dla wszystkich d ∈ D. Na tych i kolejnych zajęciach będzie- my badać efektywność algorytmów. Dla danego algorytmu A wyróżniamy efektywność:

• czasową — mierzona liczbą wykonanych operacji przez ten algorytm dla danych d ∈ D

• pamięciową — mierzona liczbą komórek pamięci potrzebnych do reali- zacji algorytmu dla danych d ∈ D.

Efektywność czasową algorytmów będziemy mierzyć liczbą wykonanych ope- racji elementarnych (patrz wykład).

Pełną funkcją kosztu algorytmu A nazywamy funkcję t : D → N, gdzie t(d) jest liczbą wszystkich operacji jednostkowych wykonanych przez algo- rytm A dla danych d.

Zwykle bardzo trudno (jeśli w ogóle) jest wyznaczyć pełną funkcję kosztu.

Zatem wśród danych wejściowych wyróżniamy te, które mają istotny wpływ na koszt algorytmu.

Zwykle D = D1× D2× . . . × Dk. Wymiarem danych jest funkcja | · | : D → W , gdzie W = Di1 × . . . × Dik.

Funkcją kosztu algorytmu nazywamy funkcję T : W → N określoną na- stępująco:

T (w) = sup{t(d) : d ∈ D & |d| = w}.

Analizując efektywność algorytmu dogodnie jest rozważać wybrane operacje jednostkowe zwane operacjami dominującymi, czyli takie operacje, których

31

(31)

liczba wykonań w algorytmie (dla wszystkich d ∈ D) jest dokładnie rzędu wszystkich wykonanych operacji.

Przykład 5.1 Zbadać złożoność czasową następującego algorytmu:

1 begin

2 {α : n ≥ 0}

3 k := n; i := 0;

4 while i < n do

5 i := i + 1; m := n; p := k; z := 1;

6 while m > 0 do

7 if odd (m) then z := zp fi;

8 m := bm2c;

9 p := p2;

10 od;

11 k := z;

12 od;

13 {β : k = nnn}

14 end;

Analizę zaczynamy od najbardziej wewnętrznej pęti (tu: pętla w linii 6).

Koszt obrotu pętli w linii 6 C1

Ilość obrotów pętli w linii 6 blg nc+1 (długość binarna liczby n) Koszt pętli w linii 6 C1(blg nc+1)

Koszt obrotu pętli w linii 4 C2+C1(blg nc+1) Ilość obrotów pętli w linii 4 n

Koszt pętli w linii 4 n[C2+C1(blg nc+1)]

Kosz operacji w linii 3 i 11 C3

Mama zatem

T (n) = C3+n[C2+C1(blg nc+1)]

= c3+C2n+C1nblg nc+C1n

= C1nblg nc+(C1+C2)n+C3. Pokażemy, że T (n) = Θ(n lg n).

Zauważmy, że

T (n) = C1nblg nc+(C1+C2)n+C3

6 C1n lg n+(C1+C2)n+C3

6 C1n lg n+(C1+C2)n lg n+C2n lg n

= (2C1+C + 2+C3)n lg n = C0n lg n

(32)

5.1. ZADANIA 33 Zatem T (n) = O(n lg n).

Ponadto,

T (n) = C1nblg nc+(C1+C2)n+C3

> C1n(lg n−1)+(C1+C2)n+C3

= C1n lg n−C1n+C1n+C2n+C3

> C1n lg n.

Stąd T (n) = Ω(n lg n). Wobec tego T (n) = Θ(n lg n), zgodnie z oczekiwa- niem.

5.1 Zadania

1. Dany jest algorytm postaci:

Alg(n : int);

begin

for i := 1 to n do j := i;

while i < n do sum :=Proc(i, j);

j := j+1;

od;

od end Alg;

gdzie Proc jest procedurą 2–argumentową. Zbadać złożość powyższego algorytmu zakładając, że Proc(i, j) ma złożoność:

(a) Θ(1) (b) Θ(j).

2. Opracować strukturę danych gwarantującą wykonanie następujących operacji na stosie s w czasie O(1):

(a) Push(s,x) ::= włóż x na stos s

(b) Pop(s) ::= zdejmij ze stosu s ostatni element (c) Empty(s) ::= sprawdź, czy stos s jest pusty

(d) FindMin(s) ::= znajdź minimalny element na stosie s.

(33)

3. Opracować strukturę danych gwarantującą wykonanie następujących operacji na zbiorze Z ⊆ {1, 2, . . . , n} w czasie O(1):

(a) Select (Z) ::= wybierz dowolny element zbioru Z i usuń go ze zbioru

(b) Search(Z,x) ::= sprawdź, czy x jest w zbiorze Z (c) Insert (Z,x) ::= wstaw x do zbioru Z.

4. Dany jest n–elementowy ciąg A o elementach typu U , gdzie U jest typem danych, w którym określono porządek liniowy ≤.

Co robi poniższy algorytm?

Proc(n : int);

var i, j, p, r, m : int; x : U ; begin

m := n;

while m > 1 do j := p := m;

x := Am; while p > 1 do

r := p−1;

p := bp2c;

for i := p to r do

if x ≤ Ai then x := Ai; j := i fi od;

od;

Aj := Am; Am := x;

m := m−1 od

end Proc;

Wyjaśnić działanie tego algorytmu. Możliwie najdokładniej zanalizo- wać jego złożoność czasową i skomentować uzyskany wynik. W jakim przypadku zastosowanie tego algorytmu jest efektywne, a kiedy nie- efektywne?

5. Dany jest ciąg (Ai)ni=1 elementów dziedziny U , w której określono po- rządek liniowy 6. Przyjmujemy: a<b ⇔ (a 6 b) i a 6= b. Co robi nastę- pujący algorytm:

(34)

5.1. ZADANIA 35 Zadanie(n :int);

begin

for i := 2 to n do k := b2ic ; if Ak<Ai then

Ai :=: Ak; while k>1 do

j := bk2c;

if Aj<Ak then Aj :=: Ak; k := j else k := 0; fi;

od fi od

end Zadanie;

Zbadać jego złożoność czasową i skomentować uzyskany wynik. Czy da- ny problem można rozwiązać efektywniej? Odpowiedź dokładnie uza- sadnić.

6. Dany jest ciąg (Ai)ni=1 o elementach typu U , w którym określono po- rządek liniowy6. Co robi następujący algorytm:

begin i := 1;

while i < n do i := i + 1 j := b2ic;

if Aj6 Ai then Aj :=: Ai; while j>1 do

p := b2jc;

if Ap6 Aj then Aj :=: Ap; j := p else j := 1 fi od

fi od end;

Zbadać jego złożoność i dokładnie skomentować uzyskany wynik. Czy problem algorytmiczny, który rozwiązuje powyższy algorytm, można rozwiązać efektywniej?

(35)
(36)

Ćwiczenia 6

Złożoność algorytmów rekurencyjnych

Niniejsze zajęcia poświęcone są analizie złożoności czasowej algorytmów re- kurencyjnych. Złożoność takich algorytmów badana jest na przykład przy zastosowaniu równań rekurencyjnych. Przypomnijmy na wstępie kilka pojęć faktów wprowadzonych na wykładzie

Równaniem rekurencyjnym nazywamy równanie postaci:

xn= f (xn−1, . . . , xn−k) , gdzie (xi)i=0 jest pewnym ciągiem, k ≥ 1.

Poniższe twierdzenie podaje postać rozwiązania pewnego równania rekuren- cyjnego dla n będących potęgami pewnej liczby dodatniej c.

Twierdzenie 6.1 Niech a, b, c ∈ R+ oraz niech n ∈ ck dla k ∈ N. Rozwiąza- niem równania rekurencyjnego postaci

T (n) = b dla n = 1 aT (nc) + bn dla n > 1 jest

T (n) =

Θ(n) dla a < c Θ(n log n) dla a = c Θ(nlogca) dla a > c

Poniższe twierdzenie umożliwia rozszerzenie rozwiązania uzyskanego w Twier- dzeniu 6.1 na wszystkie liczby naturalne, o ile rozwiązanie jest funkcją ogra- niczoną wielomianowo.

37

(37)

Twierdzenie 6.2 Jeśli

1. T : N → N i f : R+→ R+ są funkcjami niemalejącymi 2. T (ak) = Θ(f (ak)) dla k ∈ N i a > 1

3. (∃x0 > 0) (∃c > 0) (∀x ≥ x0) f (ax) ≤ cf (x) to T (n) = Θ(f (n)) dla każdego n ∈ N.

Przykład 6.1 (Problem wież Hanoi) Dane są trzy paliki P1, P2i P3. Na paliku P1umieszczono n krążków ułożonych od największego do najmniejsze- go. Jednorazowo wolno wykonać operację Move polegającą na przeniesieniu krążka z palika Pi na palik Pj (różne paliki). Mażna jedynie położyć krążek mniejszy na większym (nie odwrotnie). Celem jest przeniesienie wszystkich n krążków z palika P1 na palik P3 wykonując jedynie operację Move.

Napisać algorytm rozwiązujący to zadanie i zdabać jego złożoność czasową.

Algorytm 6.1: Hanoi

1 Hanoi (n : int, skąd, dokąd, przez : int);

2 begin

3 if n = 1 then Move(skądf, dokąd) fi;

4 Hanoi (n−1, skąd, przez, dokąd);

5 Move(skąd, dokąd);

6 Hanoi (n−1, przez, dokąd, skąd);

7 end Hanoi ;

Operacją dominującą jest przeniesienie krążka, czyli algorytm Move. Niech T (n) będzie ilością operacji dominującyc dla n krążków. Jeśli n = 1, to tylko jedna taka operacja jest wykonywana, więc T (1) = 1. Jeśli zaś n>1, wów- czas zachodzą dwa wywołania rekurencyjne, odpowiednio w linii 4 i 6, każde dla n−1 krążków, oraz jedna operacja Move. Zatem T (n) = 2T (n−1)+1.

Otrzymaliśmy więc równanie rekurencyjne postaci:

T (n) = 1 dla n = 1 2T (n−1)+1 dla n>1

Rozwiązujemy to równanie wykorzystując metodę przedstawioną ma wykła- dzie.

T (n) = 2T (n−1)+1 = 2[2T (n−2)+1]+1 = 4T (n−2)+2+1

= 4[2T (n−3)+1]+2+1 = 8T (n−3)+4+2+1 = . . .

= 2n−1T (1)+2n−2+ . . . +20 =Pn−1

i=02i = 2n−1−1.

Zatem T (n) = Θ(2n). Jest to więc bardzo czasochłonny algorytm.

(38)

6.1. ZADANIA 39

6.1 Zadania

1. Rozwiązać równania rekurencyjne:

(a) T (n) =

 1 dla n = 1

aT (n2) + b lg n dla n>1 (b) T (n) =

 1 dla n = 1

aT (n−1) + bn dla n>1 (c) T (n) =

 1 dla n = 1

4T (n2) + bn2 dla n>1

2. Zbadać złożoność algortmu sortowania szybkiego QuickSort w przy- padku, gdy ciąg wejściowy jest ciągiem stałym

3. Dana jest nielokalna tablica A : array[0 . . . n] of U, gdzie U jest ty- pem danych, w którym określono porządek liniowy 6. Zakładamy, że A[0] = − ∞. Co robi następujący algorytm:

procedure Alpha(n : integer);

procedure Beta(l , r : integer);

{α : l ≤ r}

var s , j : integer; x : U ; begin

if l = r then exit fi;

s := bl+r2 c;

Beta(l, s);

while r > s do j := s;

s := s+1;

x := A[ s ];

while x < A[ j ] do;

A[ j+1 ] := A[ j ];

j := j−1 od;

A[ j+1 ] := x;

od;

end Beta;

begin

Beta(1, n) end Alpha;

Cytaty

Powiązane dokumenty

[r]

Jeśli graf nie jest regularny, to należy dodać nowe krawędzie i ewentualnie wierz- chołki tak by przerobić go na regularny.. Algorithm

Algorytmy i Struktury

Wstarczy tak długo jak drzewo zawiera węzeł z lewym synem, wykonujemy na nim (i lewym synie) prawą

• v należy do poddrzewa p.right, jednak zauważmy, że liczba kroków tego typu nie może przekroczyć O(log n). 3

[r]

[r]

Ponieważ M jest najliczniejszym skojarzeniem, nie wśród nich