• Nie Znaleziono Wyników

T ( n/b ). podzbiorze liczb naturalnych. a ≥1, b >1, f T ( n )= aT ( n / b )+ f ( n ),

N/A
N/A
Protected

Academic year: 2021

Share "T ( n/b ). podzbiorze liczb naturalnych. a ≥1, b >1, f T ( n )= aT ( n / b )+ f ( n ),"

Copied!
37
0
0

Pełen tekst

(1)

Metoda rekurencji uniwersalnej stosowana jest do rekursji postaci

T(n)=aT(n/b)+f(n),

(10)

gdzie

a≥1, b>1, f

jest pewną funkcją nieujemną określoną na podzbiorze liczb naturalnych.

Rekursja (10) opisuje czas działania algorytmu, który dzieli problem rozmiaru n na a podproblemów o rozmiarze n/b.

Każdy z a podproblemów jest rozwiązywany rekurencyjnie w czasie T(n/b).

Koszt dzielenia problemu oraz łączenia rezultatów częściowych jest opisany funkcją f.

(2)

Ważne twierdzenie!!

(3)

Problem:

•tablica n liczb całkowitych tab[n]=tab[0], tab[1], …, tab[n-1];

•czy w tablicy tab występuje liczba x (podana jako parametr)?

Rozumowanie:

wziąć pierwszy niezbadany element tablicy n-elementowej;

jeśli aktualnie analizowany element tablicy jest równy x, to:

wypisz „Sukces” i zakończ;

w przeciwnym wypadku:

zbadaj pozostałą część tablicy n-1 elementowej.

Gdy przebadaliśmy całą tablicę i element nie został znaleziony, można np. wyświetlić komunikat o

niepowodzeniu.

(4)

Przykładowa realizacja:

int const n=10;

int tab[n]={1,2,3,2,-7,44,5,1,0,-3};

void szukaj(int tab[n], int left, int right, int x) //left, right - lewa i prawa granica obszaru poszukiwań

//tab - tablica

//x - wartość do odnalezienia {

if (left>right)

cout << "Element " << x << " nie został odnaleziony\n";

else

if (tab[left]==x)

cout <<"Znalazłem szukany element "<< x <<endl;

else

szukaj(tab, left+1,right,x);

}

(5)

Program ilustruje podstawowe cechy typowego programu rekurencyjnego:

•element znaleziony

•przekroczenie zakresu tablicy Zakończenie programu

jest jasno określone

•z tablicy o rozmiarze n schodzimy do tablicy o rozmiarze n-1

Duży problem zostaje rozbity na problemy elementarne, które umiemy

rozwiązać i na analogiczny problem, tylko o mniejszym

stopniu skomplikowaniu

(6)

złe określenie warunku zakończenia programu niewłaściwa (nieefektywna)

dekompozycja problemu

(7)

Przykład: Obliczanie silni

unsigned long int silnia (int x) {

if (x==0) return 1;

else

return x*silnia(x-1);

} Jak się liczy 3!

Proces przekazywania wyniku cząstkowego z poziomu niższego na wyższy

Zagłębianie się programu z

poziomu n na n-1 w celu dotarcia do przypadku

elementarnego 0!

Obliczanie wyników cząstkowych

(8)

Przykład: Ciąg Fibonacciego.

(Elementy tego ciągu stanowią liczby naturalne takie, że kolejny wyraz (z

wyjątkiem dwóch pierwszych) jest sumą dwóch poprzednich, tj. 1, 1, 2, 3, 5, 8, 13,…)

unsigned long int Fibonaci(int n) {

if(n<2)

return n else

return Fibonaci(n-1)+Fibonaci(n-2);

} Obliczanie czwartego

elementu ciągu

Każde zacieniowane

wyrażenie stanowi problem elementarny

Znaczna część obliczeń jest wykonywana więcej niż jeden raz!!

(9)

•Programy rekurencyjne są zazwyczaj dość pamięciożerne.

•Program obliczający 3! wywoła sam siebie tylko 3 razy, ale Fibonacci już nie jest taki łatwy do analizy.

Przykład

unsigned long int funkcja(int x) {

if(x>100)

return (x-10);

else

return funkcja(funkcja(x+11));

}

Ile wywołań?

Co się dzieje na większym przedziale liczbowym niż na rysunku? –ćw.

(10)

