background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 1

Techniki budowania algorytmów

Co w ramach wykładu?

1. Rekurencja

2. Dziel i zwyciężaj

3. Programowanie dynamiczne

4. Metoda zachłanna

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 2

Dziel i zwyciężaj

Schemat ogólny

Trzy zasadnicze kroki:
1.transformacja danych x na dane x

1

, . . . , x

k

 o mniejszym rozmiarze; 

2.rozwiązanie problemu dla danych x

i

 (i = 1, . . . , k);

3.obliczenie rozwiązania dla danych x na podstawie wyników otrzymanych 
w punkcie 2.  

W naturalny sposób implementowane są jako procedury rekurencyjne: 

function DiZ(x)

if x jest małe lub proste then return AdHoc(x
przekształć x na x

1

 , . . . , x

k

 o mniejszym rozmiarze 

niż x 
for i ← 1 to k do y

i

 ← DiZ(x

i

na podstawie y

1

 . . . y

k

 oblicz rozwiązanie y dla x 

return y 

Bitwa pod Austerlitz, 2 grudnia 1805 r. – zwycięstwo wojsk Napoleona nad 
liczniejszymi 
o około 15 000 żołnierzy połączonymi armiami Austrii i Rosji.

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 3

Przykład 1 – Wyszukiwanie binarne

  Wyszukiwanie binarne

Problem: czy klucz x znajduje się w posortowanej tablicy S
zawierającej n kluczy?
Dane wejściowe: całkowita liczba dodatnia n, tablica kluczy S 
indeksowana od 1 do n oraz klucz x.
Dane wyjściowelocation – lokalizacja klucza x w tablicy S (0, jeśli x 
nie występuje w S)

Algorytm wyszukiwania binarnego
Jeżeli x jest równe elementowi środkowemu, kończymy. W przeciwnym 
przypadku:
1.Dzielimy tablicę na dwie podtablice o rozmiarze mniej więcej dwa razy 
mniejszym od oryginalnej. Jeżeli x jest mniejsze od elementu 
środkowego, wybieramy lewą podtablicę. Jeśli x jest większe od 
elementu środkowego, wybieramy prawą podtablicę.
2.Zwyciężamy (rozwiązujemy) podtablicę poprzez określenie, czy x do 
niej należy. O ile podtablica nie posiada dostatecznie małych rozmiarów, 
należy wykorzystać rekurencję.
3.Otrzymujemy rozwiązanie problemu tablicy na podstawie rozwiązania 
problemu podtablicy. 

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 4

Wyszukiwanie binarne c.d.

int location(int lowint high)
{

int mid;
if (low > high)

return 0;

else {

mid = (low+high)/2;

if (x == S[mid])

return mid;

else if (x < S[mid])

return location(lowmid-1);

else

return location(mid+1, high);

}

}

Operacja podstawowa: 

porównanie x z S[mid]

Rozmiar danych wejściowych: 

liczba n elementów 
tablicy S

Złożoność czasowa w 
najgorszym przypadku:

W(n) = W(n/2) + 1 ,   W(1) = 1

W(n) = lgn + 1

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 5

Przykład 2 – Sortowanie przez scalanie

  Sortowanie przez scalanie

Problem: posortować n kluczy w ciąg niemalejący
Dane wejściowe: całkowita liczba dodatnia n, tablica kluczy S 
indeksowana od 1 do n.
Dane wyjściowe: tablica S, zawierająca klucze w porządku 
niemalejącym
Algorytm sortowania przez scalanie
1.Dzielimy tablicę na dwie podtablice o rozmiarze mniej więcej dwa razy 
mniejszym od oryginalnej. 
2.Zwyciężamy (rozwiązujemy) każdą podtablicę, sortując ją. O ile 
podtablica nie posiada dostatecznie małych rozmiarów, należy 
wykorzystać rekurencję.
3.Łączymy rozwiązania podtablic poprzez scalenie ich w jedną 
posortowaną tablicę. 

MERGE-SORT(Apr)
if p < r

then q ← (p + r)/2

MERGE-SORT(Apq)
MERGE-SORT(Aq+1, r)
MERGE (Apqr)

Jak scalić dwie posortowane tablice w 
jedną posortowaną tablicę w czasie 
(n)?

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 6

Sortowanie przez scalanie c.d.

void merge(Item c[], Item a[], int nItem b[], int m)
{

for (int i = 0, j = 0, k = 0; k < n + mk++)

{

if (i == n)  { c[k] = b[j++]; continue; }
if (j == m)  { c[k] = a[i++]; continue; }
c[k] = (a[i] < b[j]) ? a[i++] : b[j++];

}

}

Funkcja scalająca dwie uporządkowane tablice a i b w jedną 
uporządkowaną tablicę c.

Koszt

 

Dziel         

– 

znalezienie środka przedziału zajmuje czas stały: 

(1)

Zwyciężaj 

– 

rekurencyjne rozwiązanie dwóch podproblemów o 

rozmiarze n/2, co daje czas 2T(n/2)
Połącz – 

merge działa w czasie (n)

)

log

(

1

,

)

(

)

2

/

(

2

1

,

)

1

(

)

(

n

n

n

n

n

T

n

n

T

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 7

Przykład 3 – Sortowanie szybkie

Algorytm szybkiego sortowania QS (Quick-sort )

W każdym kroku algorytmu, elementy sortowanego ciągu e

, ..., e

r 

 są 

rozdzielane, względem wybranego elementu v, na podzbiór elementów 
mniejszych i podzbiór elementów większych od v (element v nazywa się 
medianą).  

{e

l

 , ..., e

j-1

} < v <= {e

j+1

, ..., e

r

}. 

W ten sposób znana jest ostateczna pozycja elementu w końcowym 
uporządkowaniu : element v będzie umieszczony na pozycji j-tej. 

Następnie każdy z podzbiorów {e

l

 , ..., e

j-1

} oraz {e

j+1

, ..., e

r

} jest sortowany 

osobno według tego samego algorytmu. 

Przykład rozdzielania zbioru - podstawowy krok 
algorytmu QS

Dla ciągu, który chcemy uporządkować niemalejąco: 

10, 5, 7, 8, 14, 12, 3, 5, 1 

przyjmijmy, że v jest pierwszym elementem ciągu: = 10. Podzbiór 
elementów mniejszych od v to {5,7,8,3,5,1}, a podzbiór elementów 
większych lub równych v to {14,12}:  

{5,7,8,3,5,1} < 10 <= {14,12}. 

Niech = 5 dla lewego podzbioru. Element 5 jest większy od 3 i 1, a 
niewiększy od 5, 7, 8:

{3,1} < 5 <= {5,7,8} 

  

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 8

Sortowanie szybkie c.d.

Algorytm

  

QuickSort(lr);

if  l < r  then
j := Partition(lr);

(*algorytm rozdzielania*)

QuickSort(lj-1);

(*sortowanie elementów mniejszych 

od v*)

QuickSort(j+1, r

(*sortowanie elementów 

niemniejszych niż v*)

int Partition(lr)

e :c[l];            

//element dzielący

while (r)

              while ((l < r)  (c[r] >= e))  r – 1 

         while ((l < r)  (c[l] < e)) l   l + 1

         if (l < rthen c[l]  c[r]

return l;

Algorytm  rozdzielania

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 9

Sortowanie szybkie c.d.

Czas działania algorytmu quicksort

1. Przypadek najgorszy – procedura dzieląca tworzy jeden obszar 

złożony z n – 1 elementów, a drugi tylko z 1 elementu.

W(n) = W(n – 1) + (n)

(koszt podziału wynosi (n))

Ponadto W(1) = (1), zatem

2. Przypadek najlepszy – podział na obszary o rozmiarach (około) n/2

B(n) = 2B(n/2) + (n)

co daje zależność B(n) = (n lgn)

3. Przypadek średni 

skąd A(n) = (n lgn)

n

k

n

k

n

k

k

n

n

W

n

W

1

2

1

)

(

)

(

)

(

)

1

(

)

(

1

)

(

)

1

(

1

)

(

1

n

p

n

A

p

A

n

n

A

n

p

Średni czas sortowania 

podtablic, 

kiedy pivotpoint to p

Czas podziału

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 10

Przykład 4 – Jeszcze raz algorytm rozdzielania

Problem

znaleźć k-ty największy element tablicy S

Dane wejściowe

całkowita liczba dodatnia n, tablica liczb S 

indeksowana od 1 do n oraz takie, że 1  k  n.

Dane wyjściowe

element select tablicy taki, że dokładnie k-1 

elementów tablicy S jest większych od select.

1.

Prosty algorytm – k-krotne wyszukiwanie maksimum  

ogólnie złożoność (n

2

)

2.

Posortowanie tablicy i odczytanie k-tego elementu  j.w.

2. Idea algorytmu Hoare: 

i.

Rozdzielić elementy tablicy na mniejsze i większe względem 
pewnego elementu m, np. względem pierwszego lub ostatniego 
elementu tablicy. Niech x będzie liczbą elementów większych od m.

ii. Jeżeli  x > k-1, to  elementu k-tego szukamy wśród elementów 

większych od m, stosując to samo postępowanie.

iii. Jeżeli  x = k-1, to m jest szukanym, k-tym co do wielkości 

elementem tablicy.

iv. Jeżeli  x  < k-1, to wśród elementów mniejszych od m szukamy (tą 

samą metodą) elementu (k-x-1)-tego co do wielkości.

Wyznaczanie k-tego największego elementu w ciągu

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 11

Algorytm Hoare

Przykład

Szukamy 6-tego elementu w ciągu:

c = (6, 4, 1, 8, 7, 3, 2, 5, 9, 0). 

Jako element rozdzielający przyjmujemy pierwszy wyraz ciągu. Po pierwszym 
podziale:

4, 1, 3, 2, 5, 0, 

6

, 8, 7, 9

Zgodnie z punktem iv. algorytmu, szukamy teraz elementu drugiego co do 
wielkości 
(k – x – 1 = 6 – 3 – 1 = 2) w ciągu elementów mniejszych od 6, czyli w ciągu   
4, 1, 3, 2, 5, 0. 
Po rozdzieleniu tego ciągu względem 4 otrzymamy 

1, 2, 3, 0, 

4

,  

bo tylko jeden element jest większy od 4. Na mocy punktu iii. algorytmu 
Hoare, 4 jest szukanym, szóstym co do wielkości elementem danego ciągu c.

Jak przebiegną poszukiwania, gdy jako element rozdzielający przyjmiemy 
ostatni wyraz ciągu?

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 12

Algorytm Hoare

elem Hoare (eciąglpkint)
{

 j := Partition(l,p);

// j jest pozycją elementu rozdzielającego: 

na 

// prawo elementy większe, na lewo 

elementy 

// niewiększe

 if (p-j = k-1then result := e[j] 

// e[j] jest mniejsze od dokładnie k-1 

elementów

 else

 if (p-j>k-1) then 

// jest co najmniej k elementów większych 

od e[j]

result:=Hoare(ej+1, pk);  

// e[result] jest k-tym co do wielkości 

elementem 

// ciągu e[j+1],...,e[p

else result:=Hoare(elj-1, k-(p-j+1);

// e[result] jest k-(p-j+1)-tym 

co do wielkości 

// elementem 

ciągu e[l],e[l+1],...,e[j-1]

   

return e[result] 

// e[result] jest k-tym co do wielkości 

elementem 

// ciągu e

}

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 13

Koszt algorytmu Partition 

Liczba porównań wykonanych w procesie rozdzielania zależy od liczby 
elementów znajdujących się w rozważanej części ciągu. W każdej iteracji 
pętli "while" wykonujemy tylko jedno porównanie. Pętla "while" jest 
wykonywana dokładnie r - l razy. Zatem dla ciągu n elementowego  koszt 
algorytmu Partition wynosi: 

T(n) = (n).

Koszt algorytmu Hoare

najgorszym przypadku algorytm Hoare może działać tak źle, jak 
algorytm naiwny. Jeżeli dany ciąg jest uporządkowany rosnąco, to w każdym 
rekurencyjnym wywołaniu funkcji Partition, liczba elementów mniejszych od 
rozdzielającego jest zbiorem pustym. Zatem, o ile trzeba kontynuować 
poszukiwania k-tego co do wielkości elementu, kontynuuje się je w ciągu 
elementów o jeden krótszym. Biorąc pod uwagę koszt Partition
otrzymujemy 

(n-1) + (n-2) + ... +(n-k) = kn - k(k+1)/2 

porównań w przypadku pesymistycznym. 
Na szczęście koszt średni algorytmu Hoare jest zdumiewająco dobry. 
Udowodniono, że średni koszt algorytmu Hoare jest liniowy O(n).

Algorytm Hoare - złożoność czasowa

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 14

Przykład 5 – Mnożenie (bardzo) dużych liczb

Dane wejściowe: liczy naturalne a i b (długie)
Wynik: iloczyn a·b
Dla prostoty zakładamy, że b mają tą samą długość n bitów.
Pomysł: 

1.Dzielimy n-bitowe czynniki na części n/2 - bitowe. 

a = a

1

·2

s

+a

b = b

1

·2

s

+b

gdzie s = n/2 i 0  a

1

a

0

b

1

b

0  

 2

s

2.Zwyciężamy mnożąc przez siebie otrzymane części

c

2

 = a

1

·b

1

 , c

1

 = a

1

·b

0

 + a

0

·b

1

c

0

 = a

0

·b

0

3.Łączymy wyniki i uzyskujemy końcowy wynik

a·b c

2

·2

2

c

1

·2

s  

c

Podsumowanie:  

Mnożenie dwóch liczb n-bitowych zastępujemy 

czterema mnożeniami liczb n/2-bitowych, dwoma mnożeniami przez 
potęgę 2 (czas liniowy) 
i trzema dodawaniami (czas liniowy) . Czyli  T(n) = 4T(n/2) + (n)  
Stąd:   T(n) = (n

2

) ,   czyli… żaden zysk  

 

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 15

Mnożenie dużych liczb c.d.

Zyskamy, jeśli zamiast czterech mnożeń liczb n/2-bitowych wystarczą trzy.

c

1

 = (a

1

 + a

0

)(b

1

 + b

0

) – c

0

 – c

2

  

multiply(ab)

n    max(|a|, |b|)     //|x| oznacza długość liczby n

If n jest małe then pomnóż a i b klasycznym algorytmem 

return obliczony iloczyn

s    n/2

a

1

    a/2

s

 ; a

0

    a mod 2

s

b

1

    b/2

s

 ; b

0

    b mod 2

s

z  multiply(a

0

b

0

)

y  multiply(a

1

+a

0

b

1

+b

0

)

x  multiply(a

1

b

1

)

return 2

2s

x + 2

s

(y – x – z) + z 

Tym razem:

co daje ostatecznie     T(n) = O(n

log3

) ≈ O(n

1,585

)

1

dla

)

(

)

2

/

(

3

1

dla

)

(

n

n

n

T

n

k

n

T

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 16

Przykład 6 – Mnożenie macierzy

Problem: 

wyznaczyć iloczyn dwóch macierzy o wymiarach nn

Dane wejściowe: 

dodatnia liczba całkowita n, dwuwymiarowe 

tablice liczb A i B z wierszami i kolumnami indeksowanymi 
od 1 do n.

Dane wyjściowe: 

dwuwymiarowa tablica liczb C (wiersze i kolumny 

indeksowane od 
1 do n), zawierająca iloczyn macierzy A i B.

Algorytm działający według definicji mnożenia macierzy

MATRIX-MULTIPLY(ABn)
for i 1 to n

for j  1 to n

c

ij

  0

for k  1 to n

c

ij

  c

ij

 + a

ik

·b

kj

return C

Operacja podstawowa – mnożenie elementów macierzy
Złożoność czasowa: T(n) = (n

3

)

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 17

Mnożenie macierzy c.d.

Metoda Strassena

22

21

12

11

22

21

12

11

22

21

12

11

b

b

b

b

a

a

a

a

c

c

c

c

n/2

n/2

M

1

 = (A

11

 + A

22

)(B

11

 + B

22

)

M

2

 = (A

21

 + A

22

)B

11

 

M

3

 = A

11

(B

12

 - B

22

)

M

4

 = A

22

(B

21

 – B

11

)

M

5

 = (A

11

 + A

12

)B

22

 

M

6

 = (A

21

 – A

11

)(B

11

 + B

12

)

M

7

 = (A

12

 - A

22

)(B

21

 + B

22

)

C

11

 = M

1

 + M

4

 – M

5

 + M

7

C

12

 = M

3

 + M

5

 

C

21

 = M

2

 + M

4

 

C

22

 = M

1

 + M

3

 – M

2

 + M

6

Złożoność czasowa w każdym przypadku (dla n będącego potęgą 2):

T(n) = 7T(n/2) dla n>1

(nie uwzględniamy 18 dodawań i odejmowań

T(1) = 1

Rozwiązanie rekurencji daje:  T(n) = (n

log7

) ≈ (n

2,81

)

czyli składnika18(n/2)

2

)

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 18

Mnożenie macierzy – który algorytm wybrać?

Porównanie algorytmów mnożenia macierzy o wymiarach nn

Algorytm 

standardowy

Algorytm 

Strassena

Mnożenia

n

3

n

2,81

Dodawania i 

odejmowania

n

3

 – n

2

 

6n

2,81

 – 6n

2

Inne (lepsze?) wyniki:
1.Samuel Winograd (1988) – metoda wymagająca 15 operacji dodawania 
i odejmowania, co daje złożoność czasową operacji dodawania i 
odejmowania 6n

2,81

 – 6n

2

2.Coppersmith i Winograd (1987) – algorytm o złożoności operacji 
mnożenia O(n

2,38

), 

ale o dużej stałej

Mnożenie macierzy wymaga algorytmu o złożoności co najmniej 
kwadratowej. Nikt jeszcze takiego nie znalazł ani nie udowodnił, że 
takiego nie ma.

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 19

Dziel i zwyciężaj – Znajdowanie pary najmniej 

odległych punktów

Problem

Dane:  Zbior P = {(x

1

, y

1

), . . . , (x

n

, y

n

)} współrzędnych punktów 

na płaszczyźnie.
Zadanie: 

Znaleźć dwa najbliżej położone względem siebie 

punkty w P, tj. znaleźć
i,j takie, że d(x

i

y

i

x

j

y

j

) = min{d(x

k

y

k

x

l

y

l

) | 1   k < l  n}, 

gdzie

Siłowe rozwiązanie, polegające na wyliczeniu i porównaniu 
odległości między każdą parą punktów, wymaga czasu          

2

2

)

(

)

(

)

,

,

,

(

l

k

l

k

l

l

k

k

y

y

x

x

y

x

y

x

d

)

(

2

)

(

2

n

n

n

T

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 20

Strategia Dziel i Zwyciężaj

1. 

(a) Sortujemy punkty z P według współrzędnych x i 

zapamiętujemy je w tablicy X;
(b) Sortujemy punkty z P według współrzędnych y i zapamiętujemy je w 
tablicy Y ;
(c) Znajdujemy prostą l dzielącą P na dwa równoliczne (z dokładnością do 
1) podzbiory:

• P

L

 - podzbiór punktów leżących na lewo od l,

• P

R

 - podzbiór punktów leżących na prawo od l.

Punkty znajdujące się na prostej l (o ile są takie) kwalifikujemy do tych 
podzbiorów
w dowolny sposób.

2.  { rekurencyjnie }
(i

1

j

1

)   para punktów z P

L

 o najmniejszej odległości;

(i

2

j

2

)   para punktów z P

R

 o najmniejszej odległości.

3.

Niech (i′, j′) będzie tą parą punktów znalezioną w kroku 2, dla 

której odległość (oznaczmy ją przez d) jest mniejsza. 
Sprawdzamy czy istnieje para punktów (ts) odległych o mniej niż d 
takich, że t  P

L

 

s  P

R

. Jeśli istnieje, przekazujemy ją jako wynik procedury, w 

przeciwnym razie jako wynik przekazujemy parę (i′, j′).

Znajdowanie pary najmniej odległych punktów c.d.

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 21

Znajdowanie pary najmniej odległych punktów c.d.

Realizacja kroku 3

Oznaczmy przez P

C

 zbiór tych punktów z P, które leżą w odległości nie 

większej niż d od prostej l. Niech Y′ oznacza tablicę Y, z której usunięto 
wszystkie punkty spoza P

C

Wtedy:
Jeśli (t, s) jest parą punktów odległych o mniej niż d taką, że  P

L

 i s  

P

R

to t i s należą do P

C

. Ponadto w tablicy Y‘ pomiędzy a s leży nie 

więcej niż 6 punktów.

W kroku 3 pary (ts) należy szukać tylko w zaznaczonym pasie 

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 22

Jeśli p ma być jednym z punktów pary (ts), to drugi punkt musi 
znajdować się w kwadracie B. Ponadto wszystkie punkty z Y ′ leżące 
między t a s muszą leżeć w A lub w B.
W części A leżą tylko punkty z P

L

. Ponieważ każde dwa z nich odległe są 

od siebie o co najmniej d, więc może ich tam znajdować się co najwyżej 4. 
Z analogicznego powodu w części B może znajdować się nie więcej niż 4 
punkty z P

R

. Tak więc w całym prostokącie znajduje się nie więcej niż 8 

punktów.
Krok 3 sprowadza się więc do utworzenia tablicy Y ′, a następnie do 
obliczenia odległości każdego punktu z Y ′ do co najwyżej siedmiu 
punktów następujących po nim w tej tablicy.

Znajdowanie pary najmniej odległych punktów c.d.

background image

AiSD - Wykład 4 – Dziel i zwyciężaj

Slajd 23

Złożoność czasowa

Krok 1:

– Sortowanie - (n log n).

– Znalezienie prostej l i podział P na podzbiory - koszt 
stały.

Krok 2: 

– 2T(n/2)

Krok 3:

– Utworzenie Y ′ - (n).

– Szukanie pary (ts) - (n).

Znajdowanie pary najmniej odległych punktów - 

koszt

Zauważywszy, że sortowanie punktów w każdym wywołaniu 
rekurencyjnym jest zbyteczne otrzymuje się złożoność:

T(n) = (n log n)


Document Outline