• Nie Znaleziono Wyników

drzewiastej w typie tablicowym.1.Umieszczamy w tablicy jako element pierwszy korzeń drzewa

N/A
N/A
Protected

Academic year: 2021

Share "drzewiastej w typie tablicowym.1.Umieszczamy w tablicy jako element pierwszy korzeń drzewa"

Copied!
35
0
0

Pełen tekst

(1)

Algorytm tego sortowania wykorzystuje modelowanie struktury drzewiastej w typie tablicowym.

1. Umieszczamy w tablicy jako element pierwszy korzeń drzewa binarnego (klucz korzenia).

2. Dalej, dla element udrzewa binarnego, który jest elementem tablicy o indeksie i lewy element umieszczamy na miejscu o indeksie 2i, a prawy na miejscu o indeksie 2i+1.

Tablica reprezentuje wtedy drzewo binarne

Przypomnienie:

Drzewo binarne nazywamy kopcem, gdy dla dowolnego elementu pozostałe elementy poddrzewa, dla którego jest on korzeniem, są od niego

mniejsze.

(2)

Uporządkujmy zbiór n-elementowy, umieszczając jego elementy w tablicy, nadając jej strukturę kopca.

A[1] jest elementem największym.

Jeśli A[1] zamienimy z A[n] i w tablicy z pominiętym elementem A[n]

(n-1 –elementowej), przywrócimy strukturę kopca, to A[1] ponownie będzie największy, tym razem spośród A[1], A[2], …, A[n-1].

Zamieniamy ponownie A[1] z (tym razem) A[n-1], otrzymujemy sytuację wyjściową dla tablicy o n-2 elementach.

Powtarzając to postępowanie, otrzymamy tablicę 1-elementową (najmniejszy element kopca).

Otrzymana tablica stanowi zbiór uporządkowany – sortowanie przez kopcowanie

(3)

Dwie funkcje

zamien – heapsort

(4)
(5)

Zalety:

•Rząd pesymistycznej złożoności obliczeniowej jest równy nlog2n, jest to mniejsza złożoność niż dla prostych algorytmów sortowania.

•Algorytm jest optymalny pod tym względem.

Wady:

•Brak wrażliwości na wstępne uporządkowanie tablicy (średnia złożoność jest tego samego rzędu, co pesymistyczna).

(6)

Przykład:

Rozważmy zbiór liczb całkowitych S={a1, a2, …, an} oraz algorytm sortujący G bazujący na operacji porównania  jako jedynej informacji o elementach zbioru decydującej o porządku jego elementów.

Drzewem decyzyjnym związanym ze zbiorem S i algorytmem G nazywamy drzewo binarne, którego elementy zawierają:

-pary elementów z S podlegające kolejnym porównaniom podczas sortowania algorytmem G,

- Wskazania na lewy i prawy element w zależności od wartości porównania.

(7)

Drzewo decyzyjne dla zbioru trzyelementowego.

Strzałki w lewo – odpowiedź TAK.

Strzałki w prawo – odpowiedź NIE.

Prostokąty –liście drzewa decyzyjnego.

(8)

Każdy algorytm sortujący przez porównania zbiór n-elementowy można przedstawić w postaci drzewa decyzyjnego o n! liściach zawierających wszystkie permutacje elementów zbioru S.

Elementy wewnętrzne mają zawsze dwa następniki.

Długości ścieżek od korzenia do liścia są równe ilości porównań w trakcie porządkowania.

Oszacowanie z dołu pesymistycznej złożoności obliczeniowej

wysokość drzewa decyzyjnego h(G)=

(9)

2

h(G)

n!

Dowód: Indukcja względem

wysokości drzewa

h(G)  log2(n!) =

Jest to oszacowanie z dołu liczby porównań.

Można pokazać, że to oszacowanie pesymistycznej złożoności jest również oszacowaniem z dołu średniej złożoności obliczeniowej.

(10)

Pytanie:

Czy uwzględniając inne dodatkowe własności elementów zbioru można otrzymać algorytmy o mniejszym od nlog2n rzędzie zbieżności?

TAK

Sortowanie przez zliczanie

(11)

Założenia:

• Zbiór S jest n-elementowy.

• Zbiór S zawiera liczby naturalne z przedziału 1,k, gdzie nk

S={a1, a2, …, an}, dla i=1,2,…,n jest 1  ai  k Na czym polega?

• Dla elementu ai wyznaczamy liczbę j określającą ilość elementów z S mniejszych od ai .