Stack overflow, czyli funkcja Ackermanna

#include <iostream.h>

int A(int n,int p) {

if (n==0) return 1;

if ((p==0)&&(n>=1))

if (n==1)return 2;

else return n+2;

if ((p>=1)&&(n>=1))

return A(A(n-1,p),p-1);

}

int main() {

cout << "A(3,4)="<<A(3,4) <<endl;

}

Jaki jest powód komunikatu:

„Stack overflow!” (przepełnienie stosu) podczas próby jego

wykonania?

Nastąpiła znaczna ilość

wywołań funkcji Ackermanna.

(11)

Stack overflow, czyli funkcja Ackermanna Analiza wywołań Pobieżna analiza funkcji A prowadzi do spostrzeżenia:

. 2 ...

2 )

1 , 1 (

) 0 ), 1 , 1 (

( )

1 , ( ,

1 A n A A n A n n

n        

Analogicznie dla 2 otrzymamy:

. 2 ...

) 2 , 1 (

2 )

1 ), 2 , 1 (

( )

2 , ( ,

1 A n A A n A n

n

n       

Z samej definicji funkcji Ackermanna możemy wywnioskować, że

. 1 )

3 , 0 ( ,

2 )

2 ), 3 , 1 (

( )

3 , ( ,

1   

( 1,3)

n A n A A n

A n

A

Na bazie tych równań możliwe jest rekurencyjne udowodnienie, że

. 2

) 3 , ( ,

1

2 2 n

n A

n

(12)

Stack overflow, czyli funkcja Ackermanna Analiza wywołań Nieco gorsza jest sytuacja dla A(n,4), bo trudno jest podać wzór ogólny. Ale można zobaczyć przykłady liczbowe:

. 2

. 65536 2

. 4 2

. 2

) 4 , 4 (

) 4 , 3 (

) 4 , 2 (

) 4 , 1 (

65536 2

2

2

2 22



A

A

A

A

(13)

Błąd programisty! Sprowokowanie nieskończonej ilości wywołań rekurencyjnych!

Przykład:

int niesk(int n) {

if(n==1)

return 1;

else

if ((n%2)==0) //czy n jest parzyste?

return niesk(n-2)*n;

else

return niesk(n-1)*n;

}

Dla n>=2 wszystkie wywołania rekurencyjne kończą się parzystą liczbą n. Zatem dojdziemy do

n=2, potem n=0, n=-2,……

Nigdzie po drodze nie ma przypadku elementarnego!

(14)

Błąd programisty! Jak go uniknąć?

Sprawdzić matematycznie poprawność definicji rekurencyjnej, tzn.

•określić dziedziny wartości funkcji,

•udowodnić, że się ona zakończy,

•podać złożoność obliczeniową

To nie wystarczy! Nie wiadomo, jak rzeczywisty kompilator wykona tę funkcję.

int N(int n, int p) {

if(n==0)

return 1;

else

return N(n-1,N(n-p,p));

}

Można udowodnić matematycznie, że powyższa definicja jest poprawna w tym sensie, że dla dowolnych n>=0, p>=0 jej wynik jest określony i wynosi 1.

Zakłada się, że wartość argumentu wywołania funkcji jest obliczana tylko wtedy, gdy jest to konieczne. Jak wykona to typowy kompilator C++?

Wszystkie parametry funkcji rekurencyjnej są obliczane jako pierwsze, a potem wywołana jest funkcja – wywołanie przez wartość.

(15)

int N(int n, int p) {

if(n==0)

return 1;

else

return N(n-1,N(n-p,p));

}

Wszystkie parametry funkcji rekurencyjnej są obliczane jako pierwsze, a potem wywołana jest funkcja – wywołanie przez wartość.

Zapętlenie jest spowodowane próbą obliczenia parametru p, tymczasem to drugie wywołanie nie jest potrzebne do zakończenia funkcji!

Kompilator tego nie wie!

(16)

•Łatwe do zrozumienia

•Zajmują mało miejsca (liczba wierszy kodu) – ewentualnie łatwo znaleźć błędy

Jak w takim razie usunąć wady?

Inaczej zbudować rekurencję.

Rekurencja „naturalna”

–poprzednie przykłady

Rekurencja „z parametrem dodatkowym”

Na czym polega?

(17)

unsigned long int silnia (int x) {

if (x==0) return 1;

else

return x*silnia(x-1);

}

unsigned long int silnia2 (int x, int tmp=1) {

if (x==0)

return tmp;

else

return silnia2(x-1,x*tmp);

}

Parametry domyślne funkcji fun(int a, int k=1)

Funkcja może być wywołana na dwa sposoby:

•Poprzez określenie wartości drugiego

parametru, np. fun(2,5), wtedy k przyjmuje

wartość 5;

•Bez określania wartości drugiego parametru, np.

fun(12), wtedy k przyjmuje wartość domyślną równą tej podanej w nagłówku, czyli 1.

Parametr dodatkowy przekazuje elementy wyniku końcowego – program nie ma potrzeby

przekazywania wyniku obliczeń do góry, piętro po piętrze – ostatni aktywny poziom dostarczy wynik!

(18)
(19)
(20)

Twierdzenie o rekurencji uniwersalnej podaje metodę rozwiązania tego typu rekurencji.

(21)
(22)

Zapis tego algorytmu w pseudokodzie:

(23)

Wywołania rekurencyjne

Ćwiczenie:

Zapisać w C++

(24)

Twierdzenie:

(25)
(26)
(27)

Komentarz:

(28)

Twierdzenie:

(29)
(30)

Gdy n jest dowolną liczbą naturalną rozwiązanie rekurencji jest trudniejsze. Należy wykorzystać następujący wzór sumacyjny:

Twierdzenie:

(31)

Dowód:

(32)
(33)

Korzystając z podanego wcześniej wzoru sumacyjnego, możemy policzyć wartość sumy:

co daje ostatecznie:

(34)

Rodzaje sortowania (wg pamięci)

Wewnętrzne Zbiór do posortowania mieści się w pamięci

Zewnętrzne

Zbiór do posortowania mieści się w pamięci

zewnętrznej, np. na dyskach (wykorzystuje się

tylko stałą – małą – ilość pamięci wewnętrznej.

(35)

Rodzaje sortowania (wg operacji)

Adaptacyjne

Wykonuje się różne sekwencje operacji dla

różnych układów danych

Nieadaptacyjne

Sekwencja

wykonywanych operacji nie zależy od

kolejności danych.

(36)

Parametry wydajnościowe sortowania:

czas działania algorytmu

ilość dodatkowej pamięci zużywanej przez algorytm

Wykorzystuje tyle miejsca, ile potrzeba na

zapisanie sortowanych danych + mały stos

lub tablica

Używa reprezentacji w postaci listy połączonej

albo innego sposobu pośredniego dostępu

do danych, czyli wymaga dodatkowej

pamięci na n wskaźników lub

indeksów

Wymaga dodatkowej pamięci na pełną kopię sortowanych

danych

(37)

Definicja.

Sortowanie jest stabilne, jeśli zachowuje względną kolejność elementów o jednakowych kluczach.

Przykład.

Lista studentów uporządkowana alfabetycznie według nazwisk. Jeśli chcemy ją posortować wg ocen, to studenci mający taką samą ocenę nadal będą w liście ułożeni alfabetycznie.

Cytaty

Powiązane dokumenty

Znaleźć największą liczbę n ∈ N, dla której umie Pan/i pokazać, że dla każdej nieparzystej m &lt; n, jeśli |G| = m, to G jest

, n}f oranypositiveintegern.T hisiscalledtheinitialsegmenttopology.Argumentsimilartotheabovequestionτ 2 consistsof N, ∅andeverysetof thef orm{n,

Liczba naturalna zapisana w systemie dziesiętnym jest podzielna przez 3 wtedy i tylko wtedy, gdy suma jej cyfr jest podzielna przez 3.. Udowodnij i uogólnij tę powszechnie

Probability Calculus 2019/2020 Introductory Problem Set1. Using the notation with operations on sets, how would

[r]

1 Takie sformułowanie jest zgrabne, chociaż dla jego pełnej poprawności wymagałoby dodania nic nie wnoszącego do rozwiązania zastrzeżenia, że punkt styczności leży na stycznej,

Pokaż, że test R 2 &gt; c jest równoważny te- stowi ilorazu wiarygodności dla modelu liniowego

Rozwiązanie każdego zadania należy podpisać w lewym górnym rogu pierwszej jego strony: imieniem i nazwiskiem, swoim adresem, swoim adresem elektro- nicznym oraz klasą, nazwą i