# Dziel i zwyciężaj :::success [Link do zadań, które robiliśmy](https://files.wpmii.pl/2021-11-06-zadania.pdf?fbclid=IwAR0nC7RZyqYNTmFwd9xz3El5sUdLxkPrp8szBiFHWQO5dCqGXDgvEsv3MXo) ::: ## Ogólny schemat algorytmów dziel i zwyciężaj: ``` P - jakiś problem do rozwiązania DzielIZwyciężaj(P): jeśli P jest wystarczająco mały: zwróć rozwiązanie P wpp: podziel P na P1, P2, ..., Pk zastosuj DzielIZwyciężaj(P1), DzielIZwyciężaj(P2), ..., DzielIZwyciężaj(Pk) wykorzystaj rozwiązania P1, P2, ..., Pk do stworzenia rozwiązania P zwróć rozwiązanie P ``` **UWAGA:** Poniższe algorytmy w większości są na tyle proste, że wydzielają tylko jeden podproblem (a raczej ignorują ten drugi), który jest o połowę mniejszy od głównego i rozwiązanie problemu głównego wyznaczają na podstawie rozwiązania tylko tego jednego podproblemu. Tylko wyszukiwanie minimum i maksimum używa rozwiązań obu podbroblemów. ## Wyszukiwanie binarne - Szukamy zadanego elementu w posortowanej tablicy - Korzystamy z faktu, że dla dowolnego elementu w tablicy, po lewej od niego są wyłącznie elementy niewiększe, a po prawej - niemniejsze. - Z każdą iteracją zmniejszamy przedział poszukiwań o połowę ### Indeks szukanego elementu, jeśli jest w tablicy, -1 w przeciwnym przypadku #### C++ ```cpp= int binarySearch(int arr[], int l, int r, int x) { while (l <= r) { int m = (l + r) / 2; if (arr[m] == x) { return m; } if (arr[m] > x) { r = m - 1; } else { l = m + 1; } } return -1; } ``` #### Python ```python= def binarySearch(list, l, r, x): while l <= r: m = floor((l + r) / 2) if list[m] == x: return m if list[m] > x: r = m - 1 else: l = m + 1 return -1 ``` ### Indeks pierwszego elementu niemniejszego od szukanego **UWAGA:** Te funkcje dla szukanych wartości większych niż największa wartość w tablicy i tak zwracają ostatni indeks tablicy. Można się przed tym zabezpieczyć, sprawdzając, czy ```x > tablica[r]``` i zwracając coś innego, jeśli będzie to prawdą. #### C++ ```cpp= /* Komentarze opisują co się zmienia w porównaniu z wersją podstawową */ int binarySearch(int arr[], int l, int r, int x) { while (l < r) { // < zamiast <=, chcemy skończyć, gdy l == r int m = (l + r) / 2; /* nie sprawdzamy czy arr[s] == x, * bo wartości mogą się powtarzać i pierwsza szukana może być dalej z lewej, * więc nie chcemy kończyć po znalezieniu dowolnego elementu rówego x */ if (arr[m] < x) { l = m + 1; } else { r = m; // m zamiast m - 1, bo arr[m] >= x, czyli jest potencjalnym rozwiązaniem } } return l; // zwracamy l - indeks, na którym skończyliśmy wyszukiwanie } ``` #### Python ```python= # Komentarze opisują co się zmienia w porównaniu z wersją podstawową def binarySearch(list, l, r, x): while l < r: # < zamiast <=, chcemy skończyć, gdy l == r m = floor((l + r) / 2) # nie sprawdzamy czy arr[s] == x, # bo wartości mogą się powtarzać i pierwsza szukana może być dalej z lewej, # więc nie chcemy kończyć po znalezieniu dowolnego elementu rówego x if list[m] < x: l = m + 1 else: r = m # m zamiast m - 1, bo arr[m] >= x, czyli jest potencjalnym rozwiązaniem return l # zwracamy l - indeks, na którym skończyliśmy wyszukiwanie ``` ### Indeks pierwszego elementu niewiększego od szukanego **UWAGA:** W tym wariancie do obliczania ```m``` musimy używać funkcji ```ceil```(sufit), która w C++ znajduje się w bibliotece ```cmath```, a w pythonie w ```math```. Zaokrąglanie w dół nie zadziała, ponieważ w przypadku, gdy zakres przeszukiwań będzie dwuelementowy (```l == r - 1```), obliczona wartość ```m``` zawsze będzie równa ```l```, więc wykonując instrukcję ```l = m```, nic nie zmienimy. Używając zaokrąglania w górę, w takiej sytuacji obliczona wartość ```m``` będzie równa ```r``` zatem obie z instrukcji ```r = m - 1```, ```l = m``` zmniejszą przedział poszukiwań. #### C++ ```cpp= int binarySearch(int arr[], int l, int r, int x) { while (l < r) { int m = ceil((l + r) / 2); if (arr[m] > x) { r = m - 1; } else { l = m; } return l; } ``` #### Python ```python= def binarySearch(list, l, r, x): while l < r: m = ceil((l + r) / 2) if list[m] > x: r = m - 1 else: l = m return l ``` ### Wersja podstawowa rekurencyjnie #### C++ ```cpp= int binarySearch(int arr[], int l, int r, int x) { if (l > r) { return -1; } int s = (l + r) / 2; if (arr[s] == x) { return s; } if (arr[s] > x) { return binarySearch(arr, l, s - 1, x); } return binarySearch(arr, s + 1, r, x); } ``` #### Python ```python= def binarySearch(list, l, r, x): if l > r: return -1 s = floor((l + r) / 2) if list[s] == x: return s if list[s] > x: return binarySearch(list, l, s - 1, x) return binarySearch(list, s + 1, r, x) ``` ## Szybkie potęgowanie - Obliczamy $a^n$ - Korzystamy z binarnej reprezentacji $n$, domnażając $a^k$ do wyniku, jeśli $k$-ty od prawej bit $n$ jest jedynką. ### Iteracyjnie #### C++ ```cpp= int fastExp(int a, int n) { if (n == 0) { return 1; } int result = 1; while (n > 0) { if (n % 2 == 1) { result *= a; } a *= a; n /= 2; } return result; } ``` #### Python ```python= def fastExp(a, n): if n == 0: return 1 result = 1 while n > 0: if n % 2 == 1: result *= a a *= a n = floor(n / 2) return result ``` ### Rekurencyjnie #### C++ ```cpp= int fastExp(int a, int n) { if (n == 0) { return 1; } if (n % 2 == 1) { return fastExp(a * a, n / 2) * a; } return fastExp(a * a, n / 2); } ``` #### Python ```python= def fastExp(a, n): if n == 0: return 1 if n % 2 == 1: return fastExp(a * a, floor(n / 2)) * a return fastExp(a * a, floor(n / 2)) ``` ## Wyznaczanie miejsc zerowych funkcji - Znamy wykres funkcji, ale nie znamy dokładnie miejsc zerowych - Zmniejszamy przedział poszukiwań o połowę, tak żeby miejsce zerowe ciągle się w nim zawierało - Korzystamy z faktu, że jeśli $f(a) * f(b) < 0$, to pomiędzy $a$ i $b$ jest miejsce zerowe - Kończymy, gdy znajdziemy się wystarczająco blisko zera #### C++ ```cpp= /* Jakaś zadana z góry funkcja */ double f(double x) { return sin(x) + pow(x, 2) - pow(x, 3) - 0.5; } /* Dokładność z jaką chcemy obliczyć miejsce zerowe */ double EPSILON = 0.00001; double zero(double l, double r) { double m = (l + r) / 2.0; while(abs(f(m)) > EPSILON) { if (f(l) * f(m) < 0) { r = m; } else { l = m; } m = (l + r) / 2; } return m; } ``` #### Python ```python= # Jakaś zadana z góry funkcja def f(x): return sin(x) + x**2 - x**3 - 0.5 # Dokładność z jaką chcemy obliczyć miejsce zerowe EPSILON = 0.00001 def zero(l, r): m = (l + r) / 2 while abs(f(m)) > EPSILON: if f(l) * f(m) < 0: r = m else: l = m m = (l + r) / 2 return m ``` ## Jednoczesne wyszukiwanie wartości minimalnej i maksymalnej > Tego nie zdążyliśmy przerobić na wykładzie - Szukamy minimum i maksimum w dowolnej tablicy - Dzielimy tablicę na pół i znajdujemy min i max osobno dla lewej i prawej połowy - Porównujemy znalezione wartości i zwracamy mniejszego mina oraz większego maxa - Przypadkiem bazowym będzie sytuacja, gdy nasz przedział poszukiwań jest jedno lub dwuelementowy - wtedy znalezienie mina i maxa jest trywialne #### C++ ```cpp= /* Jako że nie możemy z funkcji zwrócić dwóch wartości to pakujemy je do pair * pair to po prostu ładna reprezentacja pary liczb * <int, int> to typy wartości jakie będziemy przechowywać w parze * make_pair(a, b) tworzy parę przechowującą wartości a i b * Do elementów pary odwołujemy się przez .first oraz .second */ /* Pierwszy element pary będzie min, drugi max */ pair<int, int> minMax(int arr[], int l, int r) { int min, max; /* Przypadek bazowy */ if (l == r) { min = max = arr[l]; } else if (l == r - 1) { if (arr[l] > arr[r]) { min = arr[r]; max = arr[l]; } else { min = arr[l]; max = arr[r]; } } else { /* Dzielimy przedział na pół */ int m = (l + r) / 2; /* Znajdujemy rozwiązania dla obu podproblemów (połówek) */ pair<int, int> lMinMax = minMax(arr, l, m); pair<int, int> rMinMax = minMax(arr, m + 1, r); /* Korzystamy z roziązań podproblemów to obliczania rozwiązania problemu głównego */ if (lMinMax.first < rMinMax.first) { min = lMinMax.first; } else { min = rMinMax.first; } if (lMinMax.second > rMinMax.second) { max = lMinMax.second; } else { max = rMinMax.second; } } return make_pair(min, max); } ``` #### Python ```python= # Jako że nie możemy z funkcji zwrócić dwóch wartości to pakujemy je do krotki # krotka to taka lista, ale nie da się zmieniać jej elementów po stworzeniu # (a, b) tworzy krotkę przechowującą wartości a i b # (można przechowywać więcej wartości) # Do elementów krotki odwołujemy się jak w przypadku listy przez krotka[indeks] # Pierwszy element krotki będzie min, drugi max def minMax(list, l, r): min, max = None, None # Przypadek bazowy if l == r: min = max = list[l] elif l == r - 1: if list[l] > list[r]: min = list[r] max = list[l] else: min = list[l] max = list[r] else: # Dzielimy przedział na pół m = floor((l + r) / 2) # Znajdujemy rozwiązania dla obu podproblemów (połówek) lMinMax = minMax(list, l, m) rMinMax = minMax(list, m + 1, r) # Korzystamy z roziązań podproblemów to obliczania rozwiązania problemu głównego if lMinMax[0] < rMinMax[0]: min = lMinMax[0] else: min = rMinMax[0] if lMinMax[1] > rMinMax[1]: max = lMinMax[1] else: max = rMinMax[1] return (min, max) ```