• Element ai ustawiamy na miejscu j+1-szym.

• Jeśli takich elementów jest więcej (mających j elementów w S od nich mniejszych), to ustawiamy je na miejscach kolejnychj+2, j+3 i dalszych.

• Postępujemy tak z każdym elementem zbioru S.

(12)
(13)

Jeśli k jest rzędu O(n), to algorytm sortowania przez zliczanie ma pesymistyczną złożoność obliczeniową rzędu O(n).

(14)

Podstawowe algorytmy numeryczne:

poszukiwanie miejsc zerowych funkcji;

• iteracyjne obliczanie wartości funkcji;

• interpolacja funkcji metodą Lagrange’a;

• różniczkowanie funkcji;

• całkowanie funkcji metodą Simpsona;

• rozwiązywanie równań liniowych metodą Gaussa.

Literatura:

1. Knuth D.E.: Sztuka programowania –tom 1,2,3 Wydawnictwa Naukowo- Techniczne, 2001;

2. Praca zbiorowa pod redakcją Jerzego Klamki: Laboratorium metod

numerycznych, Skrypt nr 1305 Politechniki Śląskiej w Gliwicach, Gliwice 1987;

3. Wróblewski P.: Algorytmy, struktury danych i techniki programowania; 4. Stożek E.: Metody numeryczne w zadaniach;

5. Inne książki z metod numerycznych…..

Kompletne algorytmy, bez uzasadnienia matematycznego

(15)

Idea algorytmu:

Systematyczne przybliżanie się do miejsca zerowego funkcji za pomocą

stycznych do krzywej.

)

;

( '

) (

1 1

1

i i

z f

z f i

i

z

z

 )  ( z

i

stop, jeśli

f

Iteracyjnie powtarzamy

Symbol oznacza stałą, gwarantującą zatrzymanie

algorytmu,

z0 jest wartością początkową, f i f’ trzeba znać jawnie.

(16)

#include <iostream.h>

#include <math.h>

const double epsilon=0.0001;

double f(double x) //funkcja f(x)=3x2-2 {return 3*x*x-2;}

double fp(double x) //pochodna f’(x)=(3x2-2)’=6x {return 6*x;}

double zero(double x0,double(*f)(double),double(*fp)(double)) {

if(f(x0)<epsilon)return x0;

else

return zero(x0-f(x0)/fp(x0),f,fp);

}

int main() {

cout << "Zero funkcji 3x*x-2 wynosi "<<zero(1,f,fp)<<endl;

//wynik 0,816497 }

Zostały użyte

wskaźniki do funkcji, ale funkcji można użyć też bezpośrednio.

(17)

Idea algorytmu:

Załóżmy, że dysponujemy jawnym wzorem funkcji w postaci uwikłanej: F(x,y)=0.

Za pomocą metody Newtona można obliczyć jej wartość dla pewnego x w sposób iteracyjny:

. jesli

stop,

;

1 ) , ( '

) , ( 1

n n

y x F

y x F n

n

y y

y

y

y

Wartość początkowa y0 powinna być jak najbliższa wartości poszukiwanej y i spełniać warunek: F(x,y)F’y(x,y)>0.

Zalety:

Czasami iloraz znacznie się upraszcza!

. ) ( 2

, )

, (

, 0

) , (

,

2 1

' 1

1 1

2

n n

n y y

y x

y x y

y

y x F

x y

x F

y

Przykład:

(18)

#include <iostream.h>

#include <math.h>

const double epsilon=0.00000001;

double wart(double x,double yn) {

double yn1=2*yn-x*yn*yn;

//fabs(x)=|x|, wartość bezwzględna dla danych double if( fabs(yn-yn1)<epsilon)

return yn1;

else

return wart(x,yn1);

}

int main() {

cout << "Wartość funkcji y=1/x dla x=7 wynosi "<<wart(7,0.1);

//funkcja f(x)=3x2-2 }

(19)

Efektywne obliczanie wartości wielomianów – schemat Hornera.

Ma on zastosowanie np. w algorytmach kryptograficznych, bo trzeba tam

wykonywać obliczenia na bardzo dużych liczbach całkowitych. Można takie liczby traktować jako współczynniki wielomianów.

12 9876 0002 6000 0000 0054

).

4 5

( ) 6 ( ) 2 ( ) 6 7

8 9

(

2 20 19 18 17 16 12 11 1

21xxxxxxxx

x

. 54 ) 6000 ( ) 2 ( ) 9876 ( ) 12

( x5x4x3x2

Podstawa x=10

Podstawa x=10000

Wtedy operacje na dużych liczbach zastępujemy algorytmami działającymi na wielomianach.

(20)

. ...

)

( x a x a

1

x

1

a

1

x

1

a

0

W

n n

n n

  

W(b) – „metoda siłowa”

#include <iostream.h>

const n=5; // stopień wielomianu int oblicz(int b, int w[], int rozm) {

int res=0, pot=1;

for(int j=rozm-1;j>=0;j--) {

res+=pot*w[j]; //sumy cząstkowe pot*=b; //następna potęga b }

return res;

}

int main() {

int w[n]={1,4,-2,0,7}; // współczynniki wielomianu cout << oblicz(2,w,n) << endl;

}

(21)

 

 

... ....

)

( b a b a

1

b a

2

b a

1

b a

0

W

n

n

n

  

W(b) – schemat

Hornera

#include <iostream.h>

const n=5; // stopień wielomianu

int oblicz_wielomian2(int a,int w[],int rozm) // schemat Hornera

{

int res=w[0];

for(int j=1;j<rozm;res=res*a+w[j++]);

return res;

}

int main() {

int w[n]={1,4,-2,0,7}; // współczynniki wielomianu cout << oblicz_wielomian2(2,w,n) << endl;

}

(22)

Idea algorytmu:

Interpolacja funkcji metodą Lagrange’a służy do przybliżania funkcji (np. za pomocą wielomianu określonego stopnia), gdy:

• nie znamy funkcji tylko dysponujemy fragmentem wykresu, tzn. znamy jej wartości dla skończonego zbioru argumentów

•albo też wyliczanie na podstawie wzoru jest zbyt czasochłonne

Interpolacja funkcji f(x) za pomocą F(x).

(23)

Idea algorytmu:

Na rysunku na podstawie 7 par punktów udało się obliczyć wielomian F(x). Wielomian interpolacyjny konstruuje się za pomocą tzw. wyznacznika

Vandermonde’a, który pozwala na wyliczenie współczynników.

Jeśli jednak szukamy tylko wartości funkcji w określonym punkcie z, to prostsza jest metoda Lagrange’a:

. )

)...(

)(

( ) (

0 ( ) ( )

1 0

, 0

n

j z x x x

y

n n

j i i

i j j

x

j

z x

z x

z z

F

Powyższy wzór łatwo się tłumaczy na kod C++ za pomocą dwóch zagnieżdżonych pętli „for”.

(24)

#include <iostream.h>

#include <math.h>

const int n=3; // stopień wielomianu interpolującego // wartośći funkcji (y[i]=f(x[i]))

double x[n+1]={3.0, 5.0, 6.0, 7.0};

double y[n+1]={1.732, 2.236, 2.449, 2.646};

double interpol(double z, double x[n], double y[n]) // zwraca wartość funkcji w punkcie 'z'

{

double wnz=0,om=1,w;

for(int i=0;i<=n;i++) {

om=om*(z-x[i]);

w=1.0;

for(int j=0;j<=n;j++)

if(i!=j) w=w*(x[i]-x[j]);

wnz=wnz+y[i]/(w*(z-x[i]));

}

return wnz=wnz*om;

}

int main() {

double z=4.5;

cout << "Wartość funkcji sqrt(x) w punkcie " << z;

cout << " wynosi " << interpol(z,x,y) <<endl;

}

(25)

Idea algorytmu:

Do numerycznego różniczkowania służy tzw. wzór Stirlinga (nie wyprowadzamy!).

Pozwala on wyznaczyć pochodne f’ i f’’ w punkcie x0 dla pewnej funkcji f(x), której wartości znamy w postaci tabelarycznej:

)),...

( , (

)), ( , ( )), (

, (

)), 2 (

, 2

...(x0h f x0h x0h f x0h x0 f x0 x0h f x0h

Tablica różnic centralnych

(26)

Idea algorytmu:

Różnice  są obliczane w identyczny sposób w całej tabeli, np.:

).

2 (

) (

)

(x0 23 h f x0 h f x0 h

f     

Przyjmując upraszczające założenie, że zawsze będziemy obliczali pochodne dla punktu centralnego x=x0 wzór Stirlinga ma postać:

 

( ) ( ) ( ) ...

) ( ''

...

) ( '

6 90 4 1

12 2 1

1

2

) (

) ( 30

1 2

) (

) ( 6 1 2

) ( ) 1 (

2

2 5 1

2 5 1

2 3 1

2 3 1

2 1 2

1

x f x

f x

f x

f x f

h

h x f h x f h

x f h x f h

x f h x f h

Punktów kontrolnych może być więcej niż 5. W algorytmie poniżej jest 5 punktów, co prowadzi do tablicy różnic centralnych niskiego rzędu.

Uwaga!

•Im mniejsze h, tym większy błąd!

•Za duże h jest niezgodne z ideą metody.

•Metoda nie jest dobra do punktów na krańcach przedziałów zmienności argumentu funkcji.

(27)

#include <iostream.h>

#include <math.h>

const int n=5; // rząd obliczanych różnic centralnych wynosi n-1 double t[n][n+1]=

{ {0.8, 4.80}, // pary (x[i], y[i]) dla y=5x*x+2*x

{0.9, 5.85}, //inicjacja tablicy: wpisane są dwie pierwsze kolumny, {1, 7.00}, // a nie wiersze!

{1.1, 8.25}, {1.2, 9.60}

};

struct POCHODNE{double f1,f2;};

POCHODNE stirling(double t[n][n+1])

// funkcja zwraca wartości f'(z) i f''(z) gdzie z jest elementem

// centralnym: tutaj t[2][0], tablica 't' musi być uprzednio centralnie // zainicjowana, jej poprawność nie jest sprawdzana

{

POCHODNE res;

double h=(t[4][0]-t[0][0])/(double)(n-1); // krok argumentu 'x' for(int j=2;j<=n;j++)

for(int i=0;i<=n-j;i++)

t[i][j]=t[i+1][j-1]-t[i][j-1];

res.f1=((t[1][2]+t[2][2])/2.0-(t[0][4]+t[1][4])/12.0)/h;

res.f2=(t[1][3]-t[0][5]/12.0)/(h*h);

return res;

}

int main()

{ POCHODNE res=stirling(t);

cout << "f'=" << res.f1 << ", f''=" << res.f2 <<endl;

}

(28)

Idea algorytmu:

Przedstawiamy skomplikowaną funkcję w postaci przybliżonej , prostszej obliczeniowo.

Na danym etapie i trzy kolejne punkty funkcji podcałkowej są przybliżane parabolą.

Stosujemy to do każdego

obszaru

złożonego z 3 kolejnych punktów.

. )

(

2

0

2 1

0

3

) ( ) ( 4 )

( x

x

x f x f x

h

f

dx x

•Odstępy h muszą być jednakowe.

f

•Całka globalna jest sumą całek cząstkowych.

(29)

#include <iostream.h>

#include <math.h>

const int n=4; // ilość punktów= 2n+1

double f[2*n+1]={41, 29, 19, 11, 5, 1, -1, -1, 1};

double simpson(double f[2*n+1], double a, double b)

// funkcja zwraca całkę funkcji f(x) w przedziale [a,b], // której wartości są podane tabelarycznie w 2n+1 punktach {

double s=0,h=(b-a)/(2.0*n);

for(int i=0;i<2*n;i+=2) // skok cp dwa punkty!

s+=h*(f[i]+4*f[i+1]+f[i+2])/3.0;

return s;

}

int main() {

cout << "Wartość całki =" << simpson(f,-5,3) << endl; // 82.667 }

(30)

#include <iostream.h>

#include <math.h>

const int n=4; // ilość punktów= 2n+1 double fun(double x)

{

return x*x-3*x+1;

}

// funkcja x*x-3*x+1 w przedziale [-1,3]

double simpson_f(double(*f)(double), // wskaźnik do f(x) double a, double b, int N)

// funkcja zwraca całkę znanej w postaci wzoru funkcji f(x) // w przedziale [a,b], N - ilość podziałów

{

double s=0,h=(b-a)/(double)N;

for(int i=1;i<=N;i++)

s+=h*(fun(a+(i-1)*h)+4*fun(a-h/2.0+i*h)+fun(a+i*h))/3.0;

return s;

}

int main() {

cout << "Wartość całki =" << simpson_f(fun,-5,3,8) << endl; // 82.667 }

Całkowanie funkcji znanej w postaci analitycznej też jest możliwe

(31)

Idea algorytmu:

Aby zastosować algorytm Gaussa, musimy najpierw zapisać układ w postaci uporządkowanej. Przykład:

0 2

6 9 5

z y x

y z x

z x

0 1

1 2

6 1

1 1

9 0

5

z y

x

z y

x

z y x

 

 

 

 

 

 

 

0 6 9

1 1 2

1 1

1

1 0

5

z y x

Dalej, stosujemy metodę eliminacji Gaussa, sprowadzając macierz do macierzy trójkątnej.

0 1

1 2

6 1

1 1

9 0

5

z y

x

z y

x

z y x

6 , 3 6

, 0 1

0

2 , 4 2

, 1 1

0

9 0

5

z y

x

z y

x

z y x

6 , 0 6

, 0 0

0

2 , 4 2

, 1 1

0

9 0

5

z y

x

z y

x

z y x

Eliminacja y z wierszy 1 i 3 Eliminacja x z

wierszy 2 i 3

(32)

Idea algorytmu:

Stosujemy redukcję wsteczną, by wyznaczyć zmienne:

2 5 / ) 9

(

3 2 , 4 2

, 1

1 6

, 0 / 6 , 0

z x

z y

z

Niebezpieczeństwa:

•Eliminacja zmiennych może prowadzić do dzielenia przez zero.

•Zamieniamy wtedy wiersze, chyba, że poniżej wiersza i-tego nie ma tej zmiennej różnej od zera.

•Wtedy układ nie ma rozwiązania.

(33)

#include <iostream.h>

#include <math.h>

const int N=3;

double x[N];

double a[N][N+1]={ {5 , 0, 1, 9}, {1 , 1,-1, 6}, {2, -1, 1, 0}};

int gauss(double a[N][N+1], double x[N]) { int max;

double tmp;

for(int i=0; i<N; i++) // eliminacja { max=i;

for(int j=i+1; j<N; j++)

if(fabs(a[j][i])>fabs(a[max][i])) max=j;

for(int k=i; k<N+1; k++) // zamiana wierszy wartościami

{ tmp=a[i][k];

a[i][k]=a[max][k];

a[max][k]=tmp;

}

if(a[i][i]==0) return 0; // Układ sprzeczny!

for(j=i+1; j<N; j++)

for(k=N; k>=i; k--) // mnożenie wiersza j przez współczynnik "zerujący":

a[j][k]=a[j][k]-a[i][k]*a[j][i]/a[i][i];

}

cd.

(34)

// redukcja wsteczna

for(int j=N-1; j>=0; j--) { tmp=0;

for(int k=j+1; k<=N; k++) tmp=tmp+a[j][k]*x[k];

x[j]=(a[j][N]-tmp)/a[j][j];

}

return 1; // wszystko w porządku!

}

int main() {

if(!gauss(a,x)) cout << "Układ (1) jest sprzeczny!\n";

else

{ cout << "Rozwiązanie:\n";

for(int i=0;i<N;i++)

cout << "x["<<i<<"]="<<x[i] << endl;

} }

(35)

Warto poczytać o szybkiej transformacie Fouriera.

Warto poczytać o algorytmach przeszukiwania wzorca.

Warto poczytać o algorytmach kompresji zbiorów.

Cytaty

Powiązane dokumenty

Jeżeli zmiana argumentów funkcji ∆x, ∆y, ∆z jest nie- wielka, wówczas różniczka zupełna funkcji df jest bardzo dobrym przybliżeniem zmiany wartości funkcji ∆f wy-

Jeśli trening jest zbyt krótki lub/i liczba neuronów zbyt mała sieć będzie niedouczona (duże błędy), zbyt długi trening lub/i zbyt duża liczba neuronów skutkuje

Otóż z poprzedniego twierdzenia (o ciągłości jednostajnej) wnioskujemy, że: Wziąwszy np. W ten sposób, jeśli podzielimy przedział [a, b] na n części, to długość każdego z

Wyznaczyć wartości granic ciągów (wolno korzystać ze wzoru (♠)

Niech (q n ) będzie ciągiem wszystkich liczb wymiernych (wszystkie wyrazy ciągu są wymierne, a każda liczba wymierna występuje w tym ciągu

Jak zmieni się odpowiedź, gdy wykonamy rysunek biorąc za jednostkę na osiach śred- nicę atomu (10 −8 cm) lub średnicę jądra atomowego (10 −13 cm)?.. To samo stosuje się

Zmodyfikuj ten przykład i podaj funkcję, której zbiorem punktów nieciągłości jest Q..

Aby się w nich nie pogubić, sporządzimy teraz ich listę, do której można będzie zawsze w razie wątpliwości