├── graphs ├── index.tex ├── problems.tex └── algorithms.tex ├── notes.pdf ├── sorting ├── index.tex ├── problems.tex ├── applications.tex └── fundamentals.tex ├── tests ├── 05.06.2024 │ ├── problems.pdf │ ├── solutions.pdf │ ├── problems.tex │ └── solutions.tex └── 10.04.2024 │ ├── problems.pdf │ ├── solutions.pdf │ ├── problems.tex │ └── solutions.tex ├── algorithms ├── trust-me-it-sorts.txt ├── euclid.txt ├── pow2.txt ├── fib-slow.txt ├── find.txt ├── maximum.txt ├── task2.txt ├── num-slopes.txt ├── task1.txt ├── anon-alg1.txt ├── quicksort.txt ├── exp-rec.txt ├── find-missing-and-duplicate.txt ├── exp.txt ├── foo.txt ├── subset-sum.txt ├── alg1.txt ├── selection-sort.txt ├── bubble-sort.txt ├── mult.txt ├── two-sum.txt ├── binomial-factorial.txt ├── fib-iter-linear.txt ├── catalan.txt ├── fib-dp.txt ├── partition.txt ├── select.txt ├── vertex-cover.txt ├── anon-alg2.txt ├── kadane.txt ├── merge-sort.txt ├── quux.txt ├── alg2.txt ├── task3.txt ├── clique.txt ├── sat.txt ├── binary-search-rec.txt ├── stooge-sort.txt ├── find-majority.txt ├── solve-in-ptime.txt ├── binary-search.txt ├── clique-to-vertex-cover.txt ├── kruskal.txt ├── fibonacci-fast.txt ├── word-break.txt ├── dfs.txt ├── longest-common-subsequence.txt ├── dijkstra.txt ├── bfs.txt ├── merge.txt └── 3sat-to-subset-sum.txt ├── intractability ├── index.tex ├── problems.tex ├── p-and-np.tex └── np-hardness.tex ├── complexity ├── index.tex ├── problems.tex ├── iterative.tex └── recursive.tex ├── correctness ├── index.tex ├── recursive.tex ├── more-examples.tex ├── problems.tex └── iterative.tex ├── structures └── union_find.txt ├── foreword.tex ├── README.md ├── notes.tex ├── .gitignore ├── asymptotic-analysis.tex ├── lower-bounds.tex └── dynamic-programming.tex /graphs/index.tex: -------------------------------------------------------------------------------- 1 | \input{graphs/algorithms.tex} 2 | \input{graphs/problems.tex} -------------------------------------------------------------------------------- /notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/HEAD/notes.pdf -------------------------------------------------------------------------------- /sorting/index.tex: -------------------------------------------------------------------------------- 1 | \input{sorting/fundamentals.tex} 2 | \input{sorting/applications.tex} 3 | \input{sorting/problems.tex} -------------------------------------------------------------------------------- /tests/05.06.2024/problems.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/HEAD/tests/05.06.2024/problems.pdf -------------------------------------------------------------------------------- /tests/05.06.2024/solutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/HEAD/tests/05.06.2024/solutions.pdf -------------------------------------------------------------------------------- /tests/10.04.2024/problems.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/HEAD/tests/10.04.2024/problems.pdf -------------------------------------------------------------------------------- /tests/10.04.2024/solutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/HEAD/tests/10.04.2024/solutions.pdf -------------------------------------------------------------------------------- /algorithms/trust-me-it-sorts.txt: -------------------------------------------------------------------------------- 1 | TrustMeItSorts($A[1 \dots n] \in \arr$): 2 | for $i \leftarrow 1$ to $n$: 3 | $A[i] \leftarrow i$ -------------------------------------------------------------------------------- /intractability/index.tex: -------------------------------------------------------------------------------- 1 | \input{intractability/p-and-np.tex} 2 | \input{intractability/np-hardness.tex} 3 | \input{intractability/problems.tex} -------------------------------------------------------------------------------- /algorithms/euclid.txt: -------------------------------------------------------------------------------- 1 | Euclid($a, b \in \N$ where $a \geq b$): 2 | if $b = 0$: 3 | return $a$ 4 | 5 | return Euclid($b$, $\bmod(a, b)$) -------------------------------------------------------------------------------- /algorithms/pow2.txt: -------------------------------------------------------------------------------- 1 | Pow2($n \in \N$): 2 | $r \leftarrow 1$ 3 | 4 | for $i \leftarrow 1$ to $n$: 5 | $r \leftarrow 2r$ 6 | 7 | return $r$ -------------------------------------------------------------------------------- /algorithms/fib-slow.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{Fib}$($n \in \N$): 2 | if $n < 2$: 3 | return $n$ 4 | 5 | return $\mathfrak{Fib}(n - 1) + \mathfrak{Fib}(n - 2)$ -------------------------------------------------------------------------------- /algorithms/find.txt: -------------------------------------------------------------------------------- 1 | Find($A[1 \dots n] \in \arr$; $v \in \Z$): 2 | for $i \leftarrow 1$ to $n$: 3 | if $A[i] = v$: return $i$ 4 | 5 | return $-1$ -------------------------------------------------------------------------------- /complexity/index.tex: -------------------------------------------------------------------------------- 1 | \chapter{Анализ на сложността на алгоритми} 2 | \input{complexity/iterative.tex} 3 | \input{complexity/recursive.tex} 4 | \input{complexity/problems.tex} -------------------------------------------------------------------------------- /algorithms/maximum.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{M}$($A[1 \dots n] \in \arr$): 2 | if $n = 0$: 3 | return $-\infty$ 4 | else: 5 | return $\max(A[n], \mathfrak{M}(A[1 \dots n - 1]))$ 6 | -------------------------------------------------------------------------------- /algorithms/task2.txt: -------------------------------------------------------------------------------- 1 | $F_2$($n \in \N$): 2 | $s \leftarrow 0$ 3 | 4 | for $i \leftarrow 1$; $i^2 \leq n$; $i \leftarrow i + 3$: 5 | $s \leftarrow s + F_1(n)$ 6 | 7 | return $s$ -------------------------------------------------------------------------------- /correctness/index.tex: -------------------------------------------------------------------------------- 1 | \chapter{Коректност на алгоритми} 2 | \input{correctness/iterative.tex} 3 | \input{correctness/recursive.tex} 4 | \input{correctness/more-examples.tex} 5 | \input{correctness/problems.tex} -------------------------------------------------------------------------------- /algorithms/num-slopes.txt: -------------------------------------------------------------------------------- 1 | NumSlopes($A[1 \dots n] \in \arr$): 2 | $s \leftarrow 1$ 3 | 4 | for $i \leftarrow 2$ to $n$: 5 | if $A[i - 1] > A[i]$: 6 | $s \leftarrow s + 1$ 7 | 8 | return $s$ -------------------------------------------------------------------------------- /algorithms/task1.txt: -------------------------------------------------------------------------------- 1 | $F_1$($n \in \N$): 2 | $s \leftarrow 0$ 3 | 4 | for $i \leftarrow 1$ to $n$: 5 | for $j \leftarrow 1$; $j \leq i$; $j \leftarrow 2j$: 6 | $s \leftarrow s + ij$ 7 | 8 | return $s$ -------------------------------------------------------------------------------- /algorithms/anon-alg1.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{A}$($A[1 \dots n] \in \arr$): 2 | for $i \leftarrow 1$ to $n - 1$: 3 | for $j \leftarrow i + 1$ to $n$: 4 | if $A[i] = A[j]$: 5 | return $\T$ 6 | 7 | return $\F$ -------------------------------------------------------------------------------- /algorithms/quicksort.txt: -------------------------------------------------------------------------------- 1 | Quicksort($A[1 \dots n] \in \arr$): 2 | if $n \leq 1$: 3 | return 4 | 5 | $pp \leftarrow \mathtt{Partition}(A[1 \dots n])$ 6 | Quicksort($A[1 \dots pp - 1]$) 7 | Quicksort($A[pp + 1 \dots n]$) -------------------------------------------------------------------------------- /algorithms/exp-rec.txt: -------------------------------------------------------------------------------- 1 | $\mathcal{P}$($x, y \in \N$): 2 | if $y = 0$: 3 | return $1$ 4 | 5 | $s \leftarrow \mathcal{P}(x, \frac{y}{2})$ 6 | 7 | if $y \equiv 1 \pmod{2}$: 8 | return $s^2 x$ 9 | else: 10 | return $s^2$ -------------------------------------------------------------------------------- /algorithms/find-missing-and-duplicate.txt: -------------------------------------------------------------------------------- 1 | FindMissingAndDuplicate($A[1 \dots n]$): 2 | $(B, C) \leftarrow (A[1] + \dots + A[n] - 1 - \dots - n, A[1]^2 + \dots A[n]^2 - 1^2 - \dots - n^2)$ 3 | 4 | $d \leftarrow (B + C / B)/2$ 5 | $m = d - B$ 6 | 7 | return $(d, m)$ -------------------------------------------------------------------------------- /algorithms/exp.txt: -------------------------------------------------------------------------------- 1 | Exp($x, y \in \N$): 2 | $res \leftarrow 1$ 3 | 4 | while $y > 0$: 5 | if $y \equiv 1 \pmod 2$: 6 | $res \leftarrow res \cdot x$ 7 | 8 | $y \leftarrow \frac{y}{2}$ 9 | $x \leftarrow x^2$ 10 | 11 | return $res$ -------------------------------------------------------------------------------- /algorithms/foo.txt: -------------------------------------------------------------------------------- 1 | Foo($a \in \N$): 2 | $x \leftarrow 6$ 3 | $y \leftarrow 1$ 4 | $z \leftarrow 0$ 5 | 6 | for $i \leftarrow 0$ to $a - 1$: 7 | $z \leftarrow z + y$ 8 | $y \leftarrow y + x$ 9 | $x \leftarrow x + 6$ 10 | 11 | return $z$ -------------------------------------------------------------------------------- /algorithms/subset-sum.txt: -------------------------------------------------------------------------------- 1 | SubsetSum($A[1 \dots n]$ - масив от положителни числа; 2 | $s$ - естествено число; $I[1 \dots k]$ - сертификат): 3 | инициализирай $S$ с $0$ 4 | 5 | за всяко $i$ от $1$ до $k$: 6 | прибави $A[I[i]]$ към $S$ 7 | 8 | върни дали $S = s$? -------------------------------------------------------------------------------- /algorithms/alg1.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{A}_1$($n \in \N$): 2 | if $n < 2$: 3 | return $n$ 4 | 5 | $a \leftarrow 0$ 6 | 7 | for $i \leftarrow 1$ to $n$: 8 | $a \leftarrow a + i$ 9 | 10 | return $a + \mathfrak{A}_1(n - 1) + \mathfrak{A}_1(n - 1) + \mathfrak{A}_1(n - 2)$ -------------------------------------------------------------------------------- /algorithms/selection-sort.txt: -------------------------------------------------------------------------------- 1 | SelectionSort($A[1 \dots n] \in \arr$): 2 | for $i \leftarrow 1$ to $n - 1$: 3 | $m \leftarrow i$ 4 | 5 | for $j \leftarrow i + 1$ to $n$: 6 | if $A[j] < A[m]$: 7 | $m \leftarrow j$ 8 | 9 | swap($A[i]$, $A[m]$) 10 | -------------------------------------------------------------------------------- /algorithms/bubble-sort.txt: -------------------------------------------------------------------------------- 1 | Sort($A[1 \dots n] \in \arr$): 2 | for $i \leftarrow 1$ to $n - 1$: 3 | for $j \leftarrow 1$ to $n - i - 1$: 4 | if $A[j] > A[j + 1]$: 5 | $temp \leftarrow A[j]$ 6 | $A[j] \leftarrow A[j + 1]$ 7 | $A[j + 1] \leftarrow temp$ -------------------------------------------------------------------------------- /algorithms/mult.txt: -------------------------------------------------------------------------------- 1 | Mult($A, B, C \in (\Z)_{n \cross n}$) 2 | for $i \leftarrow 1$ to $n$: 3 | for $j \leftarrow 1$ to $n$: 4 | $s \leftarrow 0$ 5 | 6 | for $k \leftarrow 1$ to $n$: 7 | $s \leftarrow s + A[i][k] \cdot B[k][j]$ 8 | 9 | $C[i][j] \leftarrow s$ -------------------------------------------------------------------------------- /algorithms/two-sum.txt: -------------------------------------------------------------------------------- 1 | Solve$\mathbf{2SUM}$($A[1 \dots n] \in \arr$; $t \in \Z$): 2 | $(l, r) \leftarrow (1, n)$ 3 | 4 | while $l < r$: 5 | if $A[l] + A[r] = t$: return $\T$ 6 | if $A[l] + A[r] < t$: $l \leftarrow l + 1$ 7 | if $A[l] + A[r] > t$: $r \leftarrow r - 1$ 8 | 9 | return $\F$ -------------------------------------------------------------------------------- /algorithms/binomial-factorial.txt: -------------------------------------------------------------------------------- 1 | BinomialFactorial($n, k \in \N$): 2 | if $n < k$: 3 | return $0$ 4 | 5 | declare $F[0 \dots n] \in \arr$ 6 | $F[0] \leftarrow 1$ 7 | 8 | for $i \leftarrow 1$ to $n$: 9 | $F[i] \leftarrow i \cdot F[i - 1]$ 10 | 11 | return $F[n]/(F[k] \cdot F[n - k])$ -------------------------------------------------------------------------------- /algorithms/fib-iter-linear.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{F}$($n \in \N$): 2 | if $n < 2$: 3 | return $n$ 4 | 5 | $a \leftarrow 0$ 6 | $b \leftarrow 1$ 7 | 8 | for $i \leftarrow 1$ to $n - 1$: 9 | $t \leftarrow a$ 10 | $a \leftarrow b$ 11 | $b \leftarrow t + b$ 12 | 13 | return $b$ -------------------------------------------------------------------------------- /algorithms/catalan.txt: -------------------------------------------------------------------------------- 1 | $\mathcal{C}$($n \in \N$): 2 | declare $C[0 \dots n] \in \arr$ 3 | $C[0] \leftarrow 1$ 4 | 5 | for $i \leftarrow 1$ to $n$: 6 | $C[i] \leftarrow 0$ 7 | 8 | for $j \leftarrow 1$ to $i$: 9 | $C[i] \leftarrow C[i] + C[j - 1] \cdot C[i - j]$ 10 | 11 | return $C[n]$ -------------------------------------------------------------------------------- /algorithms/fib-dp.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{FibDP}$($n \in \N$): 2 | if $n < 2$: 3 | return $n$ 4 | 5 | declare $F[0 \dots n] \in \arr$ 6 | 7 | $F[0] \leftarrow 0$ 8 | $F[1] \leftarrow 1$ 9 | 10 | 11 | for $i \leftarrow 2$ to $n$: 12 | $F[i] \leftarrow F[i - 1] + F[i - 2]$ 13 | 14 | return $F[n]$ -------------------------------------------------------------------------------- /algorithms/partition.txt: -------------------------------------------------------------------------------- 1 | Partition($A[1 \dots n] \in \arr$): 2 | $pivot \leftarrow A[n]$ 3 | $pp \leftarrow 1$ 4 | 5 | for $i \leftarrow 1$ to $n - 1$: 6 | if $A[i] < pivot$: 7 | swap($A[i]$,$\;A[pp]$) 8 | $pp \leftarrow pp + 1$ 9 | 10 | swap($A[pp]$,$\;A[n]$) 11 | return $pp$ -------------------------------------------------------------------------------- /algorithms/select.txt: -------------------------------------------------------------------------------- 1 | Select($A[1 \dots n] \in \arr$; $k \in \{ 1, \dots, n \}$): 2 | $pp \leftarrow \mathtt{Partition}(A[1 \dots n])$ 3 | 4 | if $k = pp$: 5 | return $A[k]$ 6 | if $k < pp$: 7 | return Select($A[1 \dots pp - 1]$, $k$) 8 | if $k > pp$: 9 | return Select($A[pp + 1 \dots n]$, $k - pp$) -------------------------------------------------------------------------------- /algorithms/vertex-cover.txt: -------------------------------------------------------------------------------- 1 | VertexCover($G = (V, E)$ - граф; 2 | $k$ - естествено число; $X[1 \dots n]$ - сертификат): 3 | ако $n > k$: 4 | върни False 5 | 6 | за всяко ребро $(u, v) \in E$: 7 | ако $u \notin X[1 \dots n]$ и $v \notin X[1 \dots n]$: 8 | върни False 9 | 10 | върни True -------------------------------------------------------------------------------- /algorithms/anon-alg2.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{U}$($A[1 \dots n] \in \arr$): 2 | $(x, y) \leftarrow (\T, \T)$ 3 | 4 | for $i \leftarrow 1$ to $n - 1$: 5 | if $x \: \& \: A[i] > A[i + 1]$: 6 | $x \leftarrow \F$ 7 | else if $\neg x \: \& \: A[i] < A[i + 1]$: 8 | $y \leftarrow \F$ 9 | 10 | return $y$ -------------------------------------------------------------------------------- /algorithms/kadane.txt: -------------------------------------------------------------------------------- 1 | Kadane($A[1 \dots n] \in \arr$): 2 | $max\_so\_far \leftarrow A[1]$ 3 | $max\_ending\_here \leftarrow A[1]$ 4 | 5 | for $i \leftarrow 2$ to $n$: 6 | $max\_ending\_here = \max(max\_ending\_here + A[i], A[i])$ 7 | $max\_so\_far = \max(max\_ending\_here, max\_so\_far)$ 8 | 9 | return $max\_so\_far$ -------------------------------------------------------------------------------- /algorithms/merge-sort.txt: -------------------------------------------------------------------------------- 1 | MergeSort($A[1 \dots n] \in \arr$): 2 | if $n = 1$: 3 | return copy($A[1 \dots n]$) 4 | 5 | MergeSort($A[1 \dots \lfloor \frac{n}{2} \rfloor]$) 6 | MergeSort($A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$) 7 | return Merge($A[1 \dots \lfloor \frac{n}{2} \rfloor]$, $A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$) -------------------------------------------------------------------------------- /algorithms/quux.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{bar}$($n \in \Z$): return $\sqrt{n^2}$ 2 | 3 | $\mathfrak{foo}$($x, y \in Z$): return $\frac{x + y + \mathfrak{bar}(x - y)}{2}$ 4 | 5 | $\mathfrak{quux}$($A[1 \dots n] \in \arr$): 6 | $a \leftarrow A[1]$ 7 | 8 | for $i \leftarrow 2$ to $n$: 9 | $a \leftarrow \mathfrak{foo}(a, A[i])$ 10 | 11 | return $a$ -------------------------------------------------------------------------------- /algorithms/alg2.txt: -------------------------------------------------------------------------------- 1 | $\mathfrak{A}_2$($n \in \N$): 2 | if $n < 2$: 3 | return $2$ 4 | 5 | $t \leftarrow 0$ 6 | $t \leftarrow t + \mathfrak{A}_2(\frac{n}{3})$ 7 | 8 | for $i \leftarrow 2$; $i < n$; $i \leftarrow 2i$: 9 | $t \leftarrow t + 1$ 10 | 11 | $t \leftarrow t \cdot \mathfrak{A}_2(\frac{n}{3})$ 12 | 13 | return $t$ -------------------------------------------------------------------------------- /algorithms/task3.txt: -------------------------------------------------------------------------------- 1 | $F_3$($n \in \N$): 2 | $s \leftarrow 0$ 3 | 4 | for $i \leftarrow 1$; $i \leq n^2$; inc($i$): 5 | for $j \leftarrow 1$; $j \leq 2i$; inc($j$): 6 | if $j \equiv 0 \pmod{2}$: 7 | $s \leftarrow s + F_1(n)$ 8 | else: 9 | $s \leftarrow s + F_2(n)$ 10 | 11 | return $s$ -------------------------------------------------------------------------------- /algorithms/clique.txt: -------------------------------------------------------------------------------- 1 | Clique($G = (V, E)$ - граф; 2 | $k$ - естествено число; $X[1 \dots n]$ - сертификат): 3 | ако $n < k$: 4 | върни False 5 | 6 | за всеки връх $u \in X[1 \dots n]$: 7 | за всеки връх $v \in X[1 \dots n]$: 8 | ако $u \neq v$ и $(u, v) \notin E$: 9 | върни False 10 | 11 | върни True -------------------------------------------------------------------------------- /algorithms/sat.txt: -------------------------------------------------------------------------------- 1 | SAT($\varphi$ - съждителна формула в КНФ; $V[1 \dots k]$ - сертификат): 2 | за всеки дизюнкт $D$ във $\varphi$: 3 | ако има литерал $L$ в $D$, за който 4 | ($L = x$ и $x \in V[1 \dots k]$) или ($L = \overline{x}$ и $x \notin V[1 \dots k]$): 5 | продължи нататък 6 | иначе: 7 | върни False 8 | 9 | върни True -------------------------------------------------------------------------------- /algorithms/binary-search-rec.txt: -------------------------------------------------------------------------------- 1 | BinarySearch($A[1 \dots n] \in \arr$; $l, r \in \{1, \dots n\}$; $v \in \Z$): 2 | if $l > r$: 3 | return $-1$ 4 | 5 | $m \leftarrow \lfloor \frac{l + r}{2} \rfloor$ 6 | 7 | if $A[m] = v$: return $m$ 8 | if $A[m] < v$: return BinarySearch($A[1 \dots n]$, $m + 1$, $r$, $v$) 9 | if $A[m] > v$: return BinarySearch($A[1 \dots n]$, $l$, $m - 1$, $v$) -------------------------------------------------------------------------------- /algorithms/stooge-sort.txt: -------------------------------------------------------------------------------- 1 | SS($A[1 \dots n]$; $l, h \in \{ 1, \dots, n \}$): 2 | if $l \geq h$: 3 | return 4 | 5 | if $A[l] > A[h]$: 6 | swap($A[l]$, $A[h]$) 7 | 8 | $t \leftarrow \frac{h - l + 1}{3}$ 9 | 10 | if $t \geq 1$: 11 | SS($A[1 \dots n]$, $l$, $h - t$) 12 | SS($A[1 \dots n]$, $l + t$, $h$) 13 | SS($A[1 \dots n]$, $l$, $h - t$) -------------------------------------------------------------------------------- /algorithms/find-majority.txt: -------------------------------------------------------------------------------- 1 | FindMajority($A[1 \dots n] \in \arr$): 2 | $m \leftarrow A[1]$ 3 | $c \leftarrow 1$ 4 | 5 | for $i \leftarrow 2$ to $n$: 6 | if $c = 0$: 7 | $m \leftarrow A[i]$ 8 | $c \leftarrow 1$ 9 | else if $A[i] = m$: 10 | $c \leftarrow c + 1$ 11 | else: 12 | $c \leftarrow c - 1$ 13 | 14 | return $m$ -------------------------------------------------------------------------------- /algorithms/solve-in-ptime.txt: -------------------------------------------------------------------------------- 1 | Реши$\mathbf{Y}$(Вход$\mathbf{Y}$): 2 | Вход$\mathbf{X}$ $\leftarrow$ РедуцирайВход$\mathbf{Y}$ДоВход$\mathbf{X}$ЗаПолиномиалноВреме(Вход$\mathbf{Y}$) 3 | Изход$\mathbf{X}$ $\leftarrow$ Реши$\mathbf{X}$ЗаПолиномиалноВреме(Вход$\mathbf{X}$) 4 | Изход$\mathbf{Y}$ $\leftarrow$ РедуцирайИзход$\mathbf{X}$ДоИзход$\mathbf{Y}$ЗаПолиномиалноВреме(Изход$\mathbf{X}$) 5 | върни Изход$\mathbf{Y}$ -------------------------------------------------------------------------------- /algorithms/binary-search.txt: -------------------------------------------------------------------------------- 1 | BinarySearch($A[1 \dots n] \in \arr$; $v \in \Z$): 2 | $l \leftarrow 1$ 3 | $r \leftarrow n$ 4 | 5 | while $l \leq r$: 6 | $m \leftarrow \lfloor \frac{l + r}{2} \rfloor$ 7 | 8 | if $A[m] = v$: 9 | return $m$ 10 | else if $A[m] < v$: 11 | $l \leftarrow m + 1$ 12 | else: 13 | $r \leftarrow m - 1$ 14 | 15 | return $-1$ -------------------------------------------------------------------------------- /algorithms/clique-to-vertex-cover.txt: -------------------------------------------------------------------------------- 1 | Реши$\mathbf{Clique}$Чрез$\mathbf{VertexCover}$($G = (V, E)$ граф; $k$ - естествено число): 2 | инициализирай граф $\overline{G} = (V, \varnothing)$ 3 | 4 | за всеки връх $u \in V$: 5 | за всеки връх $v \in V$: 6 | ако $u \neq v$ и $(u, v) \notin E$: 7 | добави реброто $(u, v)$ към $\overline{G}$ 8 | 9 | върни Реши$\mathbf{VertexCover}$($\overline{G}$, $|V| - k$) -------------------------------------------------------------------------------- /algorithms/kruskal.txt: -------------------------------------------------------------------------------- 1 | Kruskal($E[1 \dots m]$ where $E[i] = (u, v)$ for some $u, v \in \{ 1, \dots, n \}$): 2 | Sort($E[1 \dots m]$, $\leq_G$) 3 | init empty $t \in \dynarr(\operatorname{type\_of\_element}(E[1 \dots m]))$ 4 | init $dsu \in\;$UnionFind($n$) 5 | 6 | for $i \leftarrow 1$ to $m$: 7 | $(u, v) \leftarrow E[i]$ 8 | 9 | if $dsu$.Unify($u$, $v$): 10 | $t$.push($(u, v)$) 11 | 12 | return $t$ -------------------------------------------------------------------------------- /algorithms/fibonacci-fast.txt: -------------------------------------------------------------------------------- 1 | FibMatrixExp($R \in (\N)_{2 \cross 2}$; $n \in \N$): 2 | if $n = 0$: 3 | $R \leftarrow E_{2 \cross 2}$ 4 | 5 | FibMatrixExp($R$, $\frac{n}{2}$) 6 | $R \leftarrow R^2$ 7 | 8 | if $n \equiv 1 \pmod{2}$: 9 | $R \leftarrow R \cdot \mathfrak{F}^*$ 10 | 11 | F($n \in \N$): 12 | if $n \leq 1$: 13 | return $n$ 14 | 15 | init($R \in (\N)_{2 \cross 2}$) 16 | FibMatrixExp($R$, $n - 1$); 17 | 18 | return $R[1][1]$; 19 | -------------------------------------------------------------------------------- /algorithms/word-break.txt: -------------------------------------------------------------------------------- 1 | WordBreak($\alpha \in \Sigma^*$; $D \subseteq_{fin} \Sigma^+$): 2 | $WB[0 \dots |\alpha|] \leftarrow [\F, \dots \F]$ 3 | $WB[0] \leftarrow \T$ 4 | 5 | for $i \leftarrow 1$ to $|\alpha|$: 6 | for $\beta \in D$: 7 | if $i < |\beta|$: 8 | continue 9 | 10 | if $\left(i = |\beta| \lor WB[i - |\beta|]\right)$: 11 | if $\alpha_{i - |\beta| + 1} \dots \alpha_i = \beta$: 12 | $WB[i] \leftarrow \T$ 13 | break 14 | 15 | return $WB[|\alpha|]$ -------------------------------------------------------------------------------- /algorithms/dfs.txt: -------------------------------------------------------------------------------- 1 | HelperDFS($G = (V, E) \in \graph$; $u \in V$; 2 | $vis \in \arrbool$; $r \in \dynarr(V)$): 3 | $r$.push($u$) 4 | $vis[u] \leftarrow \T$ 5 | 6 | for $v \in \adj_G(u)$: 7 | if $\neg vis[v]$: 8 | HelperDFS($G$, $v$, $vis$, $r$) 9 | 10 | DFS($G = (V, E) \in \graph$ where $V = \{ 1, \dots, n \}$): 11 | $vis[1 \dots n] \leftarrow [\F, \dots, \F]$ 12 | init empty $r \in \dynarr(V)$ 13 | 14 | for $i \leftarrow 1$ to $n$: 15 | if $\neg vis[i]$: 16 | HelperDFS($G$, $i$, $vis$, $r$) 17 | 18 | return $r$ -------------------------------------------------------------------------------- /algorithms/longest-common-subsequence.txt: -------------------------------------------------------------------------------- 1 | LCS($S_1[1 \dots n], S_2[1 \dots m] \in \str$): 2 | declare $DP[0 \dots n][0 \dots m] \in \operatorname{matrix}(\N)$ 3 | 4 | for $i \leftarrow 0$ to $n$: 5 | $DP[i][0] \leftarrow 0$ 6 | 7 | for $j \leftarrow 0$ to $m$: 8 | $DP[0][j] \leftarrow 0$ 9 | 10 | for $i \leftarrow 1$ to $n$: 11 | for $j \leftarrow 1$ to $m$: 12 | if $S_1[i - 1] = S_2[j - 1]$: 13 | $DP[i][j] \leftarrow DP[i - 1][j - 1] + 1$ 14 | else: 15 | $DP[i][j] \leftarrow \max(DP[i - 1][j], DP[i][j - 1])$ 16 | 17 | return $DP[n][m]$ -------------------------------------------------------------------------------- /algorithms/dijkstra.txt: -------------------------------------------------------------------------------- 1 | Dijkstra($G = (V, E, w) \in \weightedgraph$ where $V = \{ 1, \dots, n \}$; $s \in V$): 2 | init empty $q \in \pqueue_{\min}(\N \cross V, <_{lex(\N \cross V)})$ 3 | $d[1 \dots n] \leftarrow [-\infty, \dots, -\infty]$ 4 | $\pi[1 \dots n] \leftarrow [0, \dots, 0]$ 5 | $d[s] = 0$ 6 | $q$.push($(d[s], s)$); 7 | 8 | while $\neg q$.empty(): 9 | $(d_u, u) \leftarrow q$.pop() 10 | 11 | for $v \in \adj_G(u)$: 12 | if $d_u + w(u, v) < d[v]$: 13 | $\pi[v] \leftarrow u$ 14 | $d[v] \leftarrow d_u + w(u, v)$ 15 | $q$.push($(d[v], v)$) 16 | 17 | return $\pi[1 \dots n]$ -------------------------------------------------------------------------------- /algorithms/bfs.txt: -------------------------------------------------------------------------------- 1 | HelperBFS($G = (V, E) \in \graph$; $s \in V$; 2 | $vis \in \arrbool$; $r \in \dynarr(V)$): 3 | init empty $q \in \queue(V)$ 4 | $q$.push($s$) 5 | $vis[s] \leftarrow \T$ 6 | 7 | while $\neg q$.empty(): 8 | $u \leftarrow q$.pop() 9 | $r$.push($u$) 10 | 11 | for $v \in \adj_G(u)$: 12 | if $\neg vis[v]$: 13 | $vis[v] \leftarrow \T$ 14 | $q$.push($v$) 15 | 16 | BFS($G = (V, E) \in \graph$ where $V = \{ 1, \dots, n \}$): 17 | $vis[1 \dots n] = [\F, \dots, \F]$ 18 | init empty $r \in \dynarr(V)$ 19 | 20 | for $i \leftarrow 1$ to $n$: 21 | if $\neg vis[i]$: 22 | HelperBFS($G$, $i$, $vis$, $r$) 23 | 24 | return $r$ -------------------------------------------------------------------------------- /algorithms/merge.txt: -------------------------------------------------------------------------------- 1 | Merge($A[1 \dots n], B[1 \dots m] \in \arr$): 2 | init $C[1 \dots n + m] \in \arr$ 3 | $(i, l_A, l_B) \leftarrow (1, 1, 1)$ 4 | 5 | while $(l_A \leq n \: \& \: l_B \leq m)$: 6 | if $A[l_A] \leq B[l_B]$: 7 | $C[i] \leftarrow A[l_A]$ 8 | $l_A \leftarrow l_A + 1$ 9 | else: 10 | $C[i] \leftarrow A[l_B]$ 11 | $l_B \leftarrow l_B + 1$ 12 | $i \leftarrow i + 1$ 13 | 14 | while $l_A \leq n$: 15 | $C[i] \leftarrow A[l_A]$ 16 | $l_A \leftarrow l_A + 1$ 17 | $i \leftarrow i + 1$ 18 | 19 | while $l_B \leq m$: 20 | $C[i] \leftarrow A[l_B]$ 21 | $l_B \leftarrow l_B + 1$ 22 | $i \leftarrow i + 1$ 23 | 24 | return $C[1 \dots n + m]$ -------------------------------------------------------------------------------- /algorithms/3sat-to-subset-sum.txt: -------------------------------------------------------------------------------- 1 | Реши$\mathbf{3SAT}$Чрез$\mathbf{SubsetSum}$($\varphi$ - формула в 3КНФ с променливи $x_1, \dots, x_m$ 2 | и дизюнкти $D_1, \dots, D_l$): 3 | създай масив $A[1 \dots 2m + 2l]$ и инициализирай $t$ с $1$ 4 | 5 | за всяко $1 \leq i \leq m$: 6 | $A[t]$ $\leftarrow$ Построй$t$($m$, $l$, $x_i$, $D_1, \dots, D_l$) 7 | $A[t + 1]$ $\leftarrow$ Построй$f$($m$, $l$, $x_i$, $D_1, \dots, D_l$) 8 | $t$ $\leftarrow$ $t + 2$ 9 | 10 | за всяко $1 \leq j \leq l$: 11 | $A[t]$ $\leftarrow$ Построй$h$Или$g$($m$, $l$) 12 | $A[t + 1]$ $\leftarrow$ Построй$h$Или$g$($m$, $l$) 13 | $t$ $\leftarrow$ $t + 2$ 14 | 15 | $k$ $\leftarrow$ Построй$k$($m$, $l$) 16 | върни Реши$\mathbf{SubsetSum}$($A[1 \dots 2m + 2l]$, $k$) -------------------------------------------------------------------------------- /structures/union_find.txt: -------------------------------------------------------------------------------- 1 | struct UnionFind: 2 | Constructor($n \in \N$): 3 | init $l[1 \dots n] \in \dynarr(\{ 1, \dots, n \})$ 4 | init $s[1 \dots n] \in \dynarr(\N)$ 5 | 6 | for $i \leftarrow 1$ to $n$; 7 | $l[i] \leftarrow i$ 8 | $s[i] \leftarrow 1$ 9 | 10 | GetLeader($x \in \{ 1, \dots, n \}$): 11 | if $x \neq l[x]$: 12 | $l[x] \leftarrow\;$GetLeader($l[x]$) 13 | 14 | return $x$ 15 | 16 | Unify($x, y \in \{ 1, \dots, n \}$): 17 | $x \leftarrow\;$GetLeader($x$) 18 | $y \leftarrow\;$GetLeader($y$) 19 | 20 | if $x = y$: 21 | return $\F$ 22 | 23 | if $s[x] < s[y]$: 24 | swap($x$, $y$) 25 | 26 | $l[y] \leftarrow x$ 27 | $s[x] \leftarrow s[x] + s[y]$ 28 | 29 | return $\T$ -------------------------------------------------------------------------------- /foreword.tex: -------------------------------------------------------------------------------- 1 | \chapter*{Предисловие} 2 | \addcontentsline{toc}{chapter}{Предисловие} 3 | 4 | Тези записки представляват кратко въведение в света на алгоритмите. 5 | Тук се опитах да събера основните концепции и техники, които ще са ви нужни за създаването и анализирането на алгоритми. 6 | 7 | Искам обаче да натъртя, че тези записки НЕ СА завършени и най-вероятно никога няма да бъдат. 8 | Първо, липсват решения и доказателства на много от разгледаните въпроси. 9 | Също така е много вероятно да съдържат множество грешки -- граматични, правописни, пунктуационни и логически. 10 | Така че, моля ви, не ги използвайте като основен източник на информация. 11 | Те са създадени най-вече за да подредя собствените си мисли и евентуално да ви послужат като допълнителен материал по време на вашето обучение. 12 | 13 | Въпреки че тези записки не са перфектни, се надявам те да бъдат полезни и интересни за някои от вас. 14 | Може би ще намерите в тях полезна информация, която ще ви помогне да разберете по-добре материала и да се справите успешно с предизвикателствата в изучаването на алгоритми. 15 | 16 | \vspace*{\fill} 17 | 18 | \begin{flushright} 19 | \textit{Благодаря ви, че отделихте време да разгледате тези записки.} 20 | 21 | \textit{Надявам се, че ще ви бъдат полезни.} 22 | 23 | \textit{Тед} 24 | \end{flushright} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :books: Записки по "Дизайн и анализ на алгоритми" 2 | 3 | Тук качвам кода на [моите](https://github.com/toduko) записки (заедно със самите [тях](https://raw.githubusercontent.com/toduko/design-and-analysis-of-algorithms/master/notes.pdf)) за упражнения по _ДАА_ курса, който се води във [ФМИ СУ](https://fmi.uni-sofia.bg/). 4 | 5 | ## :warning: Важно 6 | 7 | Записките **НЕ СА** завършени: 8 | 9 | - липсват решения на голяма част от задачите; 10 | - решенията, които има, не са непременно пълни. 11 | 12 | Ще се стремя в близкото бъдеще да ги довърша, но нищо не обещавам. 13 | Също така е много вероятно вече направените материали да претърпят големи промени. 14 | 15 | ## :dart: Теми 16 | 17 | Записките покриват (до колкото могат) следните теми: 18 | 19 | - Въведение в алгоритмите и асимптотичния анализ; 20 | - Анализ на сложността на алгоритми; 21 | - Коректност на алгоритми; 22 | - Сортиране и неговите приложения; 23 | - Алгоритми върху графи; 24 | - Динамично програмиране; 25 | - Долни граници; 26 | - Алгоритмична неподатливост. 27 | 28 | ## :memo: Допълнителни задачи 29 | 30 | - първо малко контролно по ДАА на пета група КН, проведено на 10.04.2024 - [условия](tests/10.04.2024/problems.pdf) и [решения](tests/10.04.2024/solutions.pdf); 31 | - второ малко контролно по ДАА на пета група КН, проведено на 05.06.2024 - [условия](tests/05.06.2024/problems.pdf) и [решения](tests/05.06.2024/solutions.pdf). 32 | -------------------------------------------------------------------------------- /intractability/problems.tex: -------------------------------------------------------------------------------- 1 | \section{Задачи} 2 | 3 | \begin{problem} 4 | Да се докаже, че следните задачи са в класа \textbf{NP}: 5 | \begin{itemize} 6 | \item \textbf{2Partition}; 7 | \item \textbf{DominatingSet}; 8 | \item \textbf{Anticlique}; 9 | \item \textbf{HamiltonianPath}; 10 | \item \textbf{SubgraphIsomorphism}; 11 | \item \textbf{TSP}. 12 | \end{itemize} 13 | \end{problem} 14 | 15 | \begin{problem} 16 | Разглеждаме задачата \textbf{StarFreeRegexIneq}: 17 | 18 | \vspace*{2mm} 19 | \textbf{Вход:} Два регулярни израза $r_1$ и $r_2$, в които не участва $*$. 20 | 21 | \textbf{Въпрос:} Вярно ли е, че $\mathcal{L}\llbracket r_1 \rrbracket \neq \mathcal{L}\llbracket r_2 \rrbracket$? 22 | \vspace*{2mm} 23 | 24 | Да се докаже, че задачата \textbf{StarFreeRegexIneq} е в класа \textbf{NP}. 25 | \end{problem} 26 | 27 | \begin{problem} 28 | Разглеждаме задачата \textbf{ChromaticNumber}: 29 | 30 | \vspace*{2mm} 31 | \textbf{Вход:} Граф $G = \opair{V, E}$ и естествено число $k$. 32 | 33 | \textbf{Въпрос:} Вярно ли е, че хроматичното число на $G$ не надвишава $k$? 34 | \vspace*{2mm} 35 | 36 | Да се докаже, че задачата \textbf{ChromaticNumber} е в класа \textbf{NP}. 37 | \end{problem} 38 | 39 | \begin{problem} 40 | Да се докаже, че \textbf{Anticlique} е \NP-пълна задача. 41 | \end{problem} 42 | 43 | \begin{problem} 44 | Да се докаже, че \textbf{2Partition} е \NP-пълна задача. 45 | \end{problem} 46 | 47 | \begin{problem} 48 | Да се докаже, че \textbf{DominatingSet} е \NP-пълна задача. 49 | \end{problem} -------------------------------------------------------------------------------- /complexity/problems.tex: -------------------------------------------------------------------------------- 1 | \section{Задачи} 2 | 3 | \begin{problem} 4 | Да се намери асимптотиката на средната сложност по време на алгоритъма за бързо сортиране т.е. на рекурентното уравнение: 5 | \[ 6 | T(n) = \frac{1}{n} \left(\sum\limits_{i = 1}^{n - 1}T(i) + T(n - i)\right) + n - 1. 7 | \] 8 | \end{problem} 9 | 10 | \begin{problem} 11 | Да се определи сложността по време за функцията: 12 | \lstinputlisting{algorithms/task1.txt} 13 | \end{problem} 14 | 15 | \begin{problem} 16 | Да се определи сложността по време за функцията: 17 | \lstinputlisting{algorithms/task2.txt} 18 | \end{problem} 19 | 20 | \begin{problem} 21 | Да се определи сложността по време за функцията: 22 | \lstinputlisting{algorithms/task3.txt} 23 | \end{problem} 24 | 25 | \begin{problem} 26 | Да се намери асимптотиката на следните рекурентни уравнения: 27 | \begin{align*} 28 | T_1(n) & = 29T_1(\frac{n}{3}) + 2 \sum\limits_{i = 1}^n \frac{1}{i^2} & T_2(n) & = 29T_2(\frac{n}{3}) + 12n + \sqrt{n} \\ 29 | T_3(n) & = T_3(n - 1) + \frac{n}{(n + 1)(n - 1)} & T_4(n) & = 29T_4(\frac{n}{3}) + (\sum\limits_{i = 1}^n \frac{1}{i})^4 \\ 30 | T_5(n) & = 29T_5(\frac{n}{3}) + 2 \sum\limits_{i = 1}^n i^2 & T_6(n) & = 29T_6(\frac{n}{3}) + n^{\sqrt{n}} + (\sqrt{n})^n \\ 31 | T_7(n) & = T_7(\sqrt{n}) + n & T_8(n) & = 29T_8(\frac{n}{3}) + \binom{2n}{2} \\ 32 | T_9(n) & = 8T_9(n - 1) - T_9(n - 2) + 2n2^{2n} + 3n2^{3n}. & & 33 | \end{align*} 34 | \end{problem} 35 | 36 | \newpage 37 | 38 | \begin{problem} 39 | Да се намери сложността по време на следния алгоритъм: 40 | \lstinputlisting{algorithms/alg1.txt} 41 | Ще се промени ли нещо ако връщаме $a + 2 \mathfrak{A}_1(n - 1) + \mathfrak{A}_1(n - 2)$? 42 | \end{problem} 43 | 44 | \begin{problem} 45 | Да се намери сложността по време на следния алгоритъм: 46 | \lstinputlisting{algorithms/alg2.txt} 47 | \end{problem} 48 | 49 | \begin{problem} 50 | Да се намери асимптотиката на следните рекурентни уравнения: 51 | \begin{align*} 52 | T_1(n) & = 2 \sqrt{2} T_1\left(\frac{n}{\sqrt{2}}\right) + n^3 & T_2(n) & = T_2(n - 1) + \frac{1 + n}{n^2} \\ 53 | T_3(n) & = \sum\limits_{i = 0}^{n - 1}\left(T_3(i) + 2^{\frac{n}{2}}\right) & T_4(n) & = 5 T_4\left(\frac{n}{2}\right) + n^2 \log(n). 54 | \end{align*} 55 | \end{problem} -------------------------------------------------------------------------------- /tests/05.06.2024/problems.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,9pt]{extarticle} 2 | \usepackage[T2A]{fontenc} 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[bulgarian]{babel} 5 | 6 | \newcommand\examname{Второ малко контролно по ДАА на група 5} 7 | \newcommand\examdate{05.06.2024 г.} 8 | \newcommand\setsize{\footnotesize}%\normalsize,\small,\footnotesize 9 | \newcommand\remark{Времето за работа е 1 час. Полежавам ви успех.} 10 | \newcommand\quotea{Imparatorum est fortunam sperare.} 11 | \newcommand\quoteb{Fortuna solis imparatis necessis est.} 12 | \newcommand\problems{ 13 | 14 | \begin{problem} {\bf (60 т.)} 15 | \textit{Хубаво} число ще наричаме всяко естествено число от вида $2^a 3^b$ за някои $a, b \in \mathbb{N}$. 16 | Да се състави \textbf{итеративен} алгоритъм, който при подадено $n \in \mathbb{N}$ връща $(n + 1)$-вото по големина хубаво число. 17 | Алгоритъмът \textbf{трябва} да е съставен по схемата \textbf{динамично програмиране} и да има сложност по време $\Theta(n)$. 18 | Няма нужда да се прави доказателство за коректност, достатъчно е да се напише правилен инвариант. 19 | \end{problem} 20 | 21 | \begin{problem} {\bf (60 т.)} 22 | Нека разгледаме задачата TABLE-SUM: 23 | \vspace*{2mm} 24 | 25 | \hspace*{4mm}\textbf{Вход:} Таблица от естествени числа $T[1 \dots n, 1 \dots m]$ и $t \in \mathbb{N}$. 26 | 27 | \hspace*{4mm}\textbf{Въпрос:} Има ли $1 \leq i_1, \dots, i_n \leq m$, за които $\sum\limits_{k = 1}^n T[k, i_k] = t$? 28 | 29 | \vspace*{1mm} 30 | Докажете формално, че TABLE-SUM е \textbf{NP}-пълна задача. 31 | \end{problem} 32 | } 33 | \newcommand\varianta{ 34 | 35 | \problems 36 | 37 | \remark 38 | 39 | \textit{\quotea} 40 | } 41 | 42 | \newcommand\variantb{ 43 | 44 | \problems 45 | 46 | \remark 47 | 48 | \textit{\quoteb} 49 | 50 | } 51 | 52 | \usepackage{tikz} 53 | \usepackage{pgf} 54 | \usetikzlibrary{shapes,arrows,automata} 55 | \usepackage{amsmath} 56 | \usepackage{amsthm} 57 | \usepackage{amssymb} 58 | \usepackage{amsfonts} 59 | \usepackage{enumerate} 60 | \usepackage{multirow} 61 | \usepackage{alltt} 62 | 63 | \theoremstyle{definition} 64 | \newtheorem{problem}{Задача} 65 | 66 | \setlength{\textwidth}{210mm} 67 | \setlength{\textheight}{297mm} 68 | \setlength{\topmargin}{0mm} 69 | \setlength{\voffset}{-1in} 70 | \setlength{\hoffset}{-1in} 71 | \setlength{\headheight}{0mm} 72 | \setlength{\headsep}{0mm} 73 | \parindent 0.5cm 74 | \oddsidemargin 0cm 75 | \evensidemargin 0cm 76 | 77 | \newcommand{\Nat}{\mathbb{N}} 78 | \newcommand{\Int}{\mathbb{Z}} 79 | \newcommand{\Real}{\mathbb{R}} 80 | \def\dotminus{\mathbin{\ooalign{\hss\raise1ex\hbox{.}\hss\cr 81 | \mathsurround=0pt$-$}}} 82 | 83 | \newcommand{\ExamHeader}{ 84 | \footnotesize% 85 | \begin{tabular}{|c|c|c|c|c|c|} 86 | \hline 87 | \multirow{2}{*}{Име:} & \multicolumn{3}{|c|}{\phantom{whyareyouwastingyourtime}} & \multirow{2}{*}{ФН:} & \phantom{readingthistext:D}\\ 88 | & \multicolumn{3}{|c|}{} & &\\ 89 | \hline 90 | \end{tabular} 91 | 92 | \vspace*{2mm} 93 | 94 | \centerline{\examname} 95 | \centerline{\examdate} 96 | } 97 | 98 | \newcommand\twominipages{ 99 | \noindent\hfill 100 | \begin{minipage}[t]{82mm} 101 | \ExamHeader 102 | \setcounter{problem}{0} 103 | 104 | \varianta 105 | \end{minipage} 106 | \hfill\hfill 107 | \begin{minipage}[t]{82mm} 108 | \ExamHeader 109 | \setcounter{problem}{0} 110 | 111 | \variantb 112 | \end{minipage} 113 | \hspace*{\fill} 114 | } 115 | 116 | 117 | \begin{document}\thispagestyle{empty} \setsize 118 | 119 | \vspace*{4mm} 120 | \twominipages 121 | 122 | \vspace*{8mm} 123 | \twominipages 124 | 125 | \vspace*{8mm} 126 | \twominipages 127 | 128 | \vspace*{8mm} 129 | \twominipages 130 | 131 | \vfill 132 | \vspace*{4mm} 133 | 134 | 135 | \end{document} 136 | -------------------------------------------------------------------------------- /notes.tex: -------------------------------------------------------------------------------- 1 | \documentclass[oneside, 12pt]{book} 2 | 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[T2A]{fontenc} 5 | \usepackage[english, bulgarian]{babel} 6 | \usepackage{pgfplots} 7 | \usepackage{amssymb} 8 | \usepackage{hyperref, fancyhdr, lastpage, fancyvrb, tcolorbox, titlesec} 9 | \usepackage{array, tabularx, colortbl} 10 | \usepackage{tikz} 11 | \usepackage{venndiagram} 12 | \usepackage{amsthm, bm} 13 | \usepackage{relsize} 14 | \usepackage{amsmath,physics} 15 | \usepackage{mathtools} 16 | \usepackage{subcaption} 17 | \usepackage{theoremref} 18 | \usepackage{circuitikz} 19 | \usepackage{geometry} 20 | \usepackage{stmaryrd} 21 | \usepackage[symbol]{footmisc} 22 | \usepackage{minted} 23 | \usepackage{enumitem} 24 | \usepackage{listings} 25 | \usepackage{systeme} 26 | \usepackage{forest} 27 | \useforestlibrary{linguistics} 28 | 29 | \pgfplotsset{width=10cm,compat=1.9} 30 | 31 | \newcommand{\N}{\mathbb{N}} 32 | \newcommand{\Z}{\mathbb{Z}} 33 | \newcommand{\R}{\mathbb{R}} 34 | \newcommand{\T}{\mathbb{T}} 35 | \newcommand{\F}{\mathbb{F}} 36 | \newcommand{\calF}{\mathcal{F}} 37 | \newcommand{\calT}{\mathcal{T}} 38 | \newcommand{\NP}{\textbf{NP}} 39 | \newcommand{\arr}{\operatorname{array}(\mathbb{Z})} 40 | \newcommand{\arrbool}{\operatorname{array}(\mathbb{B})} 41 | \newcommand{\str}{\operatorname{string}} 42 | \newcommand{\adj}{\operatorname{adj}} 43 | \newcommand{\queue}{\operatorname{queue}} 44 | \newcommand{\pqueue}{\operatorname{priority\_queue}} 45 | \newcommand{\dynarr}{\operatorname{dynamic\_array}} 46 | \newcommand{\graph}{\mathfrak{Graph}} 47 | \newcommand{\weightedgraph}{\mathfrak{Graph}(\operatorname{weighted})} 48 | \newcommand{\dc}[1]{\tikz \node[draw, circle, inner sep=0pt, minimum size=9mm]{#1};} 49 | \newcommand{\dt}[1]{\tikz \node[circle, inner sep=0pt, minimum size=9mm]{#1};} 50 | 51 | \renewcommand{\theFancyVerbLine}{ 52 | $\arabic{FancyVerbLine}$ 53 | } 54 | 55 | \ExplSyntaxOn 56 | \NewDocumentCommand{\opair}{m} 57 | { 58 | \langle\mspace{2mu} 59 | \clist_set:Nn \l_tmpa_clist { #1 } 60 | \clist_use:Nn \l_tmpa_clist {,\mspace{3mu plus 1mu minus 1mu}\allowbreak} 61 | \mspace{2mu}\rangle 62 | } 63 | \ExplSyntaxOff 64 | 65 | \hypersetup{ 66 | colorlinks=true, 67 | linktoc=all, 68 | linkcolor=blue 69 | } 70 | 71 | \lstdefinelanguage{custom}{ 72 | morekeywords={for, if, else, return, break, continue, while, to, init, empty} 73 | } 74 | 75 | \lstset{ 76 | basicstyle=\ttfamily, 77 | breaklines=true,mathescape=true,numbers=left, 78 | inputencoding=utf8,extendedchars=true,frame=single, 79 | keywordstyle=\bf, 80 | language=custom 81 | } 82 | 83 | \theoremstyle{definition} 84 | \newtheorem*{definition}{Дефиниция} 85 | \newtheorem{problem}{Задача}[chapter] 86 | \newtheorem*{solution}{Решение} 87 | \newtheorem*{warning}{\textcolor{red}{Внимание}} 88 | \theoremstyle{plain} 89 | \newtheorem{theorem}{Теорема}[section] 90 | \newtheorem{invariant}[theorem]{Инвариант} 91 | \newtheorem{claim}[theorem]{Твърдение} 92 | \newtheorem{axiom}[theorem]{Аксиома} 93 | \newtheorem{lemma}[theorem]{Лема} 94 | \newtheorem{corollary}[theorem]{Следствие} 95 | \theoremstyle{remark} 96 | \newtheorem*{remark}{Забележка} 97 | 98 | \pagestyle{fancy} 99 | 100 | \lhead{\leftmark} 101 | \rhead{} 102 | 103 | \setlength\parindent{0pt} 104 | 105 | \begin{document} 106 | 107 | 108 | \begin{titlepage} 109 | \title{Записки за упражнения по ДАА} 110 | \author{Тодор Дуков} 111 | \date{\today} 112 | \end{titlepage} 113 | 114 | \pagenumbering{alph} 115 | \maketitle 116 | 117 | \pagenumbering{roman} 118 | 119 | \tableofcontents 120 | \include{foreword} 121 | 122 | \clearpage 123 | \pagenumbering{arabic} 124 | 125 | \include{asymptotic-analysis} 126 | \include{complexity/index} 127 | \include{correctness/index} 128 | \include{sorting/index} 129 | \include{graphs/index} 130 | \include{dynamic-programming} 131 | \include{lower-bounds} 132 | \include{intractability/index} 133 | 134 | \end{document} -------------------------------------------------------------------------------- /tests/10.04.2024/problems.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,9pt]{extarticle} 2 | \usepackage[T2A]{fontenc} 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[bulgarian]{babel} 5 | 6 | \newcommand\examname{Първо малко контролно по ДАА на група 5} 7 | \newcommand\examdate{10.04.2024 г.} 8 | \newcommand\setsize{\footnotesize}%\normalsize,\small,\footnotesize 9 | \newcommand\remark{Времето за работа е 1 час. Полежавам ви успех.} 10 | \newcommand\quotea{Imparatorum est fortunam sperare.} 11 | \newcommand\quoteb{Fortuna solis imparatis necessis est.} 12 | \newcommand\problems{ 13 | 14 | \begin{problem} {\bf (50 т.)} 15 | Дефинираме \textit{вълнист масив} индуктивно: 16 | \begin{itemize} 17 | \item Всеки едноелементен масив е вълнист. 18 | \item Масив $A[1 \dots n]$ (където $n > 1$) е вълнист, ако 19 | \begin{enumerate} 20 | \item масивът $A[1 \dots \lfloor \frac{n}{2} \rfloor]$ е сортиран, а 21 | \item масивът $A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$ е вълнист. 22 | \end{enumerate} 23 | \end{itemize} 24 | Да се състави алгоритъм с линейна сложност по време, който приема вълнист масив и го сортира. 25 | Коректността на алгоритъма да се обоснове формално и да се изследва сложността по време. 26 | \end{problem} 27 | 28 | \begin{problem} {\bf (70 т.)} 29 | Подредете по асимптотично нарастване следните функции: 30 | \begin{align*} 31 | f_1(n) &= n! &f_2(n) &= \sum\limits_{k = 1}^{n} \ln(k) &f_3(n) &= \sum\limits_{k = 1}^{n^3} \frac{1}{k} &f_4(n) &= (\ln(n))^{\ln(n)} \\ 32 | f_5(n) &= 3^{n \sqrt{n}} &f_6(n) &= \sum\limits_{k = 1}^{n!} \frac{1}{k^2} &f_7(n) &= \ln(\ln(n)) &f_8(n) &= 2^{n^2}. 33 | \end{align*} 34 | При всяко сравнение на две функции се обосновете формално. 35 | \end{problem} 36 | } 37 | \newcommand\varianta{ 38 | 39 | \problems 40 | 41 | \remark 42 | 43 | \textit{\quotea} 44 | } 45 | 46 | \newcommand\variantb{ 47 | 48 | \problems 49 | 50 | \remark 51 | 52 | \textit{\quoteb} 53 | 54 | } 55 | 56 | \usepackage{tikz} 57 | \usepackage{pgf} 58 | \usetikzlibrary{shapes,arrows,automata} 59 | \usepackage{amsmath} 60 | \usepackage{amsthm} 61 | \usepackage{amssymb} 62 | \usepackage{amsfonts} 63 | \usepackage{enumerate} 64 | \usepackage{multirow} 65 | \usepackage{alltt} 66 | 67 | \theoremstyle{definition} 68 | \newtheorem{problem}{Задача} 69 | 70 | \setlength{\textwidth}{210mm} 71 | \setlength{\textheight}{297mm} 72 | \setlength{\topmargin}{0mm} 73 | \setlength{\voffset}{-1in} 74 | \setlength{\hoffset}{-1in} 75 | \setlength{\headheight}{0mm} 76 | \setlength{\headsep}{0mm} 77 | \parindent 0.5cm 78 | \oddsidemargin 0cm 79 | \evensidemargin 0cm 80 | 81 | \newcommand{\Nat}{\mathbb{N}} 82 | \newcommand{\Int}{\mathbb{Z}} 83 | \newcommand{\Real}{\mathbb{R}} 84 | \def\dotminus{\mathbin{\ooalign{\hss\raise1ex\hbox{.}\hss\cr 85 | \mathsurround=0pt$-$}}} 86 | 87 | \newcommand{\ExamHeader}{ 88 | \footnotesize% 89 | \begin{tabular}{|c|c|c|c|c|c|} 90 | \hline 91 | \multirow{2}{*}{Име:} & \multicolumn{3}{|c|}{\phantom{whyareyouwastingyourtime}} & \multirow{2}{*}{ФН:} & \phantom{readingthistext:D}\\ 92 | & \multicolumn{3}{|c|}{} & &\\ 93 | \hline 94 | \end{tabular} 95 | 96 | \vspace*{2mm} 97 | 98 | \centerline{\examname} 99 | \centerline{\examdate} 100 | } 101 | 102 | \newcommand\twominipages{ 103 | \noindent\hfill 104 | \begin{minipage}[t]{82mm} 105 | \ExamHeader 106 | \setcounter{problem}{0} 107 | 108 | \varianta 109 | \end{minipage} 110 | \hfill\hfill 111 | \begin{minipage}[t]{82mm} 112 | \ExamHeader 113 | \setcounter{problem}{0} 114 | 115 | \variantb 116 | \end{minipage} 117 | \hspace*{\fill} 118 | } 119 | 120 | 121 | \begin{document}\thispagestyle{empty} \setsize 122 | 123 | \vspace*{4mm} 124 | \twominipages 125 | 126 | \vspace*{8mm} 127 | \twominipages 128 | 129 | \vspace*{8mm} 130 | \twominipages 131 | 132 | % % \vfill 133 | % \vspace*{4mm} 134 | 135 | 136 | \end{document} 137 | -------------------------------------------------------------------------------- /graphs/problems.tex: -------------------------------------------------------------------------------- 1 | 2 | \section{Задачи} 3 | 4 | \begin{problem} 5 | Да се напише алгоритъм, който при подаден граф намира броя на свързаните компоненти. 6 | \end{problem} 7 | 8 | \begin{problem} 9 | Да се напише алгоритъм, който при подаден граф проверява дали той е цикличен. 10 | \end{problem} 11 | 12 | \begin{problem} 13 | Да се напише алгоритъм, който при подаден граф проверява дали той е дърво. 14 | \end{problem} 15 | 16 | \begin{problem} 17 | Да се напише алгоритъм, който при подаден (може и ориентиран) граф и два негови върха намира най-късия път между тях. 18 | \end{problem} 19 | 20 | \begin{problem} 21 | Да се напише алгоритъм, който при подаден (може и ориентиран) граф, два негови върха и дължина намира броят на пътища между тях със съответната дължина. 22 | \end{problem} 23 | 24 | \begin{problem} 25 | Да се напише алгоритъм, който при подаден граф проверява дали той е двуделен. 26 | \end{problem} 27 | 28 | \begin{problem} 29 | Трябва да изпълним задачи $1, \dots, n$. 30 | Обаче имаме допълнителни изисквания $R[1 \dots k]$ от вида $\opair{i, j}$, които казват \textit{``задача $i$ трябва да се изпълни преди задача $j$''}. 31 | Да се напише алгоритъм, който при подадени изисквания намира последователност от задачи, която удовлетворява тези изисквания. 32 | Ако няма такива, да се върне съобщение за грешка. 33 | \end{problem} 34 | 35 | \begin{problem} 36 | В някакъв град има $n$ души с етикети от $0$ до $n - 1$. 37 | Има слухове, че един от тези хора тайно е съдията на града. 38 | Ако такъв човек има, то тогава: 39 | \begin{itemize} 40 | \item съдията не вярва на никого; 41 | \item всеки вярва на съдията, освен самия него. 42 | \end{itemize} 43 | Масив на вярата за този град ще наричаме всеки масив $T[1 \dots k]$ от наредени двойки $\opair{i, j}$ (за $0 \leq i, j < n$), които казват \textit{``човекът с етикет $i$ вярва на човека с етикет $j$''}. 44 | Да се напише алгоритъм, който при подаден масив на вярата, връща етикета на съдията. 45 | Ако няма съдия, да се върне $-1$. 46 | \end{problem} 47 | 48 | \begin{problem} 49 | Да се напише алгоритъм, който при подаден ориентиран тегловен граф намира теглата на минималните пътища между всеки два върха. 50 | \end{problem} 51 | 52 | \begin{problem} 53 | Да се напише алгоритъм, който при подаден тегловен ориентиран ацикличен граф и негов начален връх намира най-късите пътища от този начален връх до всички достижими от него. 54 | \end{problem} 55 | 56 | \begin{problem} 57 | Да се напише алгоритъм, който при подаден свързан цикличен граф с ребро, чието премахване ще превърне графа в дърво, връща това ребро. 58 | \end{problem} 59 | 60 | \begin{problem} 61 | Нека е дадено крайно множество от променливи $X = \{ x_1, \dots x_k \}$. 62 | Уравнения над $X$ ще наричаме всички низове от вида $x_i = x_j$ и $x_i \neq x_j$ за някои $1 \leq i, j \leq k$. 63 | Да се напише алгоритъм, който при подадено множество от променливи $X$ и списък $E[1 \dots n]$ от уравнения над $X$ проверява дали системата, съставена от списъка с уравнения има решение. 64 | \end{problem} 65 | 66 | \newpage 67 | 68 | \begin{problem} 69 | Алис и Боб имат граф с върхове измежду $0$ и $n$ и три типа ребра: 70 | \begin{itemize} 71 | \item ребрата от тип $1$ могат да бъдат траверсирани само от Алис; 72 | \item ребрата от тип $2$ могат да бъдат траверсирани само от Боб; 73 | \item ребрата от тип $3$ могат да бъдат траверсирани и от двамата. 74 | \end{itemize} 75 | Да се напише алгоритъм, който при подаден масив от ребра $E[1 \dots k]$ т.е. тройки от типа $\opair{type, i, j}$ за някои $type \in \{1, 2, 3\}$ и $i, j \in \{ 1, \dots, n \}$ връща максималния брой ребра, които могат да се махнат, и графът пак да може да бъде обходен от Алис и Боб. 76 | Ако някой от двамата не може да обходи първоначалния граф, да се върне $-1$. 77 | \end{problem} 78 | 79 | \begin{problem} 80 | За всеки две точки $p_1 =(x_1, y_1)$ и $p_2 = (x_2, y_2)$ в $\Z \cross \Z$, цената на свързване на $p_1$ и $p_2$ ще бъде $|x_1 - x_2| + |y_1 - y_2|$. 81 | Да се напише алгоритъм, който при подаден масив $P[1 \dots n]$ от точки намира минималната обща цена на свързване, за която между всеки две точки от $P[1 \dots n]$ ще има път. 82 | \end{problem} -------------------------------------------------------------------------------- /sorting/problems.tex: -------------------------------------------------------------------------------- 1 | \section{Задачи} 2 | 3 | \begin{problem} 4 | Да се напише колкото се може по-бърз алгоритъм, който приема масив от различни цели числа $A[1 \dots n]$ с $n \geq 3$, за който има $1 < i < n$ такова, че $A[1 \dots i]$ е сортиран възходящо и $A[i \dots n]$ е сортиран низходящо, и връща това $i$. 5 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 6 | \end{problem} 7 | 8 | \begin{problem} 9 | Да се напише колкото се може по-бърз алгоритъм, който при подаден сортиран целочислен масив $A[1 \dots n]$ и число $t$, което се намира в масива, връща най-малкият и най-големият индекс, на които $t$ се намира в $A[1 \dots n]$. 10 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 11 | \end{problem} 12 | 13 | \begin{problem} 14 | Да се напише колкото се може по-бърз алгоритъм, който при подаден сортиран целочислен масив $A[1 \dots n]$ и числа $k \geq 2$ и $t$, връща дали има $1 \leq i_1 < i_2 < \dots < i_k \leq n$, за които: 15 | \[ 16 | A[i_1] + A[i_2] + \dots + A[i_k] = t. 17 | \] 18 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 19 | \end{problem} 20 | 21 | \begin{problem} 22 | За $a, b, x \in \mathbb{Z}$ казваме, че $a$ е по-близо от $b$ до $x$, ако: 23 | \[ 24 | |a - x| < |b - x| \lor (|a - x| = |b - x| \: \& \: a < b). 25 | \] 26 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots n]$, и връща най-близките $k$ на брой числа до $t$ в $A[1 \dots n]$. 27 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 28 | Може ли да се напише по-бърз алгоритъм при предположение че $A[1 \dots n]$ е сортиран? 29 | \end{problem} 30 | 31 | \begin{problem} 32 | Да се напише колкото се може по-бърз алгоритъм, който приема масив $A[1 \dots n]$, съставен от числата $0, 1$ и $2$, и го сортира. 33 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 34 | \end{problem} 35 | 36 | \begin{problem} 37 | Да се напише колкото се може по-бърз алгоритъм, който приема масив $I[1 \dots n]$, съставен от двойки числа, които представят някакъв затворен интервал от цели числа ($[a, b]$ за някои $a, b \in \mathbb{Z}$), и връща нов масив, в който са сляти всички интервали с непразно сечение. 38 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 39 | \end{problem} 40 | 41 | \begin{problem} 42 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots n]$, и връща нов масив от квадратите на $A[1 \dots n]$, който е сортиран. 43 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 44 | \end{problem} 45 | 46 | \begin{problem} 47 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots 2n]$, и връща: 48 | \[ 49 | \min\{ \max \{ A'[2i] + A'[2i - 1] \mid 1 \leq i \leq n \} \mid A'[1 \dots 2n] \text{ е пермутация на } A[1 \dots 2n] \}. 50 | \] 51 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 52 | \end{problem} 53 | 54 | \begin{problem} 55 | Да се напише колкото се може по-бърз алгоритъм, който приема два целочислени масива $A[1 \dots n]$ и $B[1 \dots n]$, и връща: 56 | \begin{align*} 57 | \min \{ \sum\limits_{i = 1}^n |A'[i] - B'[i]| \mid & A'[1 \dots n] \text{ е пермутация на } A[1 \dots n] \text{ и} \\ 58 | & B'[1 \dots n] \text{ е пермутация на } B[1 \dots n] \}. 59 | \end{align*} 60 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 61 | \end{problem} 62 | 63 | \begin{problem} 64 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots n]$ и го подрежда така, че всички отрицателни числа да са вляво от всички неотрицателни. 65 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 66 | \end{problem} 67 | 68 | \begin{problem} 69 | Статиите на даден учен са номерирани от $1$ до $n$. 70 | Индексът на Хирш или $h$-индексът за този учен ще наричаме най-голямото число $k$, за което поне $k$ негови статии са цитирани поне $k$ пъти. 71 | Да се състави алгоритъм, който приема масив $C[1 \dots n]$ от броя на цитиранията на статиите на този учен и връща неговият $h$-индекс. 72 | \end{problem} -------------------------------------------------------------------------------- /correctness/recursive.tex: -------------------------------------------------------------------------------- 1 | \section{Примери за рекурсивни алгоритми} 2 | 3 | Нека разгледаме следният алгоритъм: 4 | \lstinputlisting{algorithms/maximum.txt} 5 | 6 | Очевидно при параметър целочислен масив $A[1 \dots n]$, функцията $\mathfrak{M}(A[1 \dots n])$ връща $\max A[1 \dots n]$. 7 | Ще докажем това с индукция по $n$: 8 | \begin{itemize} 9 | \item В базата имаме, че $\mathfrak{M}(A[1 \dots n]) = -\infty = \max [] = \max A[1 \dots 0]$ където със $[]$ означаваме празният масив. 10 | \item За индуктивната стъпка имаме, че: 11 | \begin{align*} 12 | \mathfrak{M}(A[1 \dots n + 1]) & = \max(A[n + 1], \mathfrak{M}(A[1 \dots n])) \stackrel{\text{(ИП)}}{=} \max(A[n + 1], \max A[1 \dots n]) \\ 13 | & = \max A[1 \dots n + 1]. 14 | \end{align*} 15 | \end{itemize} 16 | 17 | Тук управляващият параметър на рекурсията $n$ винаги намалява с $1$, докато не стигне $0$, където ще приключи алгоритъмът. 18 | В по нататъчните разсъждения ще смятаме завършването на алгоритъма за очевидно. 19 | 20 | Сложността на алгоритъма се описва със рекурентното уравнение: 21 | \[ 22 | T(n) = T(n - 1) + 1 \text{ // базата няма да я пишем}. 23 | \] 24 | 25 | Директно се вижда, че $T(n) = \sum\limits_{i = 0}^n 1 = n + 1 \asymp n$. 26 | 27 | Да видим един малко по-сложен пример -- за бързо степенуване: 28 | \lstinputlisting{algorithms/exp-rec.txt} 29 | 30 | С пълна индукция относно $y$ ще покажем, че $\mathcal{P}(x, y) = x^y$: 31 | \begin{itemize} 32 | \item $\mathcal{P}(x, 0) = 1 = x^0 = 1$ // тук се уговаряме, че $0^0 = 1$. 33 | \item $\mathcal{P}(x, 2y + 1) = x \cdot \mathcal{P}(x, y) \cdot \mathcal{P}(x, y) \stackrel{\text{(ИП)}}{=} x \cdot x^y \cdot x^y = x^{2y + 1}$. 34 | \item $\mathcal{P}(x, 2y + 2) = \mathcal{P}(x, y + 1) \cdot \mathcal{P}(x, y + 1) \stackrel{\text{(ИП)}}{=} x^{y + 1} \cdot x^{y + 1} = x^{2y + 2}$. 35 | \end{itemize} 36 | 37 | Сложността на този алгоритъм може да се опише със следното рекурентно уравнение: 38 | \[ 39 | T(n) = T(\frac{n}{2}) + 1. 40 | \] 41 | От мастър-теоремата следва, че: 42 | \[ 43 | T(n) \asymp \log(n). 44 | \] 45 | 46 | \section{Трик за бързо пресмятане на членове на някои рекурентни редици} 47 | 48 | Нека вземем за пример редицата на Фибоначи: 49 | \begin{align*} 50 | & F(0) = 0 \\ 51 | & F(1) = 1 \\ 52 | & F(n + 2) = F(n + 1) + F(n) 53 | \end{align*} 54 | Човек може да забележи, че: 55 | \[ 56 | \underbrace{\begin{pmatrix} 57 | 1 & 1 \\ 58 | 1 & 0 59 | \end{pmatrix}}_{\mathfrak{F}^*} 60 | \cdot 61 | \begin{pmatrix} 62 | F(n + 1) \\ 63 | F(n) 64 | \end{pmatrix} 65 | = 66 | \begin{pmatrix} 67 | F(n + 2) \\ 68 | F(n + 1) 69 | \end{pmatrix}. 70 | \] 71 | След това с индукция лесно се показва, че: 72 | \[ 73 | (\mathfrak{F}^*)^n 74 | \cdot 75 | \begin{pmatrix} 76 | F(1) \\ 77 | F(0) 78 | \end{pmatrix} 79 | = 80 | \begin{pmatrix} 81 | F(n + 1) \\ 82 | F(n) 83 | \end{pmatrix}. 84 | \] 85 | 86 | \newpage 87 | 88 | За да сметнем $F(n)$, можем да направим бързо степенуване на матрицата, аналогична на тази с числата: 89 | \lstinputlisting{algorithms/fibonacci-fast.txt} 90 | 91 | Коректността и сложността на алгоритъма оставяме на читателя (напълно аналогично е на предния алгоритъм). 92 | 93 | В общия случай ще имаме рекурентно уравнение от вида: 94 | \[ 95 | T(n + k + 1) = a_k T(n + k) + \dots + a_1 T(n + 1) + a_0 T(n) \text{, където } a_0, \dots, a_k, k \text{ са константи}. 96 | \] 97 | 98 | Тогава отново с индукция човек лесно може да покаже, че: 99 | \[ 100 | \begin{pmatrix} 101 | a_k & a_{k - 1} & a_{k - 2} & \dots & a_2 & a_1 & a_0 \\ 102 | 1 & 0 & 0 & \dots & 0 & 0 & 0 \\ 103 | 0 & 1 & 0 & \dots & 0 & 0 & 0 \\ 104 | \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 105 | 0 & 0 & 0 & \dots & 0 & 1 & 0 \\ 106 | \end{pmatrix}^n 107 | \cdot 108 | \begin{pmatrix} 109 | T(k) \\ 110 | T(k - 1) \\ 111 | T(k - 2) \\ 112 | \vdots \\ 113 | T(0) 114 | \end{pmatrix} 115 | = 116 | \begin{pmatrix} 117 | T(n + k) \\ 118 | T(n + k - 1) \\ 119 | T(n + k - 2) \\ 120 | \vdots \\ 121 | T(n) 122 | \end{pmatrix}. 123 | \] 124 | 125 | \newpage -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Core latex/pdflatex auxiliary files: 2 | *.aux 3 | *.lof 4 | *.log 5 | *.lot 6 | *.fls 7 | *.out 8 | *.toc 9 | *.fmt 10 | *.fot 11 | *.cb 12 | *.cb2 13 | .*.lb 14 | 15 | ## Intermediate documents: 16 | *.dvi 17 | *.xdv 18 | *-converted-to.* 19 | # these rules might exclude image files for figures etc. 20 | # *.ps 21 | # *.eps 22 | # *.pdf 23 | 24 | ## Generated if empty string is given at "Please type another file name for output:" 25 | .pdf 26 | 27 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 28 | *.bbl 29 | *.bcf 30 | *.blg 31 | *-blx.aux 32 | *-blx.bib 33 | *.run.xml 34 | 35 | ## Build tool auxiliary files: 36 | *.fdb_latexmk 37 | *.synctex 38 | *.synctex(busy) 39 | *.synctex.gz 40 | *.synctex.gz(busy) 41 | *.pdfsync 42 | 43 | ## Build tool directories for auxiliary files 44 | # latexrun 45 | latex.out/ 46 | 47 | ## Auxiliary and intermediate files from other packages: 48 | # algorithms 49 | *.alg 50 | *.loa 51 | 52 | # achemso 53 | acs-*.bib 54 | 55 | # amsthm 56 | *.thm 57 | 58 | # beamer 59 | *.nav 60 | *.pre 61 | *.snm 62 | *.vrb 63 | 64 | # changes 65 | *.soc 66 | 67 | # comment 68 | *.cut 69 | 70 | # cprotect 71 | *.cpt 72 | 73 | # elsarticle (documentclass of Elsevier journals) 74 | *.spl 75 | 76 | # endnotes 77 | *.ent 78 | 79 | # fixme 80 | *.lox 81 | 82 | # feynmf/feynmp 83 | *.mf 84 | *.mp 85 | *.t[1-9] 86 | *.t[1-9][0-9] 87 | *.tfm 88 | 89 | #(r)(e)ledmac/(r)(e)ledpar 90 | *.end 91 | *.?end 92 | *.[1-9] 93 | *.[1-9][0-9] 94 | *.[1-9][0-9][0-9] 95 | *.[1-9]R 96 | *.[1-9][0-9]R 97 | *.[1-9][0-9][0-9]R 98 | *.eledsec[1-9] 99 | *.eledsec[1-9]R 100 | *.eledsec[1-9][0-9] 101 | *.eledsec[1-9][0-9]R 102 | *.eledsec[1-9][0-9][0-9] 103 | *.eledsec[1-9][0-9][0-9]R 104 | 105 | # glossaries 106 | *.acn 107 | *.acr 108 | *.glg 109 | *.glo 110 | *.gls 111 | *.glsdefs 112 | *.lzo 113 | *.lzs 114 | *.slg 115 | *.slo 116 | *.sls 117 | 118 | # uncomment this for glossaries-extra (will ignore makeindex's style files!) 119 | # *.ist 120 | 121 | # gnuplot 122 | *.gnuplot 123 | *.table 124 | 125 | # gnuplottex 126 | *-gnuplottex-* 127 | 128 | # gregoriotex 129 | *.gaux 130 | *.glog 131 | *.gtex 132 | 133 | # htlatex 134 | *.4ct 135 | *.4tc 136 | *.idv 137 | *.lg 138 | *.trc 139 | *.xref 140 | 141 | # hyperref 142 | *.brf 143 | 144 | # knitr 145 | *-concordance.tex 146 | # TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files 147 | # *.tikz 148 | *-tikzDictionary 149 | 150 | # listings 151 | *.lol 152 | 153 | # luatexja-ruby 154 | *.ltjruby 155 | 156 | # makeidx 157 | *.idx 158 | *.ilg 159 | *.ind 160 | 161 | # minitoc 162 | *.maf 163 | *.mlf 164 | *.mlt 165 | *.mtc[0-9]* 166 | *.slf[0-9]* 167 | *.slt[0-9]* 168 | *.stc[0-9]* 169 | 170 | # minted 171 | _minted* 172 | *.pyg 173 | 174 | # morewrites 175 | *.mw 176 | 177 | # newpax 178 | *.newpax 179 | 180 | # nomencl 181 | *.nlg 182 | *.nlo 183 | *.nls 184 | 185 | # pax 186 | *.pax 187 | 188 | # pdfpcnotes 189 | *.pdfpc 190 | 191 | # sagetex 192 | *.sagetex.sage 193 | *.sagetex.py 194 | *.sagetex.scmd 195 | 196 | # scrwfile 197 | *.wrt 198 | 199 | # svg 200 | svg-inkscape/ 201 | 202 | # sympy 203 | *.sout 204 | *.sympy 205 | sympy-plots-for-*.tex/ 206 | 207 | # pdfcomment 208 | *.upa 209 | *.upb 210 | 211 | # pythontex 212 | *.pytxcode 213 | pythontex-files-*/ 214 | 215 | # tcolorbox 216 | *.listing 217 | 218 | # thmtools 219 | *.loe 220 | 221 | # TikZ & PGF 222 | *.dpth 223 | *.md5 224 | *.auxlock 225 | 226 | # titletoc 227 | *.ptc 228 | 229 | # todonotes 230 | *.tdo 231 | 232 | # vhistory 233 | *.hst 234 | *.ver 235 | 236 | # easy-todo 237 | *.lod 238 | 239 | # xcolor 240 | *.xcp 241 | 242 | # xmpincl 243 | *.xmpi 244 | 245 | # xindy 246 | *.xdy 247 | 248 | # xypic precompiled matrices and outlines 249 | *.xyc 250 | *.xyd 251 | 252 | # endfloat 253 | *.ttt 254 | *.fff 255 | 256 | # Latexian 257 | TSWLatexianTemp* 258 | 259 | ## Editors: 260 | # WinEdt 261 | *.bak 262 | *.sav 263 | 264 | # Texpad 265 | .texpadtmp 266 | 267 | # LyX 268 | *.lyx~ 269 | 270 | # Kile 271 | *.backup 272 | 273 | # gummi 274 | .*.swp 275 | 276 | # KBibTeX 277 | *~[0-9]* 278 | 279 | # TeXnicCenter 280 | *.tps 281 | 282 | # auto folder when using emacs and auctex 283 | ./auto/* 284 | *.el 285 | 286 | # expex forward references with \gathertags 287 | *-tags.tex 288 | 289 | # standalone packages 290 | *.sta 291 | 292 | # Makeindex log files 293 | *.lpz 294 | 295 | # xwatermark package 296 | *.xwm 297 | 298 | # REVTeX puts footnotes in the bibliography by default, unless the nofootinbib 299 | # option is specified. Footnotes are the stored in a file with suffix Notes.bib. 300 | # Uncomment the next line to have this generated file ignored. 301 | #*Notes.bib 302 | -------------------------------------------------------------------------------- /sorting/applications.tex: -------------------------------------------------------------------------------- 1 | \section{Два алгоритъма} 2 | 3 | Ще започнем с два често срещани алгоритъма, които се възползват от това, че данните идват сортирани: 4 | \begin{itemize} 5 | \item двоично търсене; 6 | \item алгоритъма за задачата \textbf{2SUM}. 7 | \end{itemize} 8 | 9 | Нека започнем с алгоритъма за двоично търсене: 10 | \lstinputlisting{algorithms/binary-search.txt} 11 | 12 | При подаден сортиран целочислен масив $A[1 \dots n]$ и цяло число $v$, функцията $\mathtt{BinarySearch}(A[1 \dots n], v)$ ще върне индекс на $A[1 \dots n]$, в който се намира $v$, ако има такъв, иначе ще върне $-1$. 13 | 14 | \begin{invariant} 15 | При всяко достигане на проверката за край на цикъла на ред $6$ имаме, че стойността $v$ не се намира измежду двата масива $A[1 \dots l - 1]$ и $A[r + 1 \dots n]$. 16 | \end{invariant} 17 | 18 | \begin{proof} 19 | \phantom{1} 20 | 21 | \textbf{База.} 22 | При първото достигане имаме, че $l = 1$ и $r = n$. 23 | Наистина $v$ не се намира измежду двата масива $A[1 \dots 1 - 1]$ и $A[n + 1 \dots n]$. 24 | 25 | \textbf{Поддръжка.} 26 | Нека твърдението е изпълнено за някое непоследно достигане на проверката за край на цикъла. 27 | Тогава $v$ не се намира измежду $A[1 \dots l - 1]$ и $A[r + 1 \dots n]$, и понеже достигането е непоследно, $l \leq r$. 28 | Тогава $m = \Bigl\lfloor \frac{l + r}{2} \Bigr\rfloor$, откъдето ${l \leq m \leq r}$. 29 | Трябва да разгледаме следните три случая: 30 | \begin{itemize} 31 | \item[1 сл.] $A[m] = v$ -- това няма как да е изпълнено понеже достигането е нефинално; 32 | \item[2 сл.] $A[m] < v$ -- понеже $A[1 \dots n]$ е сортиран, няма как $v$ да се намира измежду $A[1 \dots m]$, откъдето $v$ не се намира измежду $A[1 \dots \underbrace{\mathtt{m + 1}}_{l_{new}} - 1]$ и $A[r + 1 \dots n]$; 33 | \item[3 сл.] $A[m] > v$ -- напълно дуален на 2 сл. 34 | \end{itemize} 35 | 36 | \textbf{Терминация.} 37 | От цикъла винаги ще излезем, защото или ще открием $v$, или величината $r - l$ ще намалява, докато не стане отрицателна. 38 | Излизането става по два начина: 39 | \begin{itemize} 40 | \item Ако не е изпълнено условието на ред $6$ т.е. $l > r$, то тогава $l - 1 \geq r$ и понеже $v$ не се намира измежду $A[1 \dots l - 1]$ и $A[r + 1 \dots n]$, $v$ не се намира във $A[1 \dots n]$. 41 | Накрая алгоритъмът ще върне $-1$, което наистина е желания резултат. 42 | \item Ако е изпълнено е условието на ред $9$ т.е. $A[m] = v$, то тогава алгоритъмът коректно връща $m$. 43 | \end{itemize} 44 | \end{proof} 45 | 46 | Сега ще разгледаме алгоритъм за задачата \textbf{2SUM}. 47 | 48 | Ще искаме алгоритъм, който при подаден сортиран целочислен масив $A[1 \dots n]$ и цяло число $t$ разпознава дали има $1 \leq i < j \leq n$, за които: 49 | \[ 50 | A[i] + A[j] = t. 51 | \] 52 | Такива двойки $(A[i], A[j])$ ще наричаме \textit{диади}. 53 | 54 | \newpage 55 | 56 | Алгоритъмът е следния: 57 | \lstinputlisting{algorithms/two-sum.txt} 58 | 59 | \begin{invariant} 60 | При всяко достигане на проверката за край на цикъла на ред $5$, всички диади са в $A[l \dots r]$. 61 | \end{invariant} 62 | 63 | \begin{proof} 64 | \phantom{1} 65 | 66 | \textbf{База.} 67 | При първото достигане имаме, че $l = 1$ и $r = n$. 68 | Тогава твърдението е тривиално изпълнено. 69 | 70 | \textbf{Поддръжка.} 71 | Нека твърдението е изпълнено за някое непоследно достигане на проверката за край на цикъла. 72 | Тогава всички диади са в $A[l \dots r]$ и $l < r$. 73 | Разглеждаме три случая: 74 | \begin{itemize} 75 | \item[1 сл.] $A[l] + A[r] = t$ -- това няма как да е изпълнено понеже достигането е нефинално; 76 | \item[2 сл.] $A[l] + A[r] < t$ -- тогава понеже $A[1 \dots n]$ е сортиран, за всяко $l \leq i \leq r$ имаме, че $A[l] + A[i] \leq A[l] + A[r] < t$, което означава, че $l$ не участва в никоя диада във $A[l \dots r]$, следователно всички диади се намират в $A[\underbrace{l + 1}_{l_{new}} \dots r]$; 77 | \item[3 сл.] $A[l] + A[r] > t$ -- напълно дуален на 2 сл. 78 | \end{itemize} 79 | 80 | \textbf{Терминация.} 81 | От цикъла винаги ще излезем, защото или ще открием $v$, или величината $r - l$ ще намалява, докато не стане $0$. 82 | Излизането става по два начина: 83 | \begin{itemize} 84 | \item Ако не е изпълнено условието на ред $4$ т.е. $l \geq r$, то тогава няма диади в $A[1 \dots n]$, и алгоритъмът коректно ще върне $\F$. 85 | \item Ако е изпълнено условието на ред $5$ т.е. $A[l] + A[r] = t$, то тогава алгоритъмът връща $\T$ т.е. точно това, което искаме. 86 | \end{itemize} 87 | \end{proof} 88 | 89 | Двата алгоритъма са доста подобни, използват една често срещана техника за търсене в дадено множество от елементи. 90 | Търсенето започва с цялото множество и то постепенно се смалява. 91 | Разбира се, тук разгледаните алгоритми имат разлика в сложността, поради разликата в стесняването: 92 | \begin{itemize} 93 | \item първият алгоритъм има сложност $O(\log(n))$, понеже разликата между $r$ и $l$ винаги намалява двойно; 94 | \item вторият алгоритъм има сложност $O(n)$, понеже разликата между $r$ и $l$ винаги намалява с единица. 95 | \end{itemize} -------------------------------------------------------------------------------- /correctness/more-examples.tex: -------------------------------------------------------------------------------- 1 | \section{Още примери} 2 | 3 | Искаме да напишем алгоритъм, който при подадена двойка от естествени числа $\opair{a, b}$, където $b \leq a$, да върне $\gcd(a, b)$ т.е. това число $d \in \mathbb{N}$, за което: 4 | \begin{itemize} 5 | \item $d \mid a$ и $d \mid b$; 6 | \item ако $d_1 \mid a$ и $d_1 \mid b$, то $d_1 \mid d$. 7 | \end{itemize} 8 | 9 | За целта ще покажем следното: 10 | \begin{claim}\thlabel{gcd-claim} 11 | За всяко $a, b, q, r \in \mathbb{N}$, където $0 < b \leq a, \: a = bq + r$ и $r \in \{ 0, \dots, b - 1 \}$, е изпълнено, че: 12 | \[ 13 | \gcd(a, b) = \gcd(b, r) 14 | \] 15 | \end{claim} 16 | 17 | \begin{proof} 18 | Първо имаме, че $\gcd(a, b) \mid a$ и $\gcd(a, b) \mid b$, откъдето понеже $a = bq + r$, имаме $\gcd(a, b) \mid r$. 19 | Тогава $\gcd(a, b) \mid \gcd(b, r)$. 20 | От друга страна $\gcd(b, r) \mid b$ и $\gcd(b, r) \mid r$, откъдето $\gcd(b, r) \mid bq + r = a$. 21 | Така $\gcd(b, r) \mid \gcd(a, b)$. 22 | Накрая можем да заключим $\gcd(a, b) = \gcd(b, r)$ от това, че $\gcd(a, b) \mid \gcd(b, r)$ и $\gcd(b, r) \mid \gcd(a, b)$. 23 | \end{proof} 24 | 25 | На базата на това наблюдение се получава следният алгоритъм (кръстен на Евклид): 26 | \lstinputlisting{algorithms/euclid.txt} 27 | 28 | Ще покажем с индукция относно $b$, че за всяко $a \geq b$, е изпълнено, че: 29 | \[ 30 | \mathtt{Euclid}(a, b) = \gcd(a, b) 31 | \] 32 | \begin{itemize} 33 | \item В базовия случай имаме $\mathtt{Euclid}(a, 0) = a = \gcd(a, 0)$. 34 | \item Нека $b > 0$ и $a = bq + r$ за някои $r \in \{ 0, \dots, b - 1 \}$ и $q$ и нека твърдението е изпълнено за всяко $b' < b$. 35 | Тогава: 36 | \[ 37 | \mathtt{Euclid}(a, b) = \mathtt{Euclid}(b, r) \stackrel{\text{(ИП)}}{=} \gcd(b, r) \stackrel{\text{\thref{gcd-claim}}}{=} \gcd(a, b). 38 | \] 39 | \end{itemize} 40 | 41 | Алгоритъмът терминира, понеже управляващият параметър $\mathtt{b}$ винаги намаля, докато не стане $\mathtt{0}$. 42 | На пръсти ще покажем, че алгоритъмът има сложност по време и памет $O(\log(a))$. 43 | Нека видим, че ако $a > b$, то $\bmod(a, b) < \frac{a}{2}$: 44 | \begin{itemize} 45 | \item[1 сл.] ако $b \leq \frac{a}{2}$, то $\bmod(a, b) < \frac{a}{2}$ и сме готови; 46 | \item[2 сл.] ако пък $b > \frac{a}{2}$, то тогава $a - b < \frac{a}{2}$, откъдето $\bmod(a, b) = \bmod(a - b, b) = a - b < \frac{a}{2}$. 47 | \end{itemize} 48 | Тогава през на всеки две стъпки на алгоритъма от вход $\opair{a, b}$, отиваме до вход $\opair{\bmod(a, b), \bmod(b, \bmod(a, b))}$ и левият аргумент става поне два пъти по-малък. 49 | Тъй като левият аргумент е горна граница за десния, то той също ще намаля рязко. 50 | Дълбочината на дървото на рекурсията зависи само от броя на рекурсивните извиквания. 51 | Понеже те са логаритмично много и всяко едно от тях заема константна памет, сложността по памет е също $O(\log(a))$. 52 | 53 | Вторият пример е задача за числа, скрита в задача за масиви: 54 | 55 | Имаме един масив $A[1 \dots n]$, който съдържа всички числа от $1$ до $n$, с изключение на едно от тях, което е заместено с друго число от $1$ до $n$. 56 | Искаме да напишем колкото се може по-бърз алгоритъм, който при вход такъв масив $A[1 \dots n]$ връща наредената двойка $\opair{d, m}$ от дупликата и липсващото число. 57 | Оказва се, че тази задача може да се реши за линейно време и константна памет. 58 | За целта трябва да се забележат следните факти: 59 | \begin{itemize} 60 | \item $d - m = \sum\limits_{i = 1}^n A[i] - \sum\limits_{i = 1}^n i = \sum\limits_{i = 1}^n A[i] - \frac{n(n + 1)}{2} =: B$; 61 | \item $(d + m)(d - m) = d^2 - m^2 = \sum\limits_{i = 1}^n A[i]^2 - \sum\limits_{i = 1}^n i^2 = \sum\limits_{i = 1}^n A[i]^2 - \frac{n(n + 1)(2n + 1)}{6} =: C$. 62 | \end{itemize} 63 | 64 | От тях получаваме следната система от уравнения: 65 | \begin{center} 66 | \begin{tabular}{|l} 67 | $d - m = B$ \\ 68 | $d + m = \frac{C}{B}$ 69 | \end{tabular} 70 | \end{center} 71 | 72 | Най-сложното, което трябва да направим, е да пресметнем $B$ и $C$: 73 | \lstinputlisting{algorithms/find-missing-and-duplicate.txt} 74 | 75 | Нека за пълнота покажем следното: 76 | \begin{claim} 77 | За всяко $n \in \N$ е изпълнено, че: 78 | \[ 79 | \sum\limits_{i = 0}^n i = \frac{n(n + 1)}{2} \text{ и } \sum\limits_{i = 0}^n i^2 = \frac{n(n + 1)(2n + 1)}{6} 80 | \] 81 | \end{claim} 82 | 83 | \begin{proof} 84 | Правим индукция по $n$: 85 | \begin{itemize} 86 | \item В базата имаме, че $\sum\limits_{i = 0}^0 i = 0 = \frac{0(0 + 1)}{2}$ и $\sum\limits_{i = 0}^0 i^2 = \frac{0(0 + 1)(2 \cdot 0 + 1)}{6}$. 87 | \item В стъпката имаме, че: 88 | \[ 89 | \sum\limits_{i = 0}^{n + 1} i = \sum\limits_{i = 0}^{n} i + (n + 1) \stackrel{\text{(ИП)}}{=} \frac{n(n + 1)}{2} + (n + 1) = \frac{n(n + 1)}{2} + \frac{2(n + 1)}{2} = \frac{(n + 1)(n + 2)}{2} \text{, и} 90 | \] 91 | \begin{align*} 92 | \sum\limits_{i = 0}^{n + 1} i^2 & = \sum\limits_{i = 0}^{n} i^2 + (n + 1)^2 \stackrel{\text{(ИП)}}{=} \frac{n(n + 1)(2n + 1)}{6} + (n + 1)^2 \\ 93 | & = \frac{n(n + 1)(2n + 1)}{6} + \frac{6(n + 1)^2}{6} \\ 94 | & = \frac{(n + 1)(2n^2 + n + 6n + 6)}{6} = \frac{(n + 1)(n + 2)(2(n + 1) + 1)}{6}. 95 | \end{align*} 96 | \end{itemize} 97 | \end{proof} -------------------------------------------------------------------------------- /tests/05.06.2024/solutions.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[T2A]{fontenc} 4 | \usepackage[english, bulgarian]{babel} 5 | \usepackage{amssymb} 6 | \usepackage{hyperref, fancyhdr, lastpage, fancyvrb, tcolorbox, titlesec} 7 | \usepackage{array, tabularx, colortbl} 8 | \usepackage{tikz} 9 | \usepackage{venndiagram} 10 | \usepackage{amsthm, bm} 11 | \usepackage{relsize} 12 | \usepackage{amsmath,physics} 13 | \usepackage{mathtools} 14 | \usepackage{subcaption} 15 | \usepackage{theoremref} 16 | \usepackage{circuitikz} 17 | \usepackage[a5paper, left=0.50in, right=0.50in, top=0.50in, bottom=0.50in]{geometry} 18 | \usepackage{stmaryrd} 19 | \usepackage{forest} 20 | \usepackage{cancel} 21 | \usepackage{minted} 22 | \usepackage{titling} 23 | \usetikzlibrary{automata, arrows, positioning, shapes} 24 | \useforestlibrary{linguistics} 25 | \pagenumbering{gobble} 26 | \ExplSyntaxOn 27 | \NewDocumentCommand{\opair}{m} 28 | { 29 | \langle\mspace{2mu} 30 | \clist_set:Nn \l_tmpa_clist { #1 } 31 | \clist_use:Nn \l_tmpa_clist {,\mspace{3mu plus 1mu minus 1mu}\allowbreak} 32 | \mspace{2mu}\rangle 33 | } 34 | \ExplSyntaxOff 35 | 36 | \hypersetup{ 37 | colorlinks=true, 38 | linktoc=all, 39 | linkcolor=blue 40 | } 41 | 42 | \setlength\parindent{0pt} 43 | 44 | \newtheorem{problem}{Задача} 45 | \theoremstyle{definition} 46 | \newtheorem*{solution}{Решение} 47 | 48 | \begin{document} 49 | 50 | \begin{center} 51 | \Large{Решения на задачите от второто малко контролно по ДАА на група 5, проведено на 05.06.2024 г.} 52 | \end{center} 53 | 54 | \vspace*{4mm} 55 | 56 | \begin{problem} {\bf (60 т.)} 57 | \textit{Хубаво} число ще наричаме всяко естествено число от вида $2^a 3^b$ за някои $a, b \in \mathbb{N}$. 58 | Да се състави \textbf{итеративен} алгоритъм, който при подадено $n \in \mathbb{N}$ връща $(n + 1)$-вото по големина хубаво число. 59 | Алгоритъмът \textbf{трябва} да е съставен по схемата \textbf{динамично програмиране} и да има сложност по време $\Theta(n)$. 60 | Няма нужда да се прави доказателство за коректност, достатъчно е да се напише правилен инвариант. 61 | \end{problem} 62 | 63 | \begin{solution} 64 | Следният алгоритъм решава задачата: 65 | \begin{Verbatim}[frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}] 66 | Beautiful(Nat n): 67 | Аrray(Nat) B[0..n] 68 | B[0] $\leftarrow$ 1 69 | Nat two_idx $\leftarrow$ 0 70 | Nat three_idx $\leftarrow$ 0 71 | 72 | for i $\leftarrow$ 1 to n: 73 | two_candidate $\leftarrow$ 2 * B[two_idx] 74 | three_candidate $\leftarrow$ 3 * B[three_idx] 75 | B[i] $\leftarrow$ min(two_candidate, three_candidate) 76 | 77 | if B[i] = two_candidate: 78 | two_idx $\leftarrow$ two_idx + 1 79 | if B[i] = three_candidate: 80 | three_idx $\leftarrow$ three_idx + 1 81 | 82 | return B[n] 83 | \end{Verbatim} 84 | 85 | \textbf{Инвариант.} При всяко достигане на условието за край на цикъла на ред 7: 86 | \begin{itemize} 87 | \item {\tt B[j]} е {\tt (j+1)}-вото хубаво число за всяко {\tt j} между {\tt 0} и {\tt i-1} включително; 88 | \item {\tt B[two\_idx]} е най-малкото хубаво число {\tt k}, за което {\tt 2k} е извън масива {\tt B[0..i-1]}; 89 | \item {\tt B[three\_idx]} е най-малкото хубаво число {\tt k}, за което {\tt 3k} е извън масива {\tt B[0..i-1]}. 90 | \end{itemize} 91 | \end{solution} 92 | 93 | \textbf{Критерии за оценяване:} 94 | \begin{itemize} 95 | \item за правилен алгоритъм -- 30 точки; 96 | \item за правилно формулиран инвариант -- 30 точки. 97 | \end{itemize} 98 | 99 | \pagebreak 100 | 101 | \begin{problem} {\bf (60 т.)} 102 | Нека разгледаме задачата {\normalfont TABLE-SUM:} 103 | \vspace*{2mm} 104 | 105 | \hspace*{4mm} \textbf{Вход:} Таблица от естествени числа $T[1 \dots n, 1 \dots m]$ и $t \in \mathbb{N}$. 106 | 107 | \hspace*{4mm} \textbf{Въпрос:} Има ли $1 \leq i_1, \dots, i_n \leq m$, за които $\sum\limits_{k = 1}^n T[k, i_k] = t$? 108 | 109 | \vspace*{1mm} 110 | Докажете формално, че {\normalfont TABLE-SUM} е \textbf{NP}-пълна задача. 111 | \end{problem} 112 | 113 | \begin{solution} 114 | При подадена таблица $T[1 \dots n, 1 \dots m]$ и сертификат $C[1 \dots n]$, представящ индекси $1 \leq i_1, \dots, i_n \leq m$, можем за време $\Theta(n)$ да проверим дали е изпълнено, че: 115 | \[ 116 | \sum\limits_{k = 1}^n T[k, i_k] = t. 117 | \] 118 | Така задачата TABLE-SUM е в класа \textbf{NP}. 119 | 120 | Също така много лесно можем за полиномиално време да сведем задачата SUBSET-SUM към задачата TABLE-SUM. 121 | При подаден вход масив $A[1 \dots n]$ и число $t \in \mathbb{N}$ за време $\Theta(n)$ можем да построим таблица $T[1 \dots n, 1 \dots 2]$, където $T[i, 1] = A[i]$ и $T[i, 2] = 0$. 122 | Тогава са изпълнени следните твърдения: 123 | \begin{enumerate} 124 | \item Ако има подредица на $A[1 \dots n]$ със сума на елементите $t$, то тогава за всяко $1 \leq j \leq n$ ще дефинираме $i_j$ да бъде $1$ т.с.т.к. $A[i]$ участва в съответната подредица. 125 | Индексите $1 \leq i_1, \dots, i_n \leq 2$ имат желаното свойство. 126 | \item Ако $1 \leq i_1, \dots, i_n \leq 2$ са индекси с желаното свойство, то взимайки тези $1 \leq j \leq n$, за които $i_j = 1$, ще получим подредица на $A[1 \dots n]$, елементите на която се сумират до $t$. 127 | \end{enumerate} 128 | С това получихме, че задачата TABLE-SUM е \textbf{NP}-трудна, и понеже принадлежи на класа \textbf{NP}, тя е \textbf{NP}-пълна. 129 | \end{solution} 130 | 131 | \textbf{Критерии за оценяване:} 132 | \begin{itemize} 133 | \item за обосновка, че TABLE-SUM е в класа \textbf{NP} -- 30 точки; 134 | \item за обосновка, че TABLE-SUM е $\textbf{NP}$-трудна задача -- 30 точки. 135 | \end{itemize} 136 | 137 | \end{document} -------------------------------------------------------------------------------- /tests/10.04.2024/solutions.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[T2A]{fontenc} 4 | \usepackage[english, bulgarian]{babel} 5 | \usepackage{amssymb} 6 | \usepackage{hyperref, fancyhdr, lastpage, fancyvrb, tcolorbox, titlesec} 7 | \usepackage{array, tabularx, colortbl} 8 | \usepackage{tikz} 9 | \usepackage{venndiagram} 10 | \usepackage{amsthm, bm} 11 | \usepackage{relsize} 12 | \usepackage{amsmath,physics} 13 | \usepackage{mathtools} 14 | \usepackage{subcaption} 15 | \usepackage{theoremref} 16 | \usepackage{circuitikz} 17 | \usepackage[a5paper, left=0.50in, right=0.50in, top=0.50in, bottom=0.50in]{geometry} 18 | \usepackage{stmaryrd} 19 | \usepackage{forest} 20 | \usepackage{cancel} 21 | \usepackage{minted} 22 | \usepackage{titling} 23 | \usetikzlibrary{automata, arrows, positioning, shapes} 24 | \useforestlibrary{linguistics} 25 | \pagenumbering{gobble} 26 | \ExplSyntaxOn 27 | \NewDocumentCommand{\opair}{m} 28 | { 29 | \langle\mspace{2mu} 30 | \clist_set:Nn \l_tmpa_clist { #1 } 31 | \clist_use:Nn \l_tmpa_clist {,\mspace{3mu plus 1mu minus 1mu}\allowbreak} 32 | \mspace{2mu}\rangle 33 | } 34 | \ExplSyntaxOff 35 | 36 | \hypersetup{ 37 | colorlinks=true, 38 | linktoc=all, 39 | linkcolor=blue 40 | } 41 | 42 | \setlength\parindent{0pt} 43 | 44 | \newtheorem{problem}{Задача} 45 | \theoremstyle{definition} 46 | \newtheorem*{solution}{Решение} 47 | 48 | \begin{document} 49 | 50 | \begin{center} 51 | \Large{Решения на задачите от първото малко контролно по ДАА на група 5, проведено на 10.04.2024 г.} 52 | \end{center} 53 | 54 | \vspace*{4mm} 55 | 56 | \begin{problem} {\bf (50 т.)} 57 | Дефинираме \textit{вълнист масив} индуктивно: 58 | \begin{itemize} 59 | \item Всеки едноелементен масив е вълнист. 60 | \item Масив $A[1 \dots n]$ (където $n > 1$) е вълнист, ако 61 | \begin{enumerate} 62 | \item масивът $A[1 \dots \lfloor \frac{n}{2} \rfloor]$ е сортиран, а 63 | \item масивът $A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$ е вълнист. 64 | \end{enumerate} 65 | \end{itemize} 66 | Да се състави алгоритъм с линейна сложност по време, който приема вълнист масив и го сортира. 67 | Коректността на алгоритъма да се обоснове формално и да се изследва сложността по време. 68 | \end{problem} 69 | 70 | \begin{solution} 71 | Решението е проста модификация на алгоритъма за сортиране чрез сливане: 72 | \begin{Verbatim}[frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}] 73 | Sort(A[1..n] - вълнист масив): 74 | if n = 1: 75 | return 76 | 77 | Sort(A[$\mathtt{\lfloor\frac{n}{2}\rfloor}$ + 1..n]) 78 | Merge(A[1..$\mathtt{\lfloor\frac{n}{2}\rfloor}$], A[$\mathtt{\lfloor\frac{n}{2}\rfloor}$ + 1..n]) 79 | \end{Verbatim} 80 | Тук $\mathtt{Merge}$ е алгоритъмът за сливане на сортирани масиви, който е изучаван на лекции. 81 | Ще покажем коректността на $\mathtt{Sort}$ с пълна индукция по $\mathtt{n}$: 82 | \begin{itemize} 83 | \item ако $\mathtt{n = 1}$, то тогава $\mathtt{A[1..n]}$ е сортиран и алгоритъмът коректно веднага ще приключи работа; 84 | \item ако $\mathtt{n > 1}$, то тогава на ред $5$ по ИП ще сортираме вълнистия масив $\mathtt{A[\lfloor\frac{n}{2}\rfloor + 1..n]}$ и с извикването на ред $6$ ще слеем масивите $\mathtt{A[1..\lfloor\frac{n}{2}\rfloor]}$ и $\mathtt{A[\lfloor\frac{n}{2}\rfloor + 1..n]}$, което ще сортира $\mathtt{A[1..n]}$. 85 | \end{itemize} 86 | Времевата сложност се описва със следното рекурентно уравнение: 87 | \begin{center} 88 | $T(n) = T(\frac{n}{2}) + \Theta(n)$ (заради извикванията $\mathtt{Sort}$ и $\mathtt{Merge}$). 89 | \end{center} 90 | По мастър-теоремата излиза, че $T(n) \asymp n$. 91 | \end{solution} 92 | 93 | \textbf{Критерии за оценяване:} 94 | \begin{itemize} 95 | \item за правилен алгоритъм -- $20$ точки; 96 | \item за доказателство на коректността на алгоритъма -- $20$ точки; 97 | \item за обосновка на сложността по време -- $10$ точки. 98 | \end{itemize} 99 | 100 | \pagebreak 101 | 102 | \begin{problem} {\bf (70 т.)} 103 | Подредете по асимптотично нарастване следните \\ функции: 104 | \begin{align*} 105 | f_1(n) & = n! & f_2(n) & = \sum\limits_{k = 1}^{n} \ln(k) & f_3(n) & = \sum\limits_{k = 1}^{n^3} \frac{1}{k} & f_4(n) & = (\ln(n))^{\ln(n)} \\ 106 | f_5(n) & = 3^{n \sqrt{n}} & f_6(n) & = \sum\limits_{k = 1}^{n!} \frac{1}{k^2} & f_7(n) & = \ln(\ln(n)) & f_8(n) & = 2^{n^2}. 107 | \end{align*} 108 | При всяко сравнение на две функции се обосновете формално. 109 | \end{problem} 110 | 111 | \begin{solution} 112 | Окончателната наредба е следната: 113 | \[ 114 | f_6(n) \stackrel{(1)}{\prec} f_7(n) \stackrel{(2)}{\prec} f_3(n) \stackrel{(3)}{\prec} f_2(n) \stackrel{(4)}{\prec} f_4(n) \stackrel{(5)}{\prec} f_1(n) \stackrel{(6)}{\prec} f_5(n) \stackrel{(7)}{\prec} f_8(n). 115 | \] 116 | Доказателство: 117 | \begin{itemize} 118 | \item[$(1)$] $f_7(n) = \ln(\ln(n)) \succ 1 \asymp \sum\limits_{k = 1}^{n!} \frac{1}{k^2} = f_6(n)$. 119 | \item[$(2)$] $f_3(n) = \sum\limits_{k = 1}^{n^3} \frac{1}{k} \asymp \ln(n^3) = 3 \ln(n) \asymp \ln(n) \succ f_7(n)$. 120 | \item[$(3)$] $f_2(n) = \sum\limits_{k = 1}^{n} \ln(k) = \ln(n!) \asymp n \ln(n) \succ \ln(n) \asymp f_3(n)$. 121 | \item[$(4)$] $f_4(n) = (\ln(n))^{\ln(n)} = n^{\ln(\ln(n))} \succ n \ln(n) \asymp f_2(n)$. 122 | \item[$(5)$] $\ln(f_1(n)) \asymp n \ln(n) \succ \ln(n) \ln(\ln(n)) \asymp \ln(f_4(n))$, откъдето $f_1(n) \succ f_4(n)$. 123 | \item[$(6)$] $\ln(f_5(n)) \asymp n \sqrt{n} \succ n \ln(n) \asymp \ln(f_1(n))$, откъдето $f_5(n) \succ f_1(n)$. 124 | \item[$(7)$] $\ln(f_8(n)) \asymp n^2 \succ n \sqrt{n} \asymp \ln(f_5(n))$, откъдето $f_8(n) \succ f_5(n)$. 125 | \end{itemize} 126 | \end{solution} 127 | 128 | \textbf{Критерии за оценяване:} 129 | \begin{itemize} 130 | \item за всяко правилно сравнение от $(1)$ до $(7)$ -- по 5 точки; 131 | \item за обосновка на всяко сравнение от $(1)$ до $(7)$ -- по 5 точки. 132 | \end{itemize} 133 | 134 | \end{document} -------------------------------------------------------------------------------- /correctness/problems.tex: -------------------------------------------------------------------------------- 1 | \section{Задачи} 2 | 3 | \begin{problem} 4 | Даден е следният алгоритъм: 5 | \lstinputlisting{algorithms/anon-alg1.txt} 6 | 7 | \begin{enumerate} 8 | \item Какво връща той? Отговорът да се обоснове. 9 | \item Каква е неговата сложност по време и памет? 10 | \end{enumerate} 11 | \end{problem} 12 | 13 | \newpage 14 | 15 | \begin{problem} 16 | Даден е следният алгоритъм: 17 | \lstinputlisting{algorithms/fib-iter-linear.txt} 18 | 19 | Да се докаже, че $\mathfrak{F}(n)$ връща $n$-тото число на Фибоначи. 20 | \end{problem} 21 | 22 | \begin{problem} 23 | Даден е следният: алгоритъм: 24 | \lstinputlisting{algorithms/mult.txt} 25 | 26 | Да се докаже, че при вход $n \times n$ целочислени матрици $A, B$ и $C$ функцията $\mathtt{Mult}(A, B, C)$ записва в $C$ произведението на $A$ и $B$. 27 | Да се намери сложността му по време и памет. 28 | \end{problem} 29 | 30 | \begin{problem} 31 | Да се напише колкото се може по-бърз алгоритъм, който при подадено крайно множество от точки $P \subseteq \mathbb{Z} \cross \mathbb{Z}$, намира 32 | \[ 33 | \max \{ |x_1 - x_2| + |y_1 - y_2| \mid (x_1, y_1), (x_2, y_2) \in P \}. 34 | \] 35 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 36 | \end{problem} 37 | 38 | \begin{problem} 39 | Даден е следният алгоритъм: 40 | \lstinputlisting{algorithms/num-slopes.txt} 41 | 42 | Какво връща $\mathtt{NumSlopes}(A[1 \dots n])$? 43 | Отговорът да се обоснове. 44 | \end{problem} 45 | 46 | \begin{problem} 47 | Даден е следният алгоритъм: 48 | \lstinputlisting{algorithms/kadane.txt} 49 | 50 | Какво връща $\mathtt{Kadane}(A[1 \dots n])$? 51 | Отговорът да се обоснове. 52 | \end{problem} 53 | 54 | \begin{problem} 55 | Да се напише алгоритъм $\mathtt{Calculate}(F[0 \dots k], S[0 \dots k], n)$, който приема два целочислени масива $F[0 \dots k]$ и $S[0 \dots k]$, естествено число $n$ и връща числото $T(n)$, където: 56 | \begin{align*} 57 | & T(0) = F[0] \\ 58 | & T(1) = F[1] \\ 59 | & \phantom{00000} \vdots \\ 60 | & T(k) = F[k] \\ 61 | & T(n + k + 1) = S[k] \cdot T(n + k) + \dots + S[1] \cdot T(n + 1) + S[0] \cdot T(n). 62 | \end{align*} 63 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 64 | \end{problem} 65 | 66 | \newpage 67 | 68 | \begin{problem} 69 | Даден е следният алгоритъм: 70 | \lstinputlisting{algorithms/find-majority.txt} 71 | 72 | Да се докаже, че при подаден целочислен масив $A[1 \dots n]$, в който има елемент с повече от $\lfloor \frac{n}{2} \rfloor$ срещания, 73 | функцията $\mathtt{FindMajority}(A[1 \dots n])$ ще върне точно този елемент. 74 | \end{problem} 75 | 76 | \begin{problem} 77 | Да се напише алгоритъм $\mathtt{IsDerivationTree}(G = \opair{\Sigma, V, S, R}, T)$, който приема безконтекстна граматика $G$, дърво $T$ и проверява дали $T$ е дърво на извод за $G$. 78 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 79 | \end{problem} 80 | 81 | \begin{problem} 82 | Масив на Монж ще наричаме всеки двумерен масив $A[1 \dots n, 1 \dots m]$ от естествени числа, за който: 83 | \[ 84 | A[p, q] + A[s, t] \leq A[p, t] + A[s, q] \text{ за всички } 1 \leq p, s \leq n \text{ и } 1 \leq q, t \leq n. 85 | \] 86 | Да се напише алгоритъм със линейна сложност (т.е. $\Theta(n \cdot m)$), който приема двумерен масив $A[1 \dots n, 1 \dots m]$ и проверява дали е масив на Монж. 87 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 88 | \end{problem} 89 | 90 | \begin{problem} 91 | Да се напише алгоритъм, който приема естествено число $n$ и връща булев масив $P[2 \dots n]$, за който е изпълнено, че за всяко $2 \leq i \leq n$: 92 | \[ 93 | P[i] \text{ е истина } \iff i \text{ е просто}. 94 | \] 95 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 96 | \end{problem} 97 | 98 | \begin{problem} 99 | Даден е следният алгоритъм: 100 | \lstinputlisting{algorithms/stooge-sort.txt} 101 | Какво прави $\mathtt{SS}(A[1 \dots n], 1, n)$ и каква е сложността на този алгоритъм по време и памет? 102 | Обосновете отговорите си. 103 | \end{problem} 104 | 105 | \begin{problem} 106 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots n]$ и връща масив $B[1 \dots n]$, такъв че за всяко $1 \leq i \leq n$: 107 | \[ 108 | B[i] = \prod\limits_{\substack{k = 1 \\ k \neq i}}^n A[i]. 109 | \] 110 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 111 | \end{problem} 112 | 113 | \begin{problem} 114 | Да се напише колкото се може по-бърз алгоритъм, който приема целочислен масив $A[1 \dots n]$ и връща двойка $\opair{i, j}$ от различни индекси, за която: 115 | \[ 116 | A[i] \cdot A[j] = \max\limits_{1 \leq i' < j', \leq n} A[i'] \cdot A[j']. 117 | \] 118 | След това да се докаже неговата коректност и да се изследва сложността му по време и памет. 119 | \end{problem} 120 | 121 | \begin{problem} 122 | Като вход е даден масив от естествени числа $H[1 \dots n]$, който представя линии между точките $(i, 0)$ и $(i, H[i])$. 123 | Да се напише алгоритъм, който намира индексите на двете линии, които образуват контейнер, който би събрал максимално много вода. 124 | \end{problem} 125 | 126 | \newpage 127 | 128 | \begin{problem} 129 | Даден е следният алгоритъм: 130 | \lstinputlisting{algorithms/anon-alg2.txt} 131 | Какво връща $\mathfrak{U}(A[1 \dots n])$? 132 | Обосновете отговора си. 133 | \end{problem} 134 | 135 | \begin{problem} 136 | Дадени са $n$ на брой бензиностанции, които са подредени в кръгов маршрут. 137 | Масивът $G[1 \dots n]$ съдържа литрите бензин в бензиностанциите, а масивът $C[1 \dots n]$ съдържа цената (в литри бензин) за придвиждане от една бензиностанция до следващата (след бензиностанция $n$ е бензиностанция $1$). 138 | Приемаме, че имаме неограничен капацитет на резервоара. 139 | Да се състави алгоритъм, който връща индекс на бензиностанция, от която, започвайки с празен резервоар, може да се мине през всички бензиностанции. 140 | Ако не съществува такъв индекс, да се върне $-1$. 141 | За простота може да си мислите, че ако има такъв индекс, ще бъде единствен. 142 | \end{problem} -------------------------------------------------------------------------------- /complexity/iterative.tex: -------------------------------------------------------------------------------- 1 | \section{Как анализираме един алгоритъм по сложност?} 2 | 3 | Нека започнем с един прост пример: 4 | \lstinputlisting{algorithms/find.txt} 5 | 6 | Да кажем, че искаме да проверим броя на инструкциите, която тази функция ще изпълни, преди да приключи работата си. 7 | Точен отговор не може да се даде. 8 | В зависимост от това къде се намира $v$ във $A[1 \dots n]$, алгоритъмът може да приключи много бързо или много бавно. 9 | Можем да дадем горна и долна граница на бързодействието. 10 | 11 | Ако $v$ се намира в началото, то ще сме направили само следните $4$ операции: 12 | \begin{itemize} 13 | \item да инициализираме променливата $i$ със $0$; 14 | \item да проверим верността на $i < n$; 15 | \item да проверим верността на $A[i] = v$; 16 | \item да върнем $i$ т.е. $1$. 17 | \end{itemize} 18 | 19 | Нека сега да помислим какво ще стане в най-лошия случай (обикновено от тези ще се интересуваме) -- $v$ не участва в $A[1 \dots n]$. 20 | Тогава $n$ пъти ще изпълним следните $3$ операции: 21 | \begin{itemize} 22 | \item проверяваме верността на $i \leq n$; 23 | \item проверяваме верността на $A[i] = v$; 24 | \item увеличаваме $i$ с $1$. 25 | \end{itemize} 26 | Освен тези $3n$ операции, преди всичко трябва да инициализираме променливата $i$ със $1$, да се направи последната проверка на верността на $i \leq n$ (която ще ни изкара от цикъла), и да върнем $-1$. 27 | Общо излизат $3n + 3$ операции. 28 | 29 | Така виждаме, че в зависимост от входните данни, алгоритъмът приключва работа за поне $4$ стъпки и най-много $3n + 3$ стъпки. 30 | Такъв алгоритъм ще казваме, че има сложност по време $O(n)$. 31 | Разбира се, няма да е грешно и да кажем, че алгоритъмът има сложност по време $\Omega(1)$, но това не ни дава никаква информация, защото всеки алгоритъм има такава сложност. 32 | Също така, понеже не използваме допълнителни променливи, алгоритъмът ни има константна сложност по памет или сложност по памет $\Theta(1)$. 33 | 34 | По-общо казано, се интересуваме от асимптотиката на $T(n)$, където $T(n)$ е броят елементарни инструкции, които алгоритъмът извиква по време на своето изпълнение, при вход с размер $n$ в най-лошият случай. 35 | 36 | Тук вход с големина $n$ може да означава различни неща. 37 | Ако входът е някакъв масив или множество, то под размер ще разбираме броят на елементи. 38 | Ако пък входът е число, то под размер можем да разбираме самата стойност на числото или дължината на двоичния запис. 39 | 40 | \section{Предимствата и недостатъците на този вид анализ} 41 | 42 | Най-голямото предимство на асимптотичния анализ, е неговата простота. 43 | Вместо да влачим някакви константни множители и събираеми, имаме колкото се може по-проста формула, която да описва сложността на нашия алгоритъм. 44 | Това дали един алгоритъм работи със две или три стъпки по-бързо/бавно не ни интересува особено много. 45 | При много голям вход те ще работят практически еднакво. 46 | В някакъв смисъл това ни помага да виждаме по-голямата картинка. 47 | Един алгоритъм може да бъде по-бърз от друг, но от по-бърз алгоритъм до по-бърз алгоритъм има голяма разлика. 48 | 49 | Нека вземем за пример следната таблица: 50 | \begin{center} 51 | \begin{tabular}{|c|c|c|c|c|} 52 | \hline 53 | $n$ & $\lceil\log_2(n)\rceil$ & $n$ & $n^2$ & $2^n$ \\ 54 | \hline 55 | $1$ & $0$ & $1$ & $1$ & $2$ \\ 56 | \hline 57 | $10$ & $4$ & $10$ & $100$ & $1024$ \\ 58 | \hline 59 | $100$ & $7$ & $100$ & $10000$ & число със $31$ цифри \\ 60 | \hline 61 | $10000$ & $13$ & $10000$ & $100000000$ & число със $3011$ цифри \\ 62 | \hline 63 | $1000000$ & $20$ & $1000000$ & $1000000000000$ & число със $301030$ цифри \\ 64 | \hline 65 | \end{tabular} 66 | \end{center} 67 | 68 | Алгоритъм със сложността $n^2$ ще е по-бавен от алгоритъм със сложност $n$, обаче скока в бързината е много по-малък от този между $2^n$ и $n^2$. 69 | 70 | Този подход обаче си има своите недостатъци. 71 | Нека разгледаме два алгоритъма със сложности по време съответно $n$ и $2^{2^{2^{2^{1024}}}}$. 72 | Ние ведната ще се втурнем да кажем, че първият алгоритъм е по-лош. 73 | Той е с линейна сложност, а вторият алгоритъм има константна сложност. 74 | Обаче преди вторият алгоритъм даде отговор, всички звезди ще умрат т.е. няма да доживем да чуем този отговор. 75 | Разбира се, от някъде нататък, за много голями входни данни, първият алгоритъм наистина ще работи по-бавно, но ние никога няма да работим с толкова големи данни. 76 | Тогава на практика, първият алгоритъм е по-добър, нищо че асимптотично се води по-лош. 77 | Нас това няма да ни интересува в курса по ДАА. 78 | 79 | \section{Сложност по време на някои итеративни алгоритми} 80 | 81 | Нека видим сложността на алгоритъма за сортиране по метода на мехурчето: 82 | \lstinputlisting{algorithms/bubble-sort.txt} 83 | 84 | В най-лошия случай сложността $T(n)$ на функцията {\tt Sort} е следната: 85 | \[ 86 | T(n) = \sum\limits_{i = 1}^{n - 1} \sum\limits_{j = 1}^{n - i - 1} 1 = \sum\limits_{i = 1}^{n - 1} (n - i - 1) = (n - 2) + (n - 3) + \dots + 0 = \frac{(n - 2)(n - 1)}{2} \asymp n^2. 87 | \] 88 | 89 | По принцип $T(n)$ трябва да е сума от $4$, а не от $1$, но такъв константен брой операции, дори и приложени неконстантен брой пъти, не влияят на асимптотичното поведение. 90 | 91 | Нека сега разгледаме следният алгоритъм за степенуване: 92 | \lstinputlisting{algorithms/exp.txt} 93 | 94 | Той се възползва от простата идея, че за да сметнем да кажем $3^8$, можем вместо $8$ пъти да умножаваме числото $3$, да представим $3^8$ като $3^4 \cdot 3^4$. 95 | Тогава $3^4$ можем да сметнем веднъж, и да го умножим със себе си. 96 | Пак можем да представим $3^4$ като $3^2 \cdot 3^2$ и да пресметнем $3^2$ само веднъж и да го умножим със себе си. 97 | Така при по-голяма стойност на $y$ си спестяваме много работа. 98 | 99 | С уговорката, че умножението е атомарна операция, сложността по време $T(n)$ ($n$ е стойността на $y$) на функцията {\tt Exp} е следната: 100 | \[ 101 | T(n) = \sum\limits_{\substack{i = n \\ i \leftarrow \frac{i}{2}}}^1 1 = \underbrace{1 + \dots + 1}_{\substack{\text{колкото пъти} \\\text{можем да} \\ \text{делим целочислено} \\ n \text{ на } 2 \text{ преди} \\ \text{да получим } 0}} = \underbrace{1 + \dots + 1}_{\text{около } \log(n) \text{ пъти}} \asymp \log(n). 102 | \] 103 | 104 | За да можем по-формално да изследваме този вид поведение, ще трябва да си поиграем малко с рекурентни уравнения. 105 | 106 | \newpage -------------------------------------------------------------------------------- /graphs/algorithms.tex: -------------------------------------------------------------------------------- 1 | \chapter{Алгоритми върху графи} 2 | 3 | \section{Защо изобщо се занимаваме с графи?} 4 | 5 | Графите са може би най-приложимата структура в областта на компютърните науки. 6 | Тяхната моделираща мощ е несравнима с тази на останалите структури. 7 | Те могат се използват за моделиране на: 8 | \begin{itemize} 9 | \item приятелски връзки в социални мрежи; 10 | \item пътни мрежи в навигационни системи; 11 | \item йерархични системи; 12 | \item биологични мрежи. 13 | \end{itemize} 14 | Някои от задачите, които могат да решават са: 15 | \begin{itemize} 16 | \item намиране на най-къс път от точка $A$ до точка $B$; 17 | \item намиране на съвместима наредба на дадени задачи; 18 | \item намиране на най-добро разписание на полети; 19 | \item класифициране на уебсайтове по популярност; 20 | \item маркетинг в социални мрежи; 21 | \item валидация на текст. 22 | \end{itemize} 23 | 24 | \section{Как представяме графите в паметта?} 25 | 26 | В зависимост от нашите цели графите могат да бъдат представени в паметта по различни начини. 27 | Най-използваните начини са: 28 | \begin{itemize} 29 | \item \textbf{Списък на съседство}: 30 | 31 | За всеки връх се пазят в списък съседите му (и теглата ако има такива). 32 | \item \textbf{Матрица на съседство}: 33 | 34 | Пази се булева (може и числова ако графът е тегловен) таблица със всевъзможните комбинации от двойки върхове. 35 | Ако между два върха има ребро, то в съответната клетка пише единица (или теглото на реброто при тегловен граф), иначе нула. 36 | \item \textbf{Списък с ребрата}: 37 | 38 | Множеството от ребра идва като списък. Обикновено ако графът е неориентиран се пази само една пермутация на реброто. 39 | \end{itemize} 40 | 41 | Матрицата на съседство се използва по-рядко. 42 | Този подход е добър, когато графите са гъсти т.е. има много ребра в тях. 43 | В противен случай ние заемаме много повече памет от колкото ни е нужна. 44 | За сметка на това можем да проверим дали между два върха има ребро за константно време. 45 | 46 | Списъците на съседство са по-пестеливи от към памет в средния случай, обаче за сметка на това по-бавно се проверява съседство между два върха. 47 | Този подход е добър, когато графите са редки т.е. имат малко ребра в тях. 48 | Също така ако по някаква причина ни трябва да изброяваме точно съседите на някакъв връх (да кажем за някакво обхождане), това очевидно е най-добрият начин. 49 | В най-лошия случай заемаме двойно повече памет от подхода с матрицата. 50 | 51 | Списъка с ребрата е най-пестеливия начин от тези три. 52 | Пази се минималното количество нужна информация. 53 | Проблемът тук е, че проверката за съседство и изброяването на съседи на даден връх са бавни. 54 | Обаче това представяне все пак се използва, например когато искаме да построим МПД. 55 | Накратко, сложностите са такива: 56 | \begin{center} 57 | \begin{tabular}{|c|c|c|c|} 58 | \hline 59 | подход & памет & $(u, v) \in E$ & изброяване на съседите \\ 60 | \hline 61 | списък на съседство & $O(|V| + |E|)$ & $O(|V|)$ & $\Theta(|N(v)|)$ \\ 62 | \hline 63 | матрица на съседство & $\Theta(|V|^2)$ & $\Theta(1)$ & $\Theta(|V|)$ \\ 64 | \hline 65 | списък с ребра & $\Theta(|E|)$ & $O(|E|)$ & $\Theta(|E|)$ \\ 66 | \hline 67 | \end{tabular} 68 | \end{center} 69 | 70 | \section{Код на алгоритмите за обхождане на графи} 71 | 72 | Първият алгоритъм, за обхождане в широчина, е ``по-предпазлив''. 73 | Той обхожда върховете на слоеве, започвайки с някакъв първоначален връх на слой $0$. 74 | Намирайки се в слой $k$, ако преминем с ребро до необходен връх, ще се озовем в слой $k + 1$: 75 | \lstinputlisting{algorithms/bfs.txt} 76 | 77 | Сложност на алгоритъма за търсене в широчина в най-лошия случай: 78 | \begin{itemize} 79 | \item по време -- $\Theta(|V| + |E|)$; 80 | \item по памет -- $\Theta(|E|)$. 81 | \end{itemize} 82 | 83 | Тук виждаме силата на представянето чрез списъци на съседство. 84 | Ако например тук бяхме използвали матрица на съседство, алгоритъмът ни винаги щеше да има сложност $\Theta(|V|^2)$. 85 | 86 | Нека сега разгледаме другия алгоритъм -- за обхождане в дълбочина. 87 | Тук гледаме да влизаме колкото се може ``по-надълбоко'' в даден връх т.е. избираме произволно ребро в текущ връх докато можем, и след това се връщаме на предния и правим същото: 88 | 89 | \lstinputlisting{algorithms/dfs.txt} 90 | 91 | Сложността по време и памет на алгоритъма за търсене в дълбочина е същата като на този за търсене в широчина. 92 | 93 | \newpage 94 | 95 | \section{Най-къси пътища в тегловен граф} 96 | 97 | Започваме с може би най-известния нетривиален графов алгоритъм -- алгоритъмът на Дийкстра за намиране на най-къси пътища в тегловен граф. 98 | При него започваме с един стартов връх, и намираме най-късите пътища между този стартов връх и всеки достижим от него. 99 | 100 | Ето как става това: 101 | \lstinputlisting{algorithms/dijkstra.txt} 102 | Алгоритъмът има сложност по време $\Theta((|V| + |E|)\log(|V|))$ в най-лошия случай. 103 | 104 | \section{Структурата Union-find/Disjoint-union} 105 | 106 | Ще разгледаме метод за поддържане на разбиване на множества от вида $\{ 1, \dots, n \}$. 107 | Искаме да започнем от разбиването $\{ \{ 1 \}, \dots, \{ n \} \}$, и след това бързо да можем да обединяваме множества и да проверяваме дали някои $i, j$ попадат на едно и също място в разбиването. 108 | За да може тези проверки и сливания да стават бързо, се използва структурата Union-find (понякога се нарича Disjoint-union). 109 | Ще имаме единствена функция $\operatorname{unify}(i, j)$, която приема $i, j \in \{ 1, \dots, n \}$ и слива множествата от разбиването, в които $i$ и $j$ участват. 110 | Ако преди това те са били в едно и също множество, то тогава накрая връщаме $\F$, иначе връщаме $\T$. 111 | 112 | Имплементацията е следната: 113 | \lstinputlisting{structures/union_find.txt} 114 | Сложността на извикването на $\operatorname{unify}$ е $O(\alpha(n))$, където $\alpha$ е обратната функция на Акерман. 115 | Тази функция расте изключително бавно -- $\alpha(n) \leq 4$ за $n < 10^{600}$. 116 | 117 | \newpage 118 | 119 | \section{Алгоритъмът на Крускал за намиране на МПД} 120 | 121 | Алгоритъмът на Крускал работи по много естествен начин. 122 | Стараем се да приоритизираме ребрата с най-малки тегла. 123 | Не да ги добавяме само ако биха образували цикъл. 124 | 125 | Нека сега формализираме разсъжденията си. 126 | Даден тегловният граф $G = (V, E, w)$. 127 | Него ще пазим като масив от ребра $T[1 \dots k] \in \operatorname{arr}(E)$. 128 | Сега дефинираме релацията $\leq_G \subseteq E \cross E$ така: 129 | \[ 130 | e_1 \leq_G e_2 \stackrel{def}{\iff} w(e_1) \leq w(e_2). 131 | \] 132 | 133 | Вече можем да преминем на имплементацията: 134 | \lstinputlisting{algorithms/kruskal.txt} 135 | Сложността на алгоритъма по време е $\Theta(|E|\log(|E|))$ в най-лошия случай. -------------------------------------------------------------------------------- /complexity/recursive.tex: -------------------------------------------------------------------------------- 1 | \section{Защо са ни рекурентни уравнения?} 2 | 3 | Те се появяват по естествен път, когато искаме да анализираме сложността на рекурсивни алгоритми. 4 | 5 | Нека вземем за пример алгоритъма за двоично търсене: 6 | \lstinputlisting{algorithms/binary-search-rec.txt} 7 | 8 | При подаден сортиран целочислен масив $A[1 \dots n]$, негови индекси $l, r$ и цяло число $v$, функцията $\mathtt{BinarySearch}(A[1 \dots n], 1, n, v)$ ще върне индекс на $A[1 \dots n]$, в който се намира $v$, ако има такъв, иначе ще върне $-1$. 9 | Нека помислим каква е сложността на алгоритъма. 10 | Управляващите параметри на рекурсията са $l$ и $r$. 11 | Всеки път разликата между двете намалява двойно (като накрая когато $l = r$ тя ще стане отрицателна). 12 | 13 | Това означава, че в най-лошия случай сложността на алгоритъма може да се опише със следното рекурентно уравнение: 14 | \begin{align*} 15 | & T(0) = 2 \text{ // заради ред } 3 \text{ и } 4 \\ 16 | & T(n + 1) = T(\lfloor \frac{n + 1}{2} \rfloor) + 5 \text{ // заради проверките и рекурсивното извикване} 17 | \end{align*} 18 | 19 | В този случай лесно се вижда асимптотиката на $T(n)$: 20 | \begin{align*} 21 | T(n) & = T(\lfloor \frac{n}{2} \rfloor) + 5 \\ 22 | & = T(\lfloor \frac{n}{4} \rfloor) + 5 + 5 \\ 23 | & = T(\lfloor \frac{n}{8} \rfloor) + 5 + 5 + 5 = \dots = T(0) + \underbrace{5 + \dots + 5}_{\text{около } \log(n) \text{ пъти}} \asymp \log(n) 24 | \end{align*} 25 | 26 | Така получаваме, че алгоритъмът има сложност $O(\log(n))$. 27 | Обаче в общият случай далеч не е толкова лесно да се намери асимптотичното поведение на дадено рекурентно уравнение. 28 | Целта ни ще бъде да развием по-богат апарат за асимптотичен анализ на рекурентните уравнения. 29 | 30 | \section{Начини за намиране на асимптотиката на рекурентни уравнения} 31 | 32 | Начините се разделят на два типа: 33 | \begin{itemize} 34 | \item със решаване на уравнението; 35 | \item без решаване на уравнението. 36 | \end{itemize} 37 | 38 | И двата начина са ценни. 39 | Първият начин ни дава формула във явен вид, което може да ни е от полза. 40 | Понякога обаче формулата във явен вид не е \textit{``красива''}, или изобщо не може да се намери такава. 41 | Тогава идва на помощ вторият начин. 42 | Той директно ни дава някаква \textit{``хубава''} формула, без да трябва да намираме в явен вид решение на рекурентното уравнение. 43 | Проблема е обаче, че асимптотиката понякога е малко лъжлива -- алгоритъм със сложност $2^{2^{2^{1000}}}$ е асимптотично по-бавен от алгоритъм със сложност $n$, но практически вторият е по-бърз. 44 | 45 | Ще разгледаме следните методи (повечето от които са разглеждани по дискретна математика): 46 | \begin{itemize} 47 | \item налучкване и доказване 48 | \item развиване (което преди малко показахме) 49 | \item методът с характеристичното уравнение 50 | \item мастър-теоремата 51 | \end{itemize} 52 | 53 | Нека разгледаме един пример с налучкване: 54 | \begin{align*} 55 | & T(0) = 3 \\ 56 | & T(n + 1) = (n + 1)T(n) - n 57 | \end{align*} 58 | 59 | Започваме да разписваме: 60 | \begin{center} 61 | \begin{tabular}{| c | c | c |} 62 | \hline 63 | $n$ & $T(n)$ & $n!$ \\ 64 | \hline 65 | $0$ & $3$ & $1$ \\ 66 | \hline 67 | $1$ & $3$ & $1$ \\ 68 | \hline 69 | $2$ & $5$ & $2$ \\ 70 | \hline 71 | $3$ & $13$ & $6$ \\ 72 | \hline 73 | $4$ & $49$ & $24$ \\ 74 | \hline 75 | $5$ & $241$ & $120$ \\ 76 | \hline 77 | $6$ & $1441$ & $720$ \\ 78 | \hline 79 | \end{tabular} 80 | \end{center} 81 | 82 | Вече лесно можем да покажем с индукция, че $T(n) = 2(n!) + 1$: 83 | \begin{itemize} 84 | \item В базата имаме, че $T(0) = 3 = 2 \cdot 1 + 1 = 2 \cdot 0! + 1$. 85 | \item За индуктивната стъпка: 86 | \begin{align*} 87 | T(n + 1) & = (n + 1)T(n) - n \stackrel{\text{(ИП)}}{=} (n + 1)(2(n!) + 1) - n \\ 88 | & = (n + 1)(2(n!)) + n + 1 - n = 2(n + 1)! + 1 89 | \end{align*} 90 | \end{itemize} 91 | Накрая получаваме, че $T(n) \asymp n!$ 92 | 93 | Нека сега да видим как можем да използваме метода на характеристичното уравнение: 94 | \[ 95 | T(n) = 1 + \sum\limits_{i = 0}^{n - 1}T(i) \text{ // функцията е добре дефинирана и за } 0. 96 | \] 97 | Рекурентното уравнение, зададено в този вид, не може да се реши с този метод. 98 | За това ще трябва да направим преобразувания: 99 | \begin{align*} 100 | T(0) & = 1 \\ 101 | T(n + 1) & = 1 + \sum\limits_{i = 0}^{n}T(i) = 1 + T(n) + \sum\limits_{i = 0}^{n - 1}T(i) \\ 102 | & = T(n) + \underbrace{\left( 1 + \sum\limits_{i = 0}^{n - 1}T(i) \right)}_{T(n)} = 2T(n) + 1 = \underbrace{2T(n)}_{\text{хомогенна част}} 103 | \end{align*} 104 | 105 | Имаме само хомогенна част, от която получаваме характеристичното уравнение $x - 2 = 0$ с единствен корен $2$. 106 | Така: 107 | \[ 108 | T(n) = A \cdot 2^n \text{ за някоя константи } A. 109 | \] 110 | Вече няма нужда и да се намира константата -- ясно е че $T(n) \asymp 2^n$. 111 | Kато използваме метода на характеристичното уравнение, не е нужно да намираме накрая константите за да разберем каква е асимптотиката. 112 | Достатъчно е да вземем събираемото, която расте най-много. В случая е ясно, че това е $2^n$. 113 | 114 | \newpage 115 | 116 | Нека сега разгледаме и последният начин: 117 | \begin{theorem}[Мастър-теорема] 118 | Нека $a \geq 1, \: b > 1$ и $f \in \calF$. 119 | Нека $T(n) = aT(\frac{n}{b}) + f(n)$, където $\frac{n}{b}$ се интерпретира като $\lfloor \frac{n}{b} \rfloor$ или $\lceil \frac{n}{b} \rceil$. 120 | Тогава: 121 | \begin{itemize} 122 | \item[1 сл.] Ако $f(n) \preceq n^{\log_b(a) - \varepsilon}$ за някое $\varepsilon > 0$, то тогава $T(n) \asymp n^{log_b(a)}$. 123 | \item[2 сл.] Ако $f(n) \asymp n^{log_b(a)}$, то тогава $T(n) \asymp n^{log_b(a)} \log(n)$. 124 | \item[3 сл.] Ако са изпълнени следните условия: 125 | \begin{enumerate} 126 | \item $f(n) \succeq n^{\log_b(a) + \varepsilon}$ за някое $\varepsilon > 0$; и 127 | \item съществува $0 < c < 1$, за което от някъде нататък $a \cdot f(\frac{n}{b}) \leq c \cdot f(n)$, 128 | \end{enumerate} 129 | то тогава $T(n) \asymp f(n)$. 130 | \end{itemize} 131 | \end{theorem} 132 | 133 | Нека разгледаме рекурентното уравнение: 134 | \[ 135 | T(n) = 2T(\frac{n}{2}) + 1. 136 | \] 137 | Тук $a = b = 2$, и $f(n) = 1$. 138 | Също така $\log_b(a) = 1$, откъдето $f(n) = 1 \preceq n^{\log_b(a) - \varepsilon}$, за $\varepsilon \in (0, 1)$. 139 | Така по 1 сл. на мастър-теоремата получаваме, че $T(n) \asymp n$. -------------------------------------------------------------------------------- /intractability/p-and-np.tex: -------------------------------------------------------------------------------- 1 | 2 | \chapter{Алгоритмична неподатливост} 3 | 4 | \section{Класове на сложност \textbf{P} и \textbf{NP}} 5 | 6 | При решаването на различни видове задачи, които ни интересуват, е естествено да се опитаме да ги класифицираме по това колко са \textit{``сложни''}. 7 | Това сме го виждали вече -- в курса по ЕАИ сме класифицирали различни езици спрямо това каква машина може да решава въпроса за принадлежност към съответния език. 8 | Тук ще направим нещо подобно, разликата ще бъде в това, че ще се интересуваме от това за какво време се решава една задача. 9 | \begin{itemize} 10 | \item Класът на сложност \textbf{P} ще бъде множеството от всички изчислителни задачи за разпознаване, за които съществува алгоритъм с полиномиална времева сложност при най-лоши входни данни. 11 | \item Класът на сложност \textbf{NP} ще бъде множеството от всички изчислителни задачи за разпознаване, за които съществува алгоритъм с полиномиална времева сложност при всякакви входни данни, проверяващ отговора ``ДА'' на задачата с помощта на допълнителен параметър, наречен \textbf{сертификат}, който зависи от входните данни на задачата и чиято дължина е полиномиална спрямо тяхната. 12 | \end{itemize} 13 | 14 | \begin{remark} 15 | За класа \textbf{NP} има и алтернативна дефиниция -- в нея не е нужен сертификат, но се допуска алгоритъмът да е недетерминиран. 16 | Идеята е, че той едновременно \textit{``познава''} правилния отговор и го верифицира. 17 | \end{remark} 18 | 19 | \newpage 20 | 21 | Нека дадем пример за сертификат. 22 | Да кажем, че търсим отговор на въпроса: 23 | \begin{center} 24 | \textit{Вярно ли е, че уравнението $a_0 + a_1 x + \dots + a_n x^n = 0$ има реален корен?} 25 | \end{center} 26 | По-лесно ще бъде да верифицираме някое предложено решение, т.е. да отговорим на въпроса: 27 | \begin{center} 28 | \textit{Вярно ли е, че реалното число $x_0$ е корен на уравнението $a_0 + a_1 x + \dots + a_n x^n = 0$?} 29 | \end{center} 30 | В него имаме още един параметър -- предложеното решение $x_0$. 31 | В този случай това ще бъде сертификатът. 32 | Въпреки че в примера е направено така, не е нужно сертификатът да се използва. 33 | Той е само помощен и съществуването му е нужно само при отговор ``ДА''. 34 | Ясно е, че при отговор ``НЕ'' няма и как да има сертификат. 35 | 36 | Неформално казано, в класа \textbf{P} се намират задачите, които се решават \textit{``бързо''} (за полиномиално време), а в класа \textbf{NP} се намират задачите, чиито решения се верифицират \textit{``бързо''}. 37 | Лесно може да се види, че $\mathbf{P} \subseteq \mathbf{NP}$. 38 | Ако една задача може да се реши за полиномиално време без да използва сертификат, то тогава тя може да се реши и със използване на сертификат. 39 | 40 | \section{Няколко важни задачи за класа \textbf{NP}} 41 | 42 | Следните задачи за изключително важни за класа \textbf{NP} (по-късно ще разберем защо): 43 | \begin{itemize} 44 | \item Задачата \textbf{SAT}: 45 | 46 | \textbf{Вход:} Съждителна формула $\varphi$ в конюнктивна нормална форма. 47 | 48 | \textbf{Въпрос:} Има ли оценка, в която $\varphi$ е вярна? 49 | \item Задачата \textbf{3SAT}: 50 | 51 | \textbf{Вход:} Съждителна формула $\varphi$ в конюнктивна нормална форма, при която във всяка дизюнктивна клауза участват точно три литерала. 52 | 53 | \textbf{Въпрос:} Има ли оценка, в която $\varphi$ е вярна? 54 | \item Задачата \textbf{SubsetSum}: 55 | 56 | \textbf{Вход:} Масив $A[1 \dots n]$ от положителни числа и естествено число $s$. 57 | 58 | \textbf{Въпрос:} Има ли $I \subseteq \{ 1, \dots, n \}$, за което $\sum\limits_{i \in I} A[i] = s$? 59 | \item Задачата \textbf{2Partition}: 60 | 61 | \textbf{Вход:} Масив $A[1 \dots n]$ от положителни числа. 62 | 63 | \textbf{Въпрос:} Има ли $I \subseteq \{ 1, \dots, n \}$, за което $\sum\limits_{i \in I} A[i] = \sum\limits_{i \in \{ 1, \dots, n \} \setminus I} A[i]$? 64 | \item Задачата \textbf{VertexCover}: 65 | 66 | \textbf{Вход:} Граф $G = \opair{V, E}$ и естествено число $k$. 67 | 68 | \textbf{Въпрос:} Има ли $X \subseteq V$, за което $|X| \leq k$ и $X$ съдържа поне един край на всяко ребро от $E$? 69 | \item Задачата \textbf{DominatingSet}: 70 | 71 | \textbf{Вход:} Граф $G = \opair{V, E}$ и естествено число $k$. 72 | 73 | \textbf{Въпрос:} Има ли $X \subseteq V$, за което $|X| \leq k$ и всеки връх от $V \setminus X$ е съседен на някой от $X$? 74 | \item Задачата \textbf{Clique}: 75 | 76 | \textbf{Вход:} Граф $G = \opair{V, E}$ и естествено число $k$. 77 | 78 | \textbf{Въпрос:} Има ли клика $X \subseteq V$, за която $|X| \geq k$? 79 | \item Задачата \textbf{Anticlique}: 80 | 81 | \textbf{Вход:} Граф $G = \opair{V, E}$ и естествено число $k$. 82 | 83 | \textbf{Въпрос:} Има ли антиклика $X \subseteq V$, за която $|X| \geq k$? 84 | \item Задачата \textbf{HamiltonianPath}: 85 | 86 | \textbf{Вход:} Граф $G = \opair{V, E}$ и два върха $s, e \in V$. 87 | 88 | \textbf{Въпрос:} Има ли Хамилтонов път в $G$ от $s$ до $e$? 89 | \item Задачата \textbf{SubgraphIsomorphism}: 90 | 91 | \textbf{Вход:} Два графа $G_1 = \opair{V_1, E_1}$ и $G_2 = \opair{V_2, E_2}$. 92 | 93 | \textbf{Въпрос:} Има ли подграф $G$ на $G_1$, който е изоморфен на $G_2$? 94 | \item Задачата \textbf{TSP}: 95 | 96 | \textbf{Вход:} Тегловен граф $G = \opair{V, E, w}$ и естествено число $k$. 97 | 98 | \textbf{Въпрос:} Има ли Хамилтонов път в $G$ с тегло ненадвишаващо $k$? 99 | \end{itemize} 100 | 101 | Нека покажем за някои от тях, че попадат в класа \textbf{NP}. 102 | 103 | Да започнем със \textbf{SAT}. 104 | Трябва да предложим полиномиален алгоритъм, който с помощта на сертификат проверява за отговор ``ДА''. 105 | Сертификатът ще бъде оценката. 106 | Оценката може да се представи като масив от променливи $V[1 \dots k]$, чиито членове са променливите, които имат стойност ``истина''. 107 | Ясно е, че тази кодировка е с полиномиална относно формулата дължина. 108 | При дадена оценка лесно можем да видим дали формулата е вярна: 109 | \VerbatimInput[numbersep=3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/sat.txt} 110 | Този алгоритъм очевидно работи за полиномиално време. 111 | Наистина, той проверява дали $V[1 \dots k]$ задава оценка, в която $\varphi$ е вярна. 112 | Така \textbf{SAT} е в класа \textbf{NP}. 113 | Тъй като \textbf{3SAT} е просто по-лесна версия на \textbf{SAT}, тя също попада в класа \textbf{NP}. 114 | 115 | Нека сега видим, че \textbf{SubsetSum} е в класа \textbf{NP}. 116 | Тук сертификатът ще бъде масив $I[1 \dots k]$, който ще представя множество от индекси за входния масив. 117 | При дадено множество от индекси лесно можем да проверим дали е изпълнено условието за сумата: 118 | \VerbatimInput[numbersep=3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/subset-sum.txt} 119 | Този алгоритъм очевидно работи за полиномиално време. 120 | Наистина, той проверява дали $I[1 \dots k]$ задава подмножество $I$ на $\{ 1, \dots, n \}$, за което $\sum\limits_{i \in I} A[i] = s$. 121 | Така \textbf{SubsetSum} е в класа \textbf{NP}. 122 | 123 | Да видим сега, че \textbf{VertexCover} е в класа \textbf{NP}. 124 | Тук сертификатът ще бъде масив $X[1 \dots n]$, който ще представя множеството от върховете, които ще образуват потенциално върхово покритие. 125 | При дадено множество от върхове лесно можем да видим дали то е върхово покритие с размер най-много $k$: 126 | \VerbatimInput[numbersep=3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/vertex-cover.txt} 127 | Този алгоритъм очевидно работи за полиномиално време. 128 | Наистина, той проверява дали $X[1 \dots n]$ задава върхово покритие на $G$ с най-много $k$ елемента. 129 | Така \textbf{VertexCover} е в класа \textbf{NP}. 130 | 131 | Сега ще покажем, че \textbf{Clique} е в класа \textbf{NP}. 132 | Тук сертификатът ще бъде масив $X[1 \dots n]$, който ще представя множеството от върховете, които ще образуват потенциална клика. 133 | При дадено множество от върхове лесно можем да видим дали то е клика с размер поне $k$: 134 | \VerbatimInput[numbersep=3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/clique.txt} 135 | Този алгоритъм очевидно работи за полиномиално време. 136 | Наистина, той проверява дали $X[1 \dots n]$ задава клика в $G$ с поне $k$ елемента. 137 | Така \textbf{Clique} е в класа \textbf{NP}. -------------------------------------------------------------------------------- /sorting/fundamentals.tex: -------------------------------------------------------------------------------- 1 | \chapter{Сортиране и неговите приложения} 2 | 3 | \section{Каква задача решаваме?} 4 | 5 | Ще формулираме задачата във по-общ вариант. 6 | Имаме някаква преднаредба (рефлексивна и транзитивна релация) $\leq_A$ върху множество $A$. 7 | Даден ни е масив $B[1 \dots n]$ със елементи от $A$ и търсим пермутация $B'[1 \dots n]$ на $B[1 \dots n]$, за която: 8 | \[ 9 | B'[1] \leq_A B'[2] \leq_A \dots \leq_A B'[n]. 10 | \] 11 | На нас обикновено ще ни интересува частния случай $(A, \leq_A) = (\Z, \leq)$. 12 | Ще разгледаме малко по-подробно два сортиращи алгоритъма -- за сортиране чрез сливане и за бързо сортиране. 13 | 14 | \section{Сортиране чрез сливане} 15 | Нека си представим, че искаме да сортираме редицата $\opair{6, 7, 5, 4, 8, 2, 3, 1}$. 16 | Можем да направим следното разделение на подзадачи за сортиране: 17 | \begin{center} 18 | \begin{forest} 19 | [$\opair{6, 7, 5, 4, 8, 2, 3, 1}$ 20 | [$\opair{6, 7, 5, 4}$ [$\opair{6, 7}$ [$\opair{6}$] [$\opair{7}$]] [$\opair{5, 4}$ [$\opair{5}$] [$\opair{4}$]]] [$\opair{8, 2, 3, 1}$ [$\opair{8, 2}$ [$\opair{8}$] [$\opair{2}$]] [$\opair{3, 1}$ [$\opair{3}$] [$\opair{1}$]]] 21 | ] 22 | \end{forest} 23 | \end{center} 24 | Сега това, което трябва да направим, е да започнем да сливаме масивите: 25 | \begin{center} 26 | \begin{forest} 27 | for tree={grow'=north} 28 | [$\opair{1, 2, 3, 4, 5, 6, 7, 8}$ [$\opair{4, 5, 6, 7}$ [$\opair{6, 7}$ [$\opair{6}$] [$\opair{7}$]] [$\opair{4, 5}$ [$\opair{5}$] [$\opair{4}$]]] [$\opair{1, 2, 3, 8}$ [$\opair{2, 8}$ [$\opair{8}$] [$\opair{2}$]] [$\opair{1, 3}$ [$\opair{3}$] [$\opair{1}$]]]] 29 | \end{forest} 30 | \end{center} 31 | Ако имахме функция $\mathtt{Merge}$, която приемаше два сортирани масива $A[1 \dots n], B[1 \dots m]$ и връщаше масив $C[1 \dots n + m]$, който съдържа елементите на $A[1 \dots n]$ и $B[1 \dots m]$ в сортиран ред, то тогава щяхме да получим следния алгоритъм за сортиране: 32 | \lstinputlisting{algorithms/merge-sort.txt} 33 | Ще докажем това с индукция по $n$: 34 | \begin{itemize} 35 | \item В базовия случай $n = 1$. 36 | Всеки едноелементен масив е сортиран и ние коректно ще върнем копие на $A[1 \dots n]$. 37 | \item Нека сега $n > 1$. 38 | По (ИП) двете извиквания на $\mathtt{MergeSort}$ ще сортират $A[1 \dots \lfloor \frac{n}{2} \rfloor]$ и $A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$. 39 | След това по допускане извикването на $\mathtt{Merge}$ коректно ще ги слее в нов сортиран масив и след това алгоритъмът ще върне този масив. 40 | \end{itemize} 41 | Ако $\mathtt{Merge}$ завършва за време $T_M(n)$, то сложността на $\mathtt{MergeSort}$ може да се опише с рекурентното уравнение: 42 | \[ 43 | T(n) = 2 T(\frac{n}{2}) + T_M(n). 44 | \] 45 | Нещо повече, ако покажем алгоритъм $\mathtt{Merge}$ със линейна сложност по време, то тогава: 46 | \[ 47 | T(n) = 2 T(\frac{n}{2}) + \Theta(n) \asymp n \log(n). 48 | \] 49 | 50 | \newpage 51 | 52 | Ето алгоритъм със сложност $\Theta(n + m)$, който слива два сортирани масива: 53 | \lstinputlisting{algorithms/merge.txt} 54 | 55 | Нека сега формулираме инвариантът (той ще бъде един за всички цикли), чието доказателство оставяме на читателя: 56 | \begin{invariant} 57 | При всяко достигане на условието за край на цикъла на редове $7, 16$ и $21$ е изпълнено, че: 58 | \begin{itemize} 59 | \item $C[1 \dots i - 1]$ съдържа елементите на $A[1 \dots l_A - 1]$ и $B[1 \dots l_B - 1]$, и то в сортиран ред; 60 | \item $l_A$ е индексът на най-малкия елемент на $A[1 \dots n]$, който още не е копиран в $C[1 \dots n + m]$; 61 | \item $l_B$ е индексът на най-малкия елемент на $B[1 \dots m]$, който още не е копиран в $C[1 \dots n + m]$. 62 | \end{itemize} 63 | \end{invariant} 64 | 65 | \section{Бързо сортиране} 66 | Сега ще покажем още една идея за сортиране. 67 | Нека отново ни е дадена редицата $\opair{6, 7, 5, 4, 8, 2, 3, 1}$. 68 | Ако можехме да пренаредим масива така, че всички елементи, които са по-малки от $5$ да са вляво от $5$ и останалите да бъдат вдясно от $5$, то тогава щяхме да трябва да сортираме двете по-малки редици. 69 | Да кажем, след пренареждането получаваме редицата $\opair{4, 2, 1, 3, 5, 7, 6, 8}$. 70 | Тогава ще трябва само да сортираме редиците $\opair{4, 2, 1, 3}$ и $\opair{7, 6, 8}$. 71 | По формално, нека си представим, че имаме алгоритъм $\mathtt{Partition}$, която приема целочислен масив $A[1 \dots n]$ и го пренарежда така, че да има индекс $pp$, за което: 72 | \begin{itemize} 73 | \item $A[i] < A[pp]$ за всяко $1 \leq i < pp$; 74 | \item $A[i] \geq A[pp]$ за всяко $pp \leq i \leq n$, 75 | \end{itemize} 76 | и след това връща този индекс $pp$. 77 | 78 | Тогава можем да получим сортиращ алгоритъм по следния начин: 79 | \lstinputlisting{algorithms/quicksort.txt} 80 | Да докажем, че $\mathtt{Quicksort}(A[1 \dots n])$ сортира $A[1 \dots n]$ с индукция по $n$: 81 | \begin{itemize} 82 | \item В базовия случай $n \leq 1$, откъдето масивът е сортиран. 83 | \item Нека сега $n > 1$. 84 | По допускане след извикването на $\mathtt{Partition}$: 85 | \begin{itemize} 86 | \item $A[i] < A[pp]$ за всяко $1 \leq i < pp$; 87 | \item $A[i] \geq A[pp]$ за всяко $pp \leq i \leq n$, 88 | \end{itemize} 89 | Тогава по (ИП) алгоритъмът коректно ще сортира масивите $A[1 \dots pp - 1]$ и $A[pp + 1 \dots n]$. 90 | \end{itemize} 91 | 92 | Ще покажем, че $\mathtt{Partition}$ може да се имплементира със сложност $\Theta(n)$. 93 | Тук обаче сложността на сортиращия алгоритъм зависи изцяло от това какво е $pp$. 94 | В най-лошия случай $pp = 1$ или $pp = n$ и тогава ще имаме рекурентното уравнение: 95 | \[ 96 | T(n) = T(n - 1) + T(1) + \Theta(n) \asymp n^2. 97 | \] 98 | В средния случай се оказва, че сложността е $n \log(n)$. 99 | На читателя оставяме да покаже, че: 100 | \[ 101 | T(n) = \frac{1}{n} \left( \sum\limits_{k = 1}^{n - 1} T(k) + T(n - k) \right) + \Theta(n) \asymp n \log(n). 102 | \] 103 | 104 | Остава само да направим имплементация на $\mathtt{Partition}$ със сложност $\Theta(n)$: 105 | \lstinputlisting{algorithms/partition.txt} 106 | На читателя оставяме доказателството на следния: 107 | \begin{invariant} 108 | При всяко достигане на проверката за край на цикъла на ред $5$ е изпълнено, че: 109 | \begin{itemize} 110 | \item $A[j] < pivot$ за всяко $1 \leq j < pp$; 111 | \item $A[j] \geq pivot$ за всяко $pp \leq j < i$. 112 | \end{itemize} 113 | \end{invariant} 114 | 115 | Функцията $\mathtt{Partition}$ е много удобна. 116 | Чрез нея можем да намираме $k$-ти по големина елемент на масив с уникални елементи по следния начин: 117 | \lstinputlisting{algorithms/select.txt} 118 | Ще докажем, че $\mathtt{Select}(A[1 \dots n], k)$ връща $k$-тия по големина елемент на $A[1 \dots n]$ с индукция относно $n$: 119 | \begin{itemize} 120 | \item В базовия случай $n = 1$, откъдето след извикването на $\mathtt{Partition}$ ще имаме $pp = k = 1$ и алгоритъмът коректно ще върне $A[k]$. 121 | \item Нека сега $n > 1$. 122 | След извикването на $\mathtt{Partition}$: 123 | \begin{itemize} 124 | \item $A[i] < A[pp]$ за всяко $1 \leq i < pp$; 125 | \item $A[i] \geq A[pp]$ за всяко $pp \leq i \leq n$. 126 | \end{itemize} 127 | Нека сега разгледаме трите случая: 128 | \begin{itemize} 129 | \item[1 сл.] Ако $k = pp$, то понеже $A[pp]$ е $pp$-тия по големина елемент, алгоритъмът ще върне правилният отговор. 130 | \item[2 сл.] Ако $k < pp$, то е достатъчно да намерим $k$-тия по големина елемент на масива $A[1 \dots pp - 1]$, тъй като там са елементите, по-малки от $A[pp]$. 131 | По (ИП) ще получим коректен отговор. 132 | \item[3 сл.] Ако $k > pp$, то е достатъчно да намерим $(k - pp)$-тия (понеже махаме първите $pp$ елемента на $A[1 \dots n]$) по големина елемент на масива $A[pp + 1 \dots n]$, тъй като там са елементите, по-големи от $A[pp]$. 133 | По (ИП) ще получим коректен отговор. 134 | \end{itemize} 135 | \end{itemize} 136 | Тук отново сложността зависи от стойността на $pp$, която получаваме. 137 | За съжаление, отново в най-лошия случай имаме: 138 | \[ 139 | T(n) = T(n - 1) + n \asymp n^2. 140 | \] 141 | В средния случай получаваме: 142 | \[ 143 | T(n) = \frac{1}{n} \sum\limits_{k = 1}^{n - 1} T(k) + n. 144 | \] 145 | Сега ще покажем, че в средния случай: 146 | \[ 147 | T(n) \asymp n. 148 | \] 149 | Първо, можем да умножим по $n$ и от двете страни и получаваме: 150 | \[ 151 | n T(n) = \sum\limits_{k = 1}^{n - 1} T(k) + n^2. 152 | \] 153 | Тогава ако заместим $n$ с $n - 1$ и извадим, ще получим: 154 | \[ 155 | n T(n) - (n - 1) T(n - 1) = T(n - 1) + n^2 - (n - 1)^2 = T(n - 1) + 2n - 1. 156 | \] 157 | Но това е еквивалентно на: 158 | \[ 159 | n T(n) = n T(n - 1) + 2n - 1. 160 | \] 161 | Сега разделяме на $n$ и получаваме: 162 | \[ 163 | T(n) = T(n - 1) + 2 - \frac{1}{n} = T(n - 2) + 2 + 2 - \frac{1}{n} - \frac{1}{n - 1} = \dots = T(0) + 2n - \sum\limits_{k = 1}^n \frac{1}{k} \asymp n. 164 | \] 165 | -------------------------------------------------------------------------------- /correctness/iterative.tex: -------------------------------------------------------------------------------- 1 | \section{Какво имаме предвид под коректност?} 2 | 3 | За целите на този курс един алгоритъм ще наричаме \textbf{коректен}, ако завършва при всякакви входни данни и връща правилен резултат при всякакви входни данни 4 | 5 | \begin{remark} 6 | Въпреки че ние ще имаме това разбиране в курса, на практика тези изисквания невинаги са изпълнени: 7 | \begin{itemize} 8 | \item разглеждат се алгоритми, които могат и да не завършват за някои входни данни -- от теоретична гледна точка са интересни за хората, които се занимават с теорията на изчислимостта; 9 | \item разглеждат се алгоритми, които много често (но не винаги) връщат правилния резултат -- обикновено това се прави с цел бързодействие. 10 | \end{itemize} 11 | \end{remark} 12 | 13 | \section{Едно \textit{``ново''} понятие -- инвариант} 14 | 15 | Специално за итеративните алгоритми се въвежда ново понятие - \textbf{инвариант}. 16 | Това са специални твърдения, свързани с цикъла. 17 | 18 | В най-общият случай (за алгоритми) се формулират по следния начин: 19 | \begin{center} 20 | ``При $k$-тото достигане на ред $l$ (ако има няколко инструкции казваме преди/след коя се намираме) в алгоритъма $\mathfrak{A}$ е изпълнено \textit{някакво твърдение, зависещо от $k$ и променливите, използвани в $\mathfrak{A}$}''. 21 | \end{center} 22 | Доказателството на такива твърдения протича с добре познатата индукция. 23 | Първо доказваме базата т.е. какво се случва при първото достигане на цикъла. 24 | Индуктивното предположение и индуктивната стъпка се обединяват в \textit{``нова''} фаза, наречена \textbf{поддръжка}. 25 | Довършителните разсъждения, които по принцип се намират след доказването на твърдението чрез индукция, ще наричаме \textbf{терминация}. 26 | Накрая показваме, че винаги ще излезнем от цикъла (\textbf{финитност}). 27 | Обикновено това ще го смятаме за очевидно (най-вече за $\mathtt{for}$-цикли). 28 | 29 | \begin{warning} 30 | Това, за което се използват инвариантите, е да се докаже коректността на ЕДИН цикъл, не на цял алгоритъм. 31 | Когато в алгоритъма ни има няколко цикъла, на всеки от тях трябва да съответства по един инвариант. 32 | \end{warning} 33 | 34 | \section{Инвариантите в действие} 35 | 36 | Нека разгледаме следния алгоритъм за степенуване на $2$: 37 | \lstinputlisting{algorithms/pow2.txt} 38 | 39 | \begin{invariant} 40 | При всяко достигане на проверката за край на цикъла (на ред $4$) е изпълнено, че $r = 2^{i - 1}$. 41 | \end{invariant} 42 | \begin{proof} 43 | \phantom{1} 44 | 45 | \textbf{База.} 46 | Наистина при първото достигане имаме, че $i = 1$ и от там $r = 1 = 2^{i - 1}$. 47 | 48 | \textbf{Поддръжка.} 49 | Нека при някое непоследно достигане твърдението е изпълнено. 50 | Тогава преди следващото достигане на проверката на $r$ присвояваме $2r$, като знаем, че преди $r$ е бил $2^{i - 1}$, и след това на $i$ присвояваме $i + 1$. 51 | Така е ясно, че при новото достигане на проверката $r$ ще стане $2 \cdot 2^{i_{old} - 1} = 2^{i_{old}} = 2^{i - 1}$. 52 | 53 | \textbf{Терминация.} 54 | Ако е изпълнено условието за край на цикъла, то тогава $i = n + 1$, откъдето ще върнем $r = 2^{(n + 1) - 1} = 2^n$. 55 | 56 | \textbf{Финитност.} 57 | Величината $n - i$ започва с $n - 1$, и намалява с $1$, докато не стигне $-1$, когато ще излезнем от цикъла. 58 | Следователно алгоритъмът винаги завършва. 59 | \end{proof} 60 | 61 | \section{С инвариантите трябва да се внимава} 62 | 63 | Един от често срещаните капани, в които попадат хората, е да не си формулират инвариантът добре. 64 | Много е важно инвариант да дава достатъчна информация за това което наистина се случва в алгоритъма. 65 | За целта ще разгледаме един пример: 66 | \lstinputlisting{algorithms/selection-sort.txt} 67 | 68 | На интуитивно ниво е ясно какво прави кода. 69 | Намира най-малкия елемент, и го слага на първо място. 70 | След това намира втория най-малък елемент, и го слага на второ място, и т.н. 71 | 72 | Нещо, което някои биха се пробвали да направят за първия цикъл, е следното: 73 | \begin{center} 74 | \textit{При всяко достигане на проверката за край на цикъла на ред $3$ подмасивът $A[1 \dots i - 1]$ е сортиран.} 75 | \end{center} 76 | 77 | Проблемът с това твърдение, е че може много лесно да се измисли алгоритъм, за който това твърдение е изпълнено, и изобщо не сортира елементите в масива: 78 | \lstinputlisting{algorithms/trust-me-it-sorts.txt} 79 | 80 | Очевидно този за този алгоритъм горната инвариант е изпълнена, но той е безсмислен. 81 | Получаваме сортиран масив, но за сметка на това губим цялата информация, която сме имали за него. 82 | 83 | Нещо друго, което е важно да се направи, е първо да се формулира инвариант за вътрешния цикъл, и после за външния, като тънкият момент тук е, че ще ни трябват допускания за първият инвариант. 84 | Идеята е, че външния цикъл разчита на вътрешния да си свърши работата, и обратно вътрешния разчита (не винаги) на външния преди това да си е свършил работата. 85 | 86 | Нека покажем как трябва да станат инвариантите, като доказателството оставяме за упражнение на читателя. 87 | Нека $A^*[1 \dots n]$ е първоначалната стойност на входния масив. 88 | \begin{invariant}[вътрешен цикъл] 89 | При всяко достигане на проверката за край на цикъла на ред $5$ имаме, че $m$ е индексът на най-малкия елемент в масива $A[i \dots j - 1]$. 90 | \end{invariant} 91 | 92 | \begin{invariant}[външен цикъл] 93 | При всяко достигане на проверката за край на цикъла на ред $2$ имаме, че масивът $A[1 \dots i - 1]$ съдържа сортирани първите $i - 1$ по големина елементи на $A^*[1 \dots n]$, като останалите са в $A[i \dots n]$. 94 | \end{invariant} 95 | 96 | Обикновено в доказателството на коректност на алгоритми най-трудното е да се формулира инвариантът. 97 | Ако човек има добре формулирана инвариант, доказателството e на първо място възможно, а на второ -- по-лесно. 98 | 99 | \section{Подход при задачи с вече даден алгоритъм} 100 | 101 | Нека разгледаме следния алгоритъм: 102 | \lstinputlisting{algorithms/foo.txt} 103 | Питаме се какво връща той? 104 | 105 | Обикновено в такива задачи трябва да се изпробва алгоритъма върху няколко стойности. 106 | Можем да забележим, че: 107 | \begin{itemize} 108 | \item $\mathtt{Foo}(0)$ връща $0$; 109 | \item $\mathtt{Foo}(1)$ връща $1$; 110 | \item $\mathtt{Foo}(2)$ връща $8$ и така нататък. 111 | \end{itemize} 112 | 113 | Вече можем да видим какво прави алгоритъмът -- $\mathtt{Foo}(a)$ връща $a^3$. 114 | Нека сега докажем това: 115 | \begin{invariant} 116 | При всяко достигане на проверката за край на цикъла на ред $5$ е изпълнено, че: 117 | \begin{itemize} 118 | \item $x = 6 (1 + i)$; 119 | \item $y = 3i^2 + 3i + 1$; 120 | \item $z = i^3$. 121 | \end{itemize} 122 | \end{invariant} 123 | \begin{proof} 124 | \phantom{1} 125 | 126 | \textbf{База.} 127 | При първото достигане имаме, че: 128 | \begin{itemize} 129 | \item $i = 0$; 130 | \item $x = 6 = 6 \cdot 1 = 6 (1 + 0) = 6 (1 + i)$; 131 | \item $y = 1 = 3 \cdot 0^2 + 3 \cdot 0 + 1 = 3i^2 + 3i + 1$; 132 | \item $z = 0 = 0^3 = i^3$. 133 | \end{itemize} 134 | 135 | \textbf{Поддръжка.} 136 | Нека твърдението е изпълнено за някое непоследно достигане на проверката за край на цикъла. 137 | Тогава при влизане в тялото на цикъла: 138 | \begin{itemize} 139 | \item $z$ ще стане $z + y \stackrel{\text{(ИП)}}{=} i^3 + 3i^2 + 3i + 1 = (\underbrace{i + 1}_{\text{ново } i})^3$; 140 | \item $y$ ще стане $y + x \stackrel{\text{(ИП)}}{=} 3i^2 + 3i + 1 + 6 + 6i = 3(\underbrace{i + 1}_{\text{ново } i})^2 + 3(\underbrace{i + 1}_{\text{ново } i}) + 1$; 141 | \item $x$ ще стане $x + 6 \stackrel{\text{(ИП)}}{=} 6(1 + i) + 6 = 6(1 + \underbrace{i + 1}_{\text{ново } i})з$. 142 | \end{itemize} 143 | 144 | \textbf{Терминация.} 145 | В последното достигане на проверката за край на цикъла имаме, че $i = a$, и тогава на ред $12$ алгоритъмът ще върне $z = a^3$. 146 | 147 | \textbf{Финитност.} (от тук нататък повечето няма да ги пишем) 148 | Величината $a - i$ започва с $a$, и намалява с $1$, докато не стигне $0$, когато ще излезнем от цикъла. 149 | Следователно алгоритъмът винаги завършва. 150 | \end{proof} 151 | 152 | \newpage 153 | 154 | Нека разгледаме още един такъв пример: 155 | \lstinputlisting{algorithms/quux.txt} 156 | 157 | Искаме да видим какво връща $\mathfrak{quux}(A[1 \dots n])$. 158 | Тук най-трудното, което трябва да се направи, е да се определи какво връщат $\mathfrak{bar}$ и $\mathfrak{foo}$: 159 | \begin{itemize} 160 | \item $\mathfrak{bar}(n) = \sqrt{n ^ 2} = | n |$; 161 | \item $\mathfrak{foo}(x, y) = \frac{x + y + |x - y|}{2} = \max\{ x, y \}$: 162 | \begin{itemize} 163 | \item ако $x \geq y$, то $\frac{x + y + |x - y|}{2} = \frac{x + y + x - y}{2} = \frac{2x}{2} = x = \max\{ x, y \}$, 164 | \item ако $x < y$, то $\frac{x + y + |x - y|}{2} = \frac{x + y + y - x}{2} = \frac{2y}{2} = y = \max\{ x, y \}$. 165 | \end{itemize} 166 | \end{itemize} 167 | 168 | Като знаем какво правят $\mathfrak{bar}$ и $\mathfrak{foo}$, можем да забележим, че $\mathfrak{quux}(A[1 \dots n])$ дава най-големия елемент на $A[1 \dots n]$: 169 | \begin{invariant} 170 | При всяко достигане на проверката за край на цикъла на ред $15$ е изпълнено, че $a = \max A[1 \dots i - 1]$. 171 | \end{invariant} 172 | 173 | \begin{proof} 174 | \phantom{1} 175 | 176 | \textbf{База.} 177 | Наистина при първото достигане $a = A[1] = \max A[1 \dots i - 1]$. 178 | 179 | \textbf{Поддръжка.} 180 | Нека твърдението е изпълнено за някое непоследно достигане на проверката за край на цикъла. 181 | Тогава като влезем в тялото на цикъла, променливата $a$ става: 182 | \begin{align*} 183 | \mathfrak{foo}(a, A[i]) \stackrel{\text{(ИП)}}{=} \mathfrak{foo}(\max A[1 \dots i - 1], A[i]) = & \max (\max A[1 \dots i - 1], A[i]) \\ 184 | = & \max A[1 \dots i] = \max A[1 \dots \underbrace{i + 1}_{\text{ново } i} - 1] 185 | \end{align*} 186 | 187 | \textbf{Терминация.} 188 | При последното достигане на проверката за край на цикъла на ред $15$ променливата $i$ ще бъде $n + 1$, откъдето функцията ще върне точно $a = \max A[1 \dots (n + 1) - 1] = \max A[1 \dots n]$, с което сме готови. 189 | \end{proof} 190 | -------------------------------------------------------------------------------- /intractability/np-hardness.tex: -------------------------------------------------------------------------------- 1 | \section{$\mathbf{P = NP}$ или $\mathbf{P \neq NP}$? Колко пък да е трудно?} 2 | 3 | За съжаление, все още няма отговор на този въпрос -- не се знае дали $\mathbf{P = NP}$ или $\mathbf{P \neq NP}$. 4 | Най-доброто, което имаме до момента, са няколко задачи в класа \NP, които са изключително важни. 5 | Тези задачи в някакъв смисъл характеризират целия клас. 6 | Тях ще ги наричаме \NP-пълни. 7 | Формалната дефиниция е следната, една изчислителна задача \textbf{X} е \NP-пълна, ако: 8 | \begin{itemize} 9 | \item \textbf{X} е в класа \NP; 10 | \item всяка задача \textbf{Y} в класа \NP{ }може алгоритмично да се сведе до задачата \textbf{X} за полиномиално време. 11 | \end{itemize} 12 | Фактът, че \textbf{Y} се свежда до \textbf{X} за полиномиално време, ще бележим с $\mathbf{Y} \leq_p \mathbf{X}$\footnote{На други места вместо $\leq_p$ се използват означенията $\leq^p_m$ и $\propto_p$.}. 13 | Лесно се вижда, че $\leq_p$ е транзитивна. 14 | Когато второто условие е изпълнено (без да е непременно изпълнено първото) казваме, че задачата \textbf{X} е \NP-трудна. 15 | 16 | Интересното за \NP-пълните задачи е следното: 17 | \begin{itemize} 18 | \item ако покажем, че която и да е \NP-пълна задача се решава за полиномиално време, то тогава $\mathbf{P = NP}$; 19 | \item ако покажем, че която и да е \NP-пълна задача не може да се реши за полиномиално време, то тогава $\mathbf{P \neq NP}$. 20 | \end{itemize} 21 | Всички \NP-пълни задачи са еквивалентни в смисъл, че всяка може да се сведе до другата за полиномиално време. 22 | Така, имайки полиномиално алгоритъм, който решава някоя \NP-пълна задача \textbf{X}, то тогава можем да решим всяка задача \textbf{Y} от \NP{} за полиномиално време: 23 | \VerbatimInput[numbersep = 3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/solve-in-ptime.txt} 24 | 25 | Обаче ние нито имаме такъв алгоритъм, нито не знаем, че такъв алгоритъм няма. 26 | Това са задачи, които хем не можем да решим ефективно, хем не можем да покажем, че такова решение няма. 27 | Най-доброто, което можем да направим, е да покажем, че задачата е еквивалентна по трудност на други задачи, за които други много умни хора не са се сетили. 28 | 29 | \section{\textit{``Основната''} \NP-пълнa задача} 30 | 31 | По-принцип е трудно директно да се показва \NP-пълнота, ако се кара по дефиницията. 32 | Това, което обикновено се прави, е да се показва по дефиниция, че една задача е \NP-пълна (все трябва да започнем отнякъде), след което се правят полиномиални редукции от задачи, за които знаем, че са \NP-пълни, към задачите, които искаме да покажем, че са \NP-пълни. 33 | Тук се възползваме от транзитивността на $\leq_p$. 34 | Разбира се, това само би показало \NP-трудност, трябва и да се провери принадлежност към класа \NP. 35 | Задачата, от която обикновено се започва е тази за удовлетворимост/изпълнимост. 36 | 37 | \begin{theorem}[Кук-Левин] 38 | Задачата \textbf{SAT} е \NP-пълна. 39 | \end{theorem} 40 | 41 | След това се показва, че $\mathbf{SAT} \leq_p \mathbf{3SAT}$. 42 | Ние вече знаем, че тя е в класа \NP, така че ако направим тази редукция, ще излезе, че \textbf{3SAT} е \NP-пълна задача. 43 | Можем да направим следната редукция -- за всеки дизюнкт $D$ във входната формула $\varphi$: 44 | \begin{itemize} 45 | \item ако в $D$ участва точно един литерал $L$, то тогава избираме нови променливи $x$ и $y$ и заменяме $D$ с конюнкцията на дизюнктите $(L \lor x \lor y)$, $(L \lor x \lor \overline{y}), (L \lor \overline{x} \lor y)$ и $(L \lor \overline{x} \lor \overline{y})$; 46 | \item ако в $D$ участват два литерала $L_1, L_2$, то тогава избираме нова променлива $x$ и заменяме $D$ с конюнкцията на дизюнктите $(L_1 \lor L_2 \lor x)$ и $(L_1 \lor L_2 \lor \overline{x})$; 47 | \item ако в $D$ участват три литерала, не променяме $D$; 48 | \item ако в $D$ участват литералите $L_1, \dots, L_n$, където $n > 3$, то тогава избираме нови променливи $x_1, \dots, x_{n - 3}$ и заменяме $D$ с конюнкцията на дизюнктите 49 | \[ 50 | (L_1 \lor L_2 \lor x_1), (L_3 \lor \overline{x_1} \lor x_2), (L_4 \lor \overline{x_2} \lor x_3), \dots, (L_{n - 2} \lor \overline{x_{n - 4}} \lor x_{n - 3}), (L_{n - 1} \lor L_n \lor \overline{x_{n - 3}}). 51 | \] 52 | \end{itemize} 53 | Накрая ще получим като резултат формула $\psi$, която не е еквивалентна на $\varphi$, но е изпълнима т.с.т.к. $\varphi$ е изпълнима. 54 | На читателя оставяме да провери, че това е вярно. 55 | Остана само да проверим, че всичко това става за полиномиално време. 56 | Дължината на $\psi$ ще бъде полиномиална спрямо тази на $\varphi$, защото: 57 | \begin{itemize} 58 | \item дизюнктите с един литерал се заменят с четири дизюнкта с три литерала; 59 | \item дизюнктите с два литерала се заменят с два дизюнкта с три литерала; 60 | \item дизюнктите с три литерала не се променят; 61 | \item дизюнктите с $n > 4$ литерала се заменят с $n - 2$ дизюнкта с три литерала. 62 | \end{itemize} 63 | 64 | \newpage 65 | 66 | По-съмнителното е търсенето на нови променливи, но и това няма как да е прекалено бавно, защото броят на променливите, които участват в една формула е по-малък или равен на дължината ѝ. 67 | Тъй като ние ще получим формула с полиномиална на $\varphi$ дължина, в нея ще участват най-много полиномиално на $\varphi$ променливи. 68 | Това означава, че ако всеки път търсим неизползваната променлива с най-малък индекс, няма да търсим прекалено дълго. 69 | С това получаваме, че \textbf{3SAT} е \NP-трудна задача, и понеже сме показвали че е в класа \NP, то тя е и \NP-пълна. 70 | 71 | \section{Класически \NP-пълни задачи} 72 | 73 | Ще покажем няколко класически примера за \NP-пълни задачи. 74 | Тъй като за тях сме доказвали, че са в класа \NP, ни остава само да покажем, че са \NP-трудни. 75 | Нека сега покажем, че $\mathbf{3SAT} \leq_p \mathbf{Clique}$. 76 | Нека $\varphi$ е формула в 3КНФ със $n$ на брой дизюнкта. 77 | За полиномиално време ще построим граф $G$, за които $\varphi$ е изпълнима т.с.т.к. $G$ съдържа $n$-клика. 78 | За всеки дизюнкт $(L_1(x) \lor L_2(y) \lor L_3(z))$, който участва във $\varphi$, в графа $G$ има върховете от вида $\{ \opair{x, v_x}, \opair{y, v_y}, \opair{z, v_z} \}$, където $v_x, v_y, v_z \in \{ \T, \F \}$, и интерпретирайки $t$ като $v_t$ за $t \in \{ x, y, z \}$, дизюнктът $(L_1(x) \lor L_2(y) \lor L_3(z))$ се оценява като $\T$. 79 | Ребра ще има между тези множества, които не си противоречат, т.е. няма променлива $x$, за която $\opair{x, \T}$ да участва в едното множество и $\opair{x, \F}$ да участва в другото. 80 | По построение е очевидно, че в $G$ има $n$ клика т.с.т.к. $\varphi$ е изпълнима. 81 | В едната посока кликата ни казва точно как да оценим променливите, а в другата посока от оценката можем да извлечем кликата. 82 | Тъй като за всеки дизюнкт получаваме най-много $2^3$ върхове, то тогава конструкцията е полиномиална. 83 | С това показахме, че задачата \textbf{Clique} е \NP-трудна, откъдето е и \NP-пълна. 84 | 85 | Сега нека да видим, че $\mathbf{Clique} \leq_p \mathbf{VertexCover}$. 86 | За входния граф $G = \opair{V, E}$ строим $\overline{G} = \opair{V, \overline{E}}$, където: 87 | \[ 88 | \overline{E} = \{ (u, v) \mid u, v \in V \: \& \: (u, v) \notin E \}. 89 | \] 90 | Този граф очевидно се строи за време $\Theta(|V|^2)$. 91 | 92 | Оказва се, че за всяко $X \subseteq V$ е изпълнено, че: 93 | \begin{center} 94 | $X$ е клика в $G$ $\iff$ $V \setminus X$ е върхово покритие в $\overline{G}$. 95 | \end{center} 96 | 97 | \newpage 98 | 99 | $(\Rightarrow)$ 100 | Нека $X$ е клика в $G$. 101 | Тогава което и ребро $(u, v) \in \overline{E}$ да вземем, $u \notin X$ или $v \notin X$. 102 | Ако $u, v \in X$, то тогава тъй като $(u, v) \in \overline{E}$, то тогава $(u, v) \notin E$, което противоречи с факта, че $X$ е клика. 103 | Така наистина $V \setminus X$ е върхово покритие в $\overline{G}$. 104 | 105 | $(\Leftarrow)$ 106 | Нека $X$ не е клика в $G$. 107 | Тогава има два върха $u, v \in X$, за които $(u, v) \notin E$. 108 | Но тогава $(u, v) \in \overline{E}$, откъдето $V \setminus X$ не е върхово покритие в $\overline{G}$. 109 | 110 | Псевдокодът на редукцията би изглеждал така: 111 | \VerbatimInput[numbersep = 3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3}]{algorithms/clique-to-vertex-cover.txt} 112 | С това показахме, че \textbf{VertexCover} е \NP-трудна задача, откъдето е и \NP-пълна. 113 | 114 | Сега ще покажем, че $\mathbf{3SAT} \leq_p \mathbf{SubsetSum}$. 115 | Нека е дадена формула $\varphi$ в 3КНФ, в която участват променливите $x_1, \dots, x_m$ и дизюнктите $D_1, \dots, D_l$. 116 | Ще построим масив $A[1 \dots n]$ и число $k$, за които: 117 | \begin{center} 118 | $\varphi$ е изпълнима $\iff$ има $S \subseteq \{ 1, \dots, n \}$, за което $k = \sum\limits_{t \in S} A[t]$. 119 | \end{center} 120 | За всяка променлива $x_i$ строим числа $t_i, f_i$ със $m + l$ цифри по следния начин: 121 | \begin{itemize} 122 | \item $i$-тата цифра на $t_i$ и $f_i$ e $1$; 123 | \item за $m + 1 \leq j \leq m + l$, $j$-тата цифра на $t_i$ е $1$, ако $x_i$ участва в $D_{j - m}$; 124 | \item за $m + 1 \leq j \leq m + l$, $j$-тата цифра на $f_i$ е $1$, ако $\overline{x_i}$ участва в $D_{j - m}$; 125 | \item всички други цифри на $t_i$ и $f_i$ са $0$. 126 | \end{itemize} 127 | Очевидно всички тези числа могат да се построят за полиномиално на дължината на $\varphi$ време. 128 | 129 | Сега за всеки дизюнкт $D_j$ строим числа $h_j$ и $g_j$ със $m + l$ цифри по следния начин: 130 | \begin{itemize} 131 | \item $(m + j)$-тата цифра на $h_j$ и $g_j$ е $1$; 132 | \item всички други цифри на $h_j$ и $g_j$ са $0$. 133 | \end{itemize} 134 | 135 | \newpage 136 | Очевидно всички тези числа могат да се построят за полиномиално на дължината на $\varphi$ време. 137 | 138 | Накрая нека $k = \underbrace{1 \dots 1}_{m \text{ пъти}} \underbrace{3 \dots 3}_{l \text{ пъти}}$. 139 | Това число също се строи за полиномиално на дължината на $\varphi$ време. 140 | 141 | Накрая получаваме вход $[t_1, f_1, \dots t_m, f_m, h_1, g_1, \dots, h_l, g_l]$ и $k$ за \textbf{SubsetSum}. 142 | 143 | Нека $\varphi$ е изпълнима т.е. е вярна при оценка $v$. 144 | Тогава за $1 \leq i \leq m$ избираме $t_i$, ако $v(x_i) = \T$, иначе избираме $f_i$. 145 | Тъй като тези числа не се бият в първите $m$ цифри, те ще бъдат точно като в $k$, стига да не сумираме прекалено големи цифри от дясната част. 146 | За останалите $l$ цифри на $k$ можем да забележим, че в една формула ще са верни между $1$ и $3$ променливи. 147 | Тогава сумирайки избраните числа, за всяко $m + 1 \leq j \leq m + l$ знаем, че $j$-тата цифра ще е между $1$ и $3$. 148 | Така можем просто да изберем достатъчно числа от $h_j$ и $g_j$, че да добутаме до $3$. 149 | 150 | Обратно, нека имаме $I \subseteq \{ 1, \dots, 2m + 2l \}$, за което $k = \sum\limits_{i \in I} A[i]$. 151 | От структурата на числото $k$ лесно се вижда, че за всяко $1 \leq i \leq m$ точно едно от $t_i$ и $f_i$ участва в $\{ A[i] \mid i \in I \}$. 152 | Нека $v(x_i) = \T$ при $t_i \in S$ и $v(x_i) = \F$, иначе. 153 | Ако има такъв дизюнкт $D_j$, който при оценката $v$ се остойностява като $\F$, то тогава $(m + j)$-тата цифра на $k$ щеше да е строго по-малка от $3$. 154 | Следователно всички дизюнкти във $\varphi$ са верни при оценка $v$, откъдето $\varphi$ е изпълнима. 155 | 156 | Псевдокодът на редукцията би изглеждал така: 157 | \VerbatimInput[numbersep = 3pt, frame=single, numbers=left,commandchars=\\\{\},codes={\catcode`$=3\catcode`_=8}]{algorithms/3sat-to-subset-sum.txt} 158 | С това показахме, че \textbf{SubsetSum} е \NP-трудна задача, откъдето е и \NP-пълна. -------------------------------------------------------------------------------- /asymptotic-analysis.tex: -------------------------------------------------------------------------------- 1 | \chapter{Въведение в алгоритмите и асимптотичния анализ} 2 | 3 | \section{Що е то алгоритъм?} 4 | 5 | Алгоритмите се срещат навсякъде около нас: 6 | \begin{itemize} 7 | \item рецептите са алгоритми за готвене; 8 | \item сутрешното приготвяне; 9 | \item придвижването от точка A до точка B; 10 | \item търсенето на книга в библиотеката. 11 | \end{itemize} 12 | 13 | Въпреки това е трудно да се даде формална дефиниция на това какво точно е алгоритъм. 14 | На ниво интуиция, човек може да си мисли, че това просто е някакъв последователен списък от стъпки/инструкции, които човек/машина трябва да изпълни. 15 | Други начини човек да си мисли за алгоритмите, са: 16 | \begin{itemize} 17 | \item програми -- обикновено така се реализират алгоритми; 18 | \item машини на Тюринг, крайни (стекови) автомати или формални граматики; 19 | \item частично рекурсивни функции. 20 | \end{itemize} 21 | 22 | Един програмист в ежедневието си постоянно пише алгоритми за да решава различни задачи/проблеми. 23 | Една задача може да се решава по много начини, някои по-добри от други. 24 | Добрият програмист, освен че ще намери решение на проблема, той ще намери най-доброто решение (или поне достатъчно добро за неговите цели). 25 | 26 | \section{Какво означава добро решение?} 27 | 28 | Хубаво е човек да се води по следните (неизчерпателни) критерии: 29 | \begin{itemize} 30 | \item решението трябва да е коректно -- ако алгоритъмът работи само през 50\% от времето, най-вероятно можем да се справим по-добре; 31 | \item решението трябва да е бързо -- ако алгоритъмът ще завърши работа след като всички звезди са измрели, то той практически не ни върши работа; 32 | \item решението трябва да заема малко памет -- ако алгоритъмът по време на своята работа се нуждае от повече памет, колкото компютърът може да предостави, за нас този алгоритъм е безполезен; 33 | \item решението трябва да е просто -- това е може би най-маловажният критерии от тези, но въпреки това е хубаво когато човек може, да пише чист и разбираем код, който лесно се разширява. 34 | \end{itemize} 35 | 36 | За да можем да сравняваме алгоритми в зависимост от това колко големи ресурси (време и памет) използват, трябва първо да можем да ``измерваме'' тези ресурси. 37 | 38 | \section{Как мерим времето и паметта?} 39 | 40 | Когато пишем алгоритми, имаме няколко базови инструкции (за които предварително сме се уговорили), които ще наричаме \textbf{атомарни инструкции}. 41 | Тяхното извикване ще отнеме една единица време. 42 | \textbf{Време за изпълнение} ще наричаме броят на извикванията на атомарните инструкции по време на изпълнение на програмата. 43 | Също така числата и символите ще бъдат нашите \textbf{атомарни типове данни}, и ще заемат една единица памет. 44 | \textbf{Паметта}, която една програма заема, ще наричаме максималния брой на единици от атомарни типове данни по време на изпълнение, без да броим входните данни. 45 | Обикновено времето и паметта зависят от размера на подадените входни данни. 46 | Това означава, че можем да си мислим за времето и паметта като функции на размера на входа. 47 | Подходът, който ще изберем, е да сравняваме функциите за време/памет на различните алгоритми асимптотично. 48 | Интересуваме се не толкова от конкретните стойности, а от поведението им, когато размерът на входа клони към безкрайност. 49 | 50 | \section{Основни дефиниции} 51 | \footnotetext[1]{точност до константен множител и константно събираемо} 52 | Множеството от функции, които ще анализираме, е 53 | \[ 54 | \calF = \{ f \mid f : \R^{\geq 0} \rightarrow \R \: \& \: (\exists n_0 > 0) (\forall n \geq n_0) (f(n) > 0) \}. 55 | \] 56 | 57 | 58 | \begin{definition} 59 | За всяка функция $f \in \calF$ дефинираме: 60 | \begin{align*} 61 | \Theta(f) = \{ g \in \calF \: \mid & (\exists c_1 > 0)(\exists c_2 > 0) \\ 62 | & (\exists n_0 \geq 0)(\forall n \geq n_0)(c_1 \cdot f(n) \leq g(n) \leq c_2 \cdot f(n))\}. 63 | \end{align*} 64 | 65 | \end{definition} 66 | Може да тълкуваме $\Theta(f)$ като: 67 | \begin{center} 68 | \textit{``множеството от функциите, които растат\footnotemark[1] със скоростта на $f$''.} 69 | \end{center} 70 | 71 | 72 | Нека вземем за пример $f(n) = 3n + 1$ и $g(n) = n + 200$: 73 | 74 | \begin{tikzpicture} 75 | \begin{axis}[axis lines = left, xlabel = \(n\), ylabel = {\(h(n)\)}] 76 | \addplot[domain = 0:1000, color=red]{x+200}; 77 | \addlegendentry{$g(n)$}; 78 | \addplot[domain = 0:1000, color=blue]{(3*x)+1}; 79 | \addlegendentry{$f(n) (c_2 = 1)$}; 80 | \addplot[domain = 0:1000, color=green]{((3*x)+1)/4}; 81 | \addlegendentry{$\frac{f(n)}{4} (c_1 = \frac{1}{4})$}; 82 | \end{axis} 83 | \end{tikzpicture} 84 | 85 | На картинката се вижда как от един момент нататък, функцията $g$ остава ``заключена`` между $c_1 \cdot f$ и $c_2 \cdot f$. 86 | Точно заради това $g \in \Theta(f)$. 87 | \begin{remark} 88 | Вместо да пишем $g \in \Theta(f)$, ще пишем $g = \Theta(f)$ или $g \asymp f$. 89 | \end{remark} 90 | 91 | \newpage 92 | 93 | \begin{definition} 94 | За всяка функция $f \in \calF$ дефинираме: 95 | \begin{align*} 96 | O(f) = \{ g \in \calF \: \mid (\exists c > 0)(\exists n_0 \geq 0)(\forall n \geq n_0)(g(n) \leq c \cdot f(n))\}. 97 | \end{align*} 98 | \end{definition} 99 | Може да тълкуваме $O(f)$ като: 100 | \begin{center} 101 | \textit{``множеството от функциите, които не растат\footnotemark[1] по-бързо от $f$''.} 102 | \end{center} 103 | 104 | 105 | Тук заслабваме условията от $\Theta(f)$ като искаме само горната граница. 106 | 107 | За пример човек може да вземе $f(n) = n^2$ и $g(n) = n$: 108 | 109 | \begin{tikzpicture} 110 | \begin{axis}[axis lines = left, xlabel = \(n\), ylabel = {\(h(n)\)}] 111 | \addplot[domain = 0:10, color=red]{x*x}; 112 | \addlegendentry{$f(n)$}; 113 | \addplot[domain = 0:10, color=blue]{x}; 114 | \addlegendentry{$g(n)$}; 115 | \end{axis} 116 | \end{tikzpicture} 117 | 118 | \begin{remark} 119 | Вместо да пишем $g \in O(f)$, ще пишем $g = O(f)$ или $g \preceq f$. 120 | \end{remark} 121 | 122 | \begin{definition} 123 | За всяка функция $f \in \calF$ дефинираме: 124 | \begin{align*} 125 | o(f) = \{ g \in \calF \: \mid (\forall c > 0)(\exists n_0 \geq 0)(\forall n \geq n_0)(g(n) < c \cdot f(n))\}. 126 | \end{align*} 127 | \end{definition} 128 | Може да тълкуваме $o(f)$ като: 129 | \begin{center} 130 | \textit{``множеството от функциите, които растат\footnotemark[1] по-бавно от $f$''.} 131 | \end{center} 132 | Разликата между $O(f)$ и $o(f)$ е строгото неравенство и универсалният квантор в началото. 133 | Лесно се вижда, че $o(f) \subseteq O(f)$. 134 | Тук изключваме функциите от същия порядък. 135 | \begin{remark} 136 | Вместо да пишем $g \in o(f)$, ще пишем $g = o(f)$ или $g \prec f$. 137 | \end{remark} 138 | 139 | \begin{definition} 140 | За всяка функция $f \in \calF$ дефинираме: 141 | \begin{align*} 142 | \Omega(f) = \{ g \in \calF \: \mid (\exists c > 0)(\exists n_0 \geq 0)(\forall n \geq n_0)(c \cdot f(n) \leq g(n))\}. 143 | \end{align*} 144 | \end{definition} 145 | Може да тълкуваме $\Omega(f)$ като: 146 | \begin{center} 147 | \textit{``множеството от функциите, които не растат\footnotemark[1] по-бавно от $f$''.} 148 | \end{center} 149 | Това е дуалното множество на $O(f)$. 150 | \begin{remark} 151 | Вместо да пишем $g \in \Omega(f)$, ще пишем $g = \Omega(f)$ или $g \succeq f$. 152 | \end{remark} 153 | 154 | \begin{definition} 155 | За всяка функция $f \in \calF$ дефинираме: 156 | \begin{align*} 157 | \omega(f) = \{ g \in \calF \: \mid (\forall c > 0)(\exists n_0 \geq 0)(\forall n \geq n_0)(c \cdot f(n) < g(n))\}. 158 | \end{align*} 159 | \end{definition} 160 | Може да тълкуваме $\omega(f)$ като: 161 | \begin{center} 162 | \textit{``множеството от функциите, които растат\footnotemark[1] по-бързо от $f$''.} 163 | \end{center} 164 | Това е дуалното множество на $o(f)$. 165 | \begin{remark} 166 | Вместо да пишем $g \in \omega(f)$, ще пишем $g = \omega(f)$ или $g \succ f$. 167 | \end{remark} 168 | 169 | \begin{warning} 170 | Не всички функции от $\calF$ са сравними по релациите $\prec, \preceq$ или $\asymp$. 171 | 172 | За пример човек може да вземе функциите $f(n) = n$ и $g(n) = n^{1 + \sin(n)}$. 173 | Лесно се вижда, че функцията $g(n)$ ``плава'' между $n^0 = 1$ и $n^2$ т.е. няма нито как да расте по-бързо, нито как да расте по-бавно. 174 | \end{warning} 175 | 176 | Въпреки това, тези релации са сравнително хубави. 177 | \begin{claim} 178 | Следните свойства са в сила: 179 | \begin{itemize} 180 | \item $\asymp$ е релация на еквивалентност; 181 | \item $\prec$ и $\succ$ са транзитивни и антирефлексивни; 182 | \item $\preceq$ и $\succeq$ са транзитивни и рефлексивни. 183 | \end{itemize} 184 | \end{claim} 185 | 186 | Доказателството на това твърдение оставяме за упражнение на читателя. 187 | То е една елементарна разходка из дефинициите. 188 | 189 | \newpage 190 | 191 | \section{Полезни свойства} 192 | 193 | Тук ще изброим няколко свойства, които много често се ползват в задачите: 194 | \begin{itemize} 195 | \item Нека $f, g \in \calF$ и $\lim\limits_{n \rightarrow \infty} \frac{f(n)}{g(n)} = l$ (тук искаме границата да съществува). 196 | Тогава: \\ 197 | --- ако $l = 0$, то $f \prec g$; \\ 198 | --- ако $l = \infty$, то $f \succ g$; \\ 199 | --- в останалите случаи $f \asymp g$. 200 | \item $f + g \asymp \max\{f, g\}$ за всяко $f, g \in \calF$. 201 | \item $c \cdot f \asymp f$ за всяко $f \in F$ и $c > 0$. 202 | \item $f \asymp g \iff f^c \asymp g^c$ за всяко $f, g \in F$ и $c > 0$. 203 | \item $O(f) \cap \Omega(f) = \Theta(f)$ за всяко $f \in \calF$. 204 | \item $o(f) \cap \omega(f) = O(f) \cap \omega(f) = o(f) \cap \Omega(f) = \varnothing$ за всяко $f \in \calF$. 205 | \item $f \prec g \iff g \succ f$ и $f \preceq g \iff g \succeq f$ за всяко $f, g \in \calF$. 206 | \item Нека $f, g \in \calF$ са растящи и неограничени и $c > 1$. Тогава: 207 | \begin{itemize} 208 | \item ако $f \prec g$, то $c^f \prec c^g$; 209 | \item ако $\log_c(f) \prec \log_c(g)$, то $f \prec g$; 210 | \item ако $c^f \asymp c^g$, то $f \asymp g$; 211 | \item ако $f \asymp g$, то $\log_c(f) \asymp \log_c(g)$. 212 | \end{itemize} 213 | \item Тъй като $\log_a(n) = \frac{\log_b(n)}{\log_b(a)}$, то $\log_a(n) \asymp \log_b(n)$ -- вече ще пишем само $\log(n)$, като ще имаме предвид $\log_2(n)$. 214 | \item $n! \asymp \sqrt{n} \frac{n^n}{e^n}$ -- от апроксимация на Стирлинг. 215 | Тук по-общият резултат е 216 | \[ 217 | \lim\limits_{n \to \infty} \frac{n!}{\sqrt{2 \pi n} \left( \frac{n}{e} \right)^n} = 1. 218 | \] 219 | \item $\log(n!) \asymp n \log(n)$. 220 | \item $\log(n) \prec n^k \prec 2^n \prec n! \prec n^n \prec 2^{n^2}$ за всяко $k \geq 1$. 221 | \end{itemize} 222 | 223 | \begin{claim} 224 | Нека $f \in \calF$ е неограничено растяща и диференцируема, $a > 1$ и $k, l > 0$. 225 | Тогава $(\log_a f(n))^{k} \prec f(n)^{l}$. 226 | \end{claim} 227 | 228 | \begin{proof} 229 | Първо правим следната преработка: 230 | \[ 231 | \frac{f(n)^{l}}{(\log_a f(n))^k} = \frac{f(n)^{l \cdot \frac{k}{k}}}{(\log_a f(n))^k} \stackrel{t = \frac{l}{k}}{=} \frac{f(n)^{tk}}{(\log_a f(n))^k} = \left( \frac{f(n)^t}{\log_a f(n)} \right)^k. 232 | \] 233 | След това забелязваме, че: 234 | \begin{align*} 235 | & \lim\limits_{n \to \infty} \frac{f(n)^t}{\log_a f(n)} & \\ 236 | = & \lim\limits_{n \to \infty} \frac{(f(n)^t)'}{(\log_a f(n))'} & \text{// правило на L'Hôpital} \\ 237 | = & \lim\limits_{n \to \infty} \frac{t f(n)^{t - 1} f'(n)}{\frac{f'(n)}{\ln(a) f(n)}} & \\ 238 | = & \lim\limits_{n \to \infty} t \ln(a) f(n)^t & \text{// } f(n) \text{ е неограничено растяща и } t, \ln(a) > 0 \\ 239 | = & \;\infty. & 240 | \end{align*} 241 | Тъй като $\lim\limits_{n \to \infty} \frac{f(n)^t}{\log_a f(n)} = \infty$ и $k > 0$, то тогава: 242 | \[ 243 | \lim\limits_{n \to \infty} \frac{f(n)^{l}}{(\log_a f(n))^k} = \lim\limits_{n \to \infty} \left( \frac{f(n)^t}{\log_a f(n)} \right)^k = \infty. 244 | \] 245 | Така $(\log_a f(n))^{k} \prec f(n)^{l}$. 246 | \end{proof} 247 | 248 | \section{Задачи} 249 | 250 | \begin{problem} 251 | Да се сравнят асимптотично следните двойки функции: 252 | \begin{enumerate} 253 | \item $f(n) = \log(\log(n))$ и $g(n) = \log(n)$; 254 | \item $f(n) = 5n^3$ и $g(n) = n \sqrt{n^9 + n^5}$; 255 | \item $f(n) = n 5^n$ и $g(n) = n^ 2 3^n$; 256 | \item $f(n) = n^n$ и $g(n) = 3^{n^2}$; 257 | \item $f(n) = 3^{n^2}$ и $g(n) = 2^{n^3}$. 258 | \end{enumerate} 259 | \end{problem} 260 | 261 | \begin{problem} 262 | Да се докаже, че $\sum\limits_{i = 0}^n i^k \asymp n^{k+1}$. 263 | \end{problem} 264 | 265 | \begin{problem} 266 | Да се подредят по асимптотично нарастване следните функции: 267 | \begin{align*} 268 | f_1(n) & = n^2 & f_2(n) & = \sqrt{n} & f_3(n) & = \log^2(n) & f_4(n) & = \sqrt{\log(n)!} \\ 269 | f_5(n) & = \sum\limits_{k = 2}^{\log(n)} \frac{1}{k} & f_6(n) & = \log(\log(n)) & f_7(n) & = 2^{2^{\sqrt{n}}} & f_8(n) & = \binom{\binom{n}{3}}{2} \\ 270 | f_9(n) & =2^{n^2} & f_{10}(n) & = 3^{n \sqrt{n}} & f_{11}(n) & =2^{\binom{n}{2}} & f_{12}(n) & = \sum\limits_{k = 1}^{n^2} \frac{1}{2^k}. 271 | \end{align*} 272 | \end{problem} 273 | 274 | \begin{problem} 275 | Да се докаже, че съществуват $f, g \in \calF$, за които $f \asymp g$ и въпреки това $\lim\limits_{n \to \infty} \frac{f(n)}{g(n)}$ не съществува. 276 | \end{problem} -------------------------------------------------------------------------------- /lower-bounds.tex: -------------------------------------------------------------------------------- 1 | \chapter{Долни граници} 2 | 3 | \section{Какво са долни граници?} 4 | 5 | Дойде времето за по-депресиращите резултати в курса. 6 | До сега единственото, което правихме, беше да показваме, че за задача $\mathbf{X}$ може да се напише алгоритъм със времева сложност $O(f)$ или $\Theta(f)$ за някое $f \in \mathcal{F}$. 7 | Доста естествено изниква следния въпрос: 8 | \begin{center} 9 | \textit{Възможно ли е да съществува по-бърз алгоритъм, който решава задачата $\mathbf{X}$?} 10 | \end{center} 11 | Ясно е, че е неприемлив отговор от сорта на 12 | \begin{center} 13 | \textit{Не мога да измисля такъв алгоритъм, следователно такъв алгоритъм не съществува.} 14 | \end{center} 15 | В тази тема ще се опитаме да отговорим в някакъв смисъл положително на въпроси от този тип. 16 | Преди това нека въведем няколко дефиниции. 17 | 18 | \textbf{Изчислителна задача} ще наричаме всяко множество от наредени двойки $\mathbf{X}$, като: 19 | \begin{itemize} 20 | \item $\operatorname{Dom}(\mathbf{X})$ ще наричаме \textbf{вход}; 21 | \item $\operatorname{Rng}(\mathbf{X})$ ще наричаме \textbf{изход}. 22 | \end{itemize} 23 | За пример можем да вземем изчислителната задача \textbf{Sort}: 24 | 25 | \vspace*{2mm} 26 | \textbf{Вход:} Целочислен масив $A[1 \dots n]$. 27 | 28 | \textbf{Изход:} Пермутация $A'[1 \dots n]$ на $A[1 \dots n]$, за която $A'[1] \leq A'[2] \leq \dots \leq A'[n]$. 29 | \vspace*{2mm} 30 | 31 | \newpage 32 | 33 | Ще казваме, че \textbf{алгоритъм} $\mathbf{AlgX}$ \textbf{решава задачата} $\mathbf{X}$, ако за всяко $x \in \operatorname{Dom}(\mathbf{X})$ е изпълнено $\opair{x, \mathbf{AlgX}(x)} \in \mathbf{X}$. 34 | Нека $\mathbf{X}$ е изчислителна задача и нека $f \in F$. 35 | Тогава: 36 | \begin{itemize} 37 | \item Казваме, че $f$ е \textbf{долна граница} за $\mathbf{X}$, ако всеки алгоритъм, който решава $\mathbf{X}$, работи за време (или брой операции от конкретен вид) поне $f$\footnote{ 38 | Тук се има предвид, че ако $T(n)$ е броят на стъпки (или операции от конкретен вид), за който алгоритъма завършва при вход с големина $n$, то $f(n) \leq T(n)$. 39 | }. 40 | \item Казваме, че $\Omega(f)$ е \textbf{долна граница} за $\mathbf{X}$, ако всеки алгоритъм, който решава $\mathbf{X}$, работи за време (или брой операции от конкретен вид) $\Omega(f)$. 41 | \end{itemize} 42 | \section{Техники за изследване на долни граници} 43 | 44 | Най-често се използват следните техники, които показват че задача $\mathbf{X}$ има долна граница за време $f$ (или $\Omega(f)$): 45 | \begin{itemize} 46 | \item директни разсъждения за конкретния пример -- тази техника обикновено се използва в малки задачи, където човек за сравнително малко време може да направи пълен анализ. 47 | Разбира се, тази техника се използва и при по-обобщените примери, но не толкова често. 48 | \item дърво на взимане на решения -- тази техника се използва в задачи, където за решаването им се изисква задаването на редица въпроси, чиите отговор ни дава все повече и повече информация. 49 | Можем да си мислим за запитванията заедно с информацията, която носят, като едно дърво. 50 | Всеки въпрос ще разклонява дървото, докато накрая имаме цялата ни нужна информация в листата, и не трябва да задаваме повече въпроси. 51 | Тогава долната граница ще бъде височината на дървото. 52 | \item аргументация чрез противник -- тази техника е трудна да се обясни без да се даде пример. 53 | Идеята е, че играем срещу противник, като нашата цел е да разкрием някаква информация, която уж е била предварително фиксирана. 54 | Противника обаче си измисля информацията на момента, като целта му е да ни накара да зададем колкото се може повече въпроси и в отговорите му на въпросите да няма противоречия. 55 | \item чрез редукция\footnote{ 56 | Това е може би най-приложимата техника от всички. 57 | Тя се използва не само в контекста на сложност. 58 | Оказва се, че е много удобно човек да може да говори за това дали една задача е \textit{``по-трудна''} от друга в контекста на разрешимост. 59 | } -- ако знаем, че можем алгоритмично да сведем задача $\mathbf{Y}$ до задача $\mathbf{X}$ за време $o(f)$ и $\mathbf{Y}$ има долна граница за време (или брой операции от конкретен вид) $\Omega(f)$, то тогава второто е изпълнено и за задача $\mathbf{X}$. 60 | В някакъв смисъл тази редукция казва, че задачата $\mathbf{X}$ е поне толкова трудна, колкото задачата $\mathbf{Y}$. 61 | \end{itemize} 62 | 63 | \section{Техниките в действие} 64 | 65 | Ще започнем със пример за аргументация с противник. 66 | Решаваме задачата за максимален елемент -- даден ни е като вход целочислен масив $A[1 \dots n]$ и искаме да получим като изход $\max \{ A[i] \mid 1 \leq i \leq n \}$. 67 | Твърдим, че всеки алгоритъм, който решава задачата, използва поне $n - 1$ сравнения. 68 | Ако при работа на алгоритъма е проверено условието ``$A[i] \leq A[j]$'' и е върнало $\T$, ще казваме, че $A[i]$ е загубило това сравнение и $A[j]$ е спечелило това сравнение. 69 | Нека $A[i]$ е максималният елемент на $A[1 \dots n]$. 70 | Тогава за всяко $j \neq i$ имаме, че $A[j]$ е загубило сравнение. 71 | Ако има $A[j]$, което не е загубило сравнение, то тогава $A[j]$ не е сравнявано с $A[i]$. 72 | Ако сменим $A[j]$ със $A[i] + 1$, то тогава при изпълнението на алгоритъма резултатите (от сравненията) няма да се променят, и алгоритъмът ще върне $A[i]$, което е абсурд. 73 | Тъй като при всяко сравнение един връх печели, а другия губи, то за да постигнем тези $n - 1$ загуби ни трябват $n - 1$ сравнения. 74 | Ако си представим какво прави стандартния алгоритъм за намиране на максимум, той постоянно поддържа победител. 75 | Започва с един елемент, и когато той загуби, го заменя с друг. 76 | Накрая ще сме завършили с елемент, който никога не е губил, и е транзитивно е победил всички останали. 77 | 78 | Нека сега дадем пример за дърво на вземане на решения. 79 | Разглеждаме задачата \textbf{Sort}. 80 | Ще покажем, че всеки сортиращ алгоритъм, който работи на базата на директни сравнения, работи за време $\Omega(n \log (n))$. 81 | Нека фиксираме $n \in \N$ и някакъв символ $a$. 82 | Нека $\calT_n$ е множеството от всички дървета, за които е изпълнено, че: 83 | \begin{itemize} 84 | \item върховете са от вида $(a_i < a_j, P)$, където $1 \leq i, j \leq n, \; P \subseteq S_n$\footnote{$S_n$ е симетричната група за $\{ 1, \dots, n \}$.} и $|P| \geq 2$, или са от вида $\sigma \in S_n$; 85 | \item ако $n > 1$, то коренът е $(a_i < a_j, S_n)$ за някои $1 \leq i, j \leq n$, иначе е единственият елемент на $S_n$; 86 | \item за всеки връх от вида $(a_i < a_j, P)$, има $1 \leq k_1, k_2, m_1, m_2 \leq n$, за които: 87 | \begin{itemize} 88 | \item лявото му дете (стига да е добре дефинирано т.е. дясната компонента не е $\varnothing$) е наредената двойка $(a_{k_1} < a_{m_1}, \{ \sigma \in P \mid \sigma(i) < \sigma(j) \})$ (или ако се получава само една пермутация, само тя), и 89 | \item дясното му дете (стига да е добре дефинирано т.е. дясната компонента не е $\varnothing$) е наредената двойка $(a_{k_2} < a_{m_2}, \{ \sigma \in P \mid \sigma(i) \not< \sigma(j) \})$ (или ако се получава само една пермутация, само тя). 90 | \end{itemize} 91 | \end{itemize} 92 | Тогава ако вземем произволен алгоритъм за сортиране $\mathbf{AlgX}$, който е базиран на сравнение, при пресмятането на $\mathbf{AlgX}(A[1 \dots n])$, можем да забележим, че траверсираме някое дърво $T \in \calT_n$ от корен до листо. 93 | В корена се намира първото запитване $a_i < a_j$ (т.е. можем да си мислим, че питаме дали $A[i] < A[j]$, където $A[i], A[j]$ са първоначалните стойности на входния масив), и спрямо отговора на дадено запитване ние се движим наляво или надясно в дървото. 94 | Накрая се намираме в листо $\sigma \in S_n$, която задава сортирана пермутация $A'[1 \dots n]$ на $A[1 \dots n]$ така -- $A'[i] = A[\sigma(i)]$. 95 | Това означава, че за всяко сравнение по време на работа на $\mathbf{AlgX}(A[1 \dots n])$ можем да си мислим, че минаваме през едно ребро в $T$. 96 | В най-лошия случай входът $A[1 \dots n]$ би бил такъв, че да трябва да изминем максимален път от корен до листо т.е. път с дължина $h(T)$ (височината на дървото). 97 | Но $T$ е двоично дърво с $n!$ листа и разклоненост $2$, следователно $h(T) \geq \log_2(n!)$. 98 | Така в този случай извикването $\mathbf{AlgX}(A[1 \dots n])$ ще използва поне $\log(n!)$ сравнения. 99 | Тъй като $\log(n!) \asymp n \log(n)$, получаваме че всеки сортиращ алгоритъм, базиран на сравнения, прави $\Omega(n \log(n))$ сравнения. 100 | Нещо повече, това ще е вярно и за масив от естествени числа. 101 | Тук никъде не се възползваме от това, че числата са цели. 102 | Възползвахме се от това, че са безкрайно много. 103 | 104 | Нека сега покажем един пример с редукция. 105 | Разглеждаме изчислителната задача \textbf{Матрьошки}: 106 | 107 | \textbf{Вход:} Масив $T[1 \dots n]$ със елементи от вида $(l, w, h)$ -- дължините, широчините, височините на $n$ играчки с форма на правоъгълен паралелепипед, които могат да се вложат една в друга. 108 | 109 | \textbf{Изход:} Ред на влагане на играчките, като вътрешната играчка е първа. 110 | 111 | Оказва се, че тази задача се решава (на базата на директни сравнения) за време $\Omega(n \log(n))$. 112 | Ще покажем това като сведем задачата за сортиране на естествени числа към \textbf{Матрьошки}. 113 | Нека $\mathbf{AlgM}$ е алгоритъм, който решава задачата \textbf{Матрьошки} със сложност по време $f(n)$. 114 | 115 | \newpage 116 | 117 | Тогава следният алгоритъм очевидно сортира масива от естествени числа $A[1 \dots n]$: 118 | \begin{enumerate} 119 | \item Декларираме нов масив $T[1 \dots n]$. 120 | \item За всяко $1 \leq i \leq n$ инициализираме $T[i]$ със $(A[i], A[i], A[i])$. 121 | \item Извикваме $\mathbf{AlgM}(T[1 \dots n])$ с резултат $T'[1 \dots n]$. 122 | \item Декларираме нов масив $A'[1 \dots n]$ 123 | \item За всяко $1 \leq i \leq n$ инициализираме $A'[i]$ със най-лявата компонента на $T'[i]$. 124 | \item Връщаме $A'[1 \dots n]$. 125 | \end{enumerate} 126 | Сложността на алгоритъма е $\Theta(n + f(n))$. 127 | Ако $f(n) = o(n \log(n))$, щяхме да получим сортиращ алгоритъм, който работи за време $o(n \log(n))$, което е абсурд. 128 | 129 | \section{Задачи} 130 | 131 | \begin{problem} 132 | Един целочислен масив $A[1 \dots 2n]$ ще наричаме симетричен, ако за всяко $1 \leq i \leq n$ е изпълнено, че $A[i] = A[2n - i + 1]$. 133 | Да се докаже, че сортирането на симетрични масиви чрез сравнения изисква време $\Omega(n \log(n))$. 134 | \end{problem} 135 | 136 | \begin{problem} 137 | Един целочислен масив $A[1 \dots 2n]$ ще наричаме специален, ако за всяко $1 \leq i \leq n$ е изпълнено, че $A[2i] < A[2i - 1]$. 138 | Да се докаже, че сортирането на специални масиви чрез сравнения изисква време $\Omega(n \log(n))$. 139 | \end{problem} 140 | 141 | \begin{problem} 142 | Разглеждаме задачата \textbf{SortPointsCounterClockwise}: 143 | 144 | \vspace*{2mm} 145 | \textbf{Вход:} Точки $P_1, \dots, P_n \in \N \cross \N$. 146 | 147 | \textbf{Изход:} Последователност $P'_1, \dots, P'_n$ на точките $P_1, \dots, P_n$, за която за всяко $1 \leq i < n$ е изпълнено, че най-малкото завъртане от вектора $\overrightarrow{OP'_i}$ към вектора $\overrightarrow{OP'_{i + 1}}$ става обратно на часовниковата стрелка, където $O$ е началото на координатната система. 148 | \vspace*{2mm} 149 | 150 | Да се докаже, че решението на задачата \textbf{SortPointsCounterClockwise} чрез сравнения изисква време $\Omega(n \log(n))$. 151 | \end{problem} 152 | 153 | \newpage 154 | 155 | \begin{problem} 156 | Разглеждаме задачата \textbf{SortPointsAngle}: 157 | 158 | \vspace*{2mm} 159 | \textbf{Вход:} Точки $P_1, \dots, P_n \in \N \cross \N$. 160 | 161 | \textbf{Изход:} Подреждане на точките $P_1, \dots, P_n$ по големина на ъгъла, който сключва радиус-векторът с $Ox$. 162 | \vspace*{2mm} 163 | 164 | Да се докаже, че решението на задачата \textbf{SortPointsAngle} чрез сравнения изисква време $\Omega(n \log(n))$. 165 | \end{problem} 166 | 167 | \begin{problem} 168 | Нека $A[1 \dots n]$ и $B[1 \dots n]$ са сортирани целочислени масиви. 169 | Да се докаже, че $2n - 1$ е долна граница за броя на сравнения при сливането на тези два масива в един сортиран масив $C[1 \dots 2n]$. 170 | \end{problem} 171 | 172 | \begin{problem} 173 | Даден е граф с $2n$ върха. 174 | Интересуват ни въпроси от вида: 175 | \begin{center} 176 | \textit{``Има ли ребро от връх} $i$ \textit{до връх} $j$\textit{?''} 177 | \end{center} 178 | Да се докаже, че $n^2$ е долна граница за броя въпроси, който е нужен, за да установим дали дадения граф е свързан. 179 | \end{problem} 180 | 181 | \begin{problem} 182 | Искаме да познаем число между $1$ и $n$, което някой друг човек е намислил. 183 | За целта можем да му задаваме въпроси (на които той трябва да отговори честно) от вида: 184 | \begin{center} 185 | \textit{``Вярно ли е, че} $k$ \textit{е по-малко от намисленото число?''} 186 | \end{center} 187 | Да се докаже, че $\lceil \log_2(n) \rceil$ е долна граница за броя въпроси, който е нужен, да познаем намисленото число. 188 | \end{problem} 189 | 190 | \begin{problem} 191 | Да се докаже, че сортирането на двоична пирамида чрез сравнения изисква време $\Omega(n \log(n))$. 192 | \end{problem} 193 | 194 | \begin{problem} 195 | Дефинираме вълнист масив индуктивно: 196 | \begin{itemize} 197 | \item Всеки едноелементен масив е вълнист. 198 | \item Масив $A[1 \dots n]$ (където $n > 1$) е вълнист, ако 199 | \begin{enumerate} 200 | \item масивът $A[1 \dots \lfloor \frac{n}{2} \rfloor]$ е сортиран, а 201 | \item масивът $A[\lfloor \frac{n}{2} \rfloor + 1 \dots n]$ е вълнист. 202 | \end{enumerate} 203 | \end{itemize} 204 | Да се докаже, че пермутирането на масив до вълнист изисква време $\Omega(n \log(n))$. 205 | \end{problem} 206 | 207 | \begin{problem} 208 | Да се докаже, че строенето на двоична пирамида от масив $A[1 \dots n]$ изисква $n - 1$ сравнения. 209 | \end{problem} 210 | 211 | \begin{problem} 212 | Разглеждаме задачата \textbf{ElementUniqueness}: 213 | 214 | \vspace*{2mm} 215 | \textbf{Вход:} Целочислен масив $A[1 \dots n]$. 216 | 217 | \textbf{Въпрос:} Вярно ли е, че масивът $A[1 \dots n]$ съдържа само уникални елементи? 218 | \vspace*{2mm} 219 | 220 | Да се докаже, че решението на задачата \textbf{ElementUniqueness} чрез сравнения изисква време $\Omega(n \log(n))$. 221 | \end{problem} 222 | 223 | \begin{problem} 224 | Разглеждаме задачата \textbf{Mode}: 225 | 226 | \vspace*{2mm} 227 | \textbf{Вход:} Целочислен масив $A[1 \dots n]$. 228 | 229 | \textbf{Изход:} Най-често срещано число в $A[1 \dots n]$. 230 | \vspace*{2mm} 231 | 232 | Да се докаже, че решението на задачата \textbf{Mode} чрез сравнения изисква време $\Omega(n \log(n))$. 233 | \end{problem} 234 | 235 | \begin{problem} 236 | Разглеждаме задачата \textbf{ClosestPair}: 237 | 238 | \vspace*{2mm} 239 | \textbf{Вход:} Целочислен масив $A[1 \dots n]$. 240 | 241 | \textbf{Изход:} $\min \{ |A[i] - A[j]| \mid 1 \leq i < j \leq n \}$. 242 | \vspace*{2mm} 243 | 244 | Да се докаже, че решението на задачата \textbf{ClosestPair} чрез сравнения изисква време $\Omega(n \log(n))$. 245 | \end{problem} -------------------------------------------------------------------------------- /dynamic-programming.tex: -------------------------------------------------------------------------------- 1 | \chapter{Динамично програмиране} 2 | 3 | \section{Какво е динамично програмиране?} 4 | 5 | Динамичното програмиране не е нито динамично, нито програмиране. 6 | Това е както оптимизационен метод, така и алгоритмична парадигма, която е разработена от Ричард Белман през 50-те години на миналия век. 7 | В този метод една зачача се разделя на подзадачи по рекурсивен начин. 8 | Той се използва в два случая: 9 | \begin{itemize} 10 | \item при задачи, които имат припокриваща се подструктура т.е. задачата се разделя на подзадачи, които се срещат няколко пъти; 11 | \item при задачи, които имат оптимална подструктура т.е. оптимално решение може да се конструира от оптимални решения на подзадачите. 12 | \end{itemize} 13 | 14 | \section{Прости примери за динамично програмиране} 15 | 16 | Да кажем, че искаме да сметнем $n$-тото число на Фибоначи. 17 | Един начин е да караме по рекурентното уравнение: 18 | \lstinputlisting{algorithms/fib-slow.txt} 19 | Проблемът е, че получаваме експоненциална сложност по време. 20 | Нещо, което можем да направим, е да пазим вече пресметнатите стойности, за да не се налага да ги пресмятаме пак: 21 | \lstinputlisting{algorithms/fib-dp.txt} 22 | Това е пример за задача с припокриваща се подструктура, с решение по схемата \textbf{динамично програмиране}. 23 | Успяхме да решим задачата за линейно време. 24 | 25 | Нека сега видим пример за задача с оптимална подструктура. 26 | Да кажем, че имаме два низа $S_1[1 \dots n]$ и $S_2[1 \dots m]$ и искаме да пресметнем дължината на най-дългата обща подредица на $S_1$ и $S_2$. 27 | Лесно се вижда, че дължината $\operatorname{LCS}_{S_1, S_2}(i, j)$ на най-дългата подредица на $S_1[1 \dots i]$ и $S_2[1 \dots j]$ може да се пресметне рекурсивно така: 28 | \[ 29 | \operatorname{LCS}_{S_1, S_2}(i, j) = \begin{cases} 30 | 0 & \text{, ако } i = 0 \text{ или } j = 0 \\ 31 | \operatorname{LCS}_{S_1, S_2}(i - 1, j - 1) + 1 & \text{, ако } i, j > 0 \text{ и } S_1[i] = S_2[j] \\ 32 | \max\{ \operatorname{LCS}_{S_1, S_2}(i - 1, j), \operatorname{LCS}_{S_1, S_2}(i, j - 1) \} & \text{, ако } i, j > 0 \text{ и } S_1[i] \neq S_2[j] 33 | \end{cases} 34 | \] 35 | Ако искаме да пресметнем това със обикновена рекурсия, отново ще получим експоненциална сложност по време. 36 | Отново можем да направим решение по схемата \textbf{динамично програмиране} със сложност $\Theta(n \cdot m)$. 37 | Единственото, което трябва да се направи, е да се измисли последователност за пресмятане на: 38 | \[ 39 | \begin{pmatrix} 40 | \operatorname{LCS}_{S_1, S_2}(0, 0) & \dots & \operatorname{LCS}_{S_1, S_2}(n, 0) \\ 41 | \vdots & \ddots & \vdots \\ 42 | \operatorname{LCS}_{S_1, S_2}(m, 0) & \dots & \operatorname{LCS}_{S_1, S_2}(n, m) 43 | \end{pmatrix} 44 | \] 45 | Важно е да искаме преди пресмятането на всяка клетка, необходимите за нея данни вече са готови. 46 | Ето как може да стане това: 47 | \lstinputlisting{algorithms/longest-common-subsequence.txt} 48 | 49 | \section{Динамично програмиране за решаване на комбинаторни задачи} 50 | 51 | Вижда се, че тази техника е много удобна за бързо пресмятане на рекурентни зависимости. 52 | Едно приложение е в решаването на комбинаторни задачи. 53 | Да кажем, че искаме да пресметнем бързо ${n \choose k}$. 54 | 55 | Нека първо си припомним дефиницията: 56 | \[ 57 | {n \choose k} = \frac{n!}{k! (n - k)!}. 58 | \] 59 | 60 | Ние ще пресметнем ${n \choose k}$ точно по този начин. 61 | За тази цел единственото нещо, което се иска да сметнем трите стойности -- $n!, k!$ и $(n - k)!$. 62 | 63 | \newpage 64 | 65 | Това става елементарно по следния начин: 66 | \lstinputlisting{algorithms/binomial-factorial.txt} 67 | Получихме сложност по време $\Theta(n)$ т.е. имаме сравнително бързо решение. 68 | Обаче то не е практично. 69 | Проблемът е, че функцията $n!$ расте много бързо. 70 | Ние ще работим с големи стойности във $F[0 \dots n]$, но крайният отговор ще е много по-малък от това. 71 | Ако искаме да си гарантираме възможно най-малки стойности по време на изчисление, трябва да го пресметнем за време $\Theta(n \cdot k)$ чрез формулата: 72 | \[ 73 | {n \choose k} = {n - 1 \choose k - 1} + {n - 1 \choose k}. 74 | \] 75 | 76 | Нека сега се опитаме да пресметнем броят $T_n$\footnote{Тези числа се наричат числа на Каталан.} на двоични дървета за търсене с $n$ различни върха. 77 | При $n = 0$ положението е ясно. 78 | Ако $n \geq 1$, то имаме няколко случая в зависимост от това кой връх е корен на дървото. 79 | Броят на двоичните дървета за търсене с корен $i$-тия по големина връх, където $1 \leq i \leq n$, е равен на броя двоичните дървета с $(i - 1)$-те по-малки от него върха, умножен по броя на двоичните дървета с останалите $n - i$ върха. 80 | Това е точно $T_{i - 1} \cdot T_{n - i}$. 81 | Така получаваме следното рекурентно уравнение: 82 | \begin{align*} 83 | T_0 & = 1 \\ 84 | T_n & = \sum\limits_{i = 1}^n T_{i - 1} \cdot T_{n - i} \text{ за } n > 0 85 | \end{align*} 86 | Ясно е, че не искаме да пресмятаме $T_n$ чрез рекурсия -- това би било кошмарно бавно. 87 | 88 | \newpage 89 | 90 | Отново ще помним предишни изчисления, за да си забързаме алгоритъма до такъв със сложност $\Theta(n^2)$: 91 | \lstinputlisting{algorithms/catalan.txt} 92 | 93 | \section{Два интересни примера} 94 | 95 | За първия пример, нека имаме някакъв краен речник $D$ от непразни думи над $\Sigma = \{ a, \dots, z \}$. 96 | Ще искаме при подаден низ над $\Sigma$ да видим дали той може да се разбие на думи от речника (с възможни повторения). 97 | Нека например да вземем речника $D = \{ \text{mango}, \text{i}, \text{icecream}, \text{like}, \text{with} \}$. 98 | Тогава думата ``ilikeicecreamwithmango'' може да се разбие на ``i like icecream with mango''. 99 | 100 | Нека е подаден един низ $\alpha \in \Sigma^*$. 101 | Ако $\alpha = \varepsilon$, то тогава очевидно можем да получим $\alpha$ чрез конкатенация на думи от $D$. 102 | Ако $\alpha \neq \varepsilon$ и $\alpha$ се получава чрез конкатенация на думи от $D$, то тогава има $\beta \in \Sigma^*$ и $\gamma \in D$, за които е изпълнено, че $\alpha = \beta \gamma$ и $\beta$ може да се получи чрез думи от $D$. 103 | Но $\gamma \neq \varepsilon$, т.е. успяхме да сведем задачата за $\alpha$ до задача за $\beta$, като $|\beta| < |\alpha|$. 104 | Нека сега да формализираме тези разсъждения. 105 | Нека $\alpha = \alpha_1 \dots \alpha_n$, където $\alpha_i \in \Sigma$. 106 | Тогава булевата функция $\operatorname{WB}_{D, \alpha}(i)$, която казва дали $\alpha_1 \dots \alpha_i$ може да се разбие на думи от $D$, изглежда така: 107 | \[ 108 | \operatorname{WB}_{D, \alpha}(i) = \begin{cases} 109 | \T & \text{, ако } i = 0 \\ 110 | \bigvee\limits_{\beta \in D} (\alpha_{i - |\beta| + 1} \dots \alpha_i = \beta \: \& \: \operatorname{WB}_{D, \alpha}(i - |\beta|)) & \text{, иначе} 111 | \end{cases} 112 | \] 113 | 114 | \newpage 115 | 116 | На нас това, което ни трябва, е $\operatorname{WB}_{D, \alpha}(|\alpha|)$. 117 | С малко мислене върху това как се пресмята $\operatorname{WB}_{D, \alpha}$, човек може да стигне до следното итеративно решение: 118 | \lstinputlisting{algorithms/word-break.txt} 119 | Да направим сега малко анализ. 120 | Ако $|\alpha| = n, |D| = m$, и $\max \{ |\beta| \mid \beta \in D \} = k$, то това решение има сложност по време $O(n \cdot m \cdot k)$, понеже за всяко $1 \leq i \leq n$ и за всяка дума $\beta \in D$ (те са $m$ на брой) проверяваме дали $\alpha_{i - |\beta| + 1} \dots \alpha_i = \beta$, което става за време $O(k)$, и дали $\operatorname{WB}_{D, \alpha}(i - |\beta|) = \T$, което поради наличието на масива $WB[0 \dots n]$ става за константно време. 121 | 122 | Този масив доста помага, ако не го бяхме ползвали, сложността щеше да се опише (горе долу) с рекурентното уравнение: 123 | \begin{align*} 124 | T(n) = \sum\limits_{i = 1}^{n} (m \cdot k \cdot T(n - i)) + \Theta(1) & = m \cdot k \cdot \sum\limits_{i = 0}^{n - 1} T(i) + \Theta(1) \\ 125 | & = m \cdot k \cdot T(n - 1) + m \cdot k \cdot \sum\limits_{i = 0}^{n - 2} T(i) + \Theta(1) \\ 126 | & = m \cdot k \cdot T(n - 1) + T(n - 1) \\ 127 | & = (m \cdot k + 1) T(n - 1). 128 | \end{align*} 129 | 130 | В този случай $T(n) \asymp (m \cdot k + 1)^n$. 131 | 132 | В горния алгоритъм сложността по памет очевидно е $\Theta(n)$, заради допълнителния масив, който заделяме. 133 | 134 | Вторият пример ще бъде под формата на игра. 135 | Наредени са $n$ монети със стойности съответно $v_1, \dots, v_n$. 136 | Редуваме се с опонент да теглим една монета от избран от краищата на редицата, докато монетите не свършат. 137 | Накрая всеки човек печели толкова, колкото е изтеглил. 138 | В случай че играем първи, каква печалба можем да си гарантираме? 139 | 140 | Първо да започнем с двата най-прости типа игри -- с една или две монети. 141 | В играта с една монета е ясно, че най-голямата печалба, която можем да си гарантираме, е стойността на монетата. 142 | В игра с две монети със стойности съответно $v_1, v_2$, най-голямата гарантирана печалба е очевидно $\max \{ v_1, v_2 \}$. 143 | 144 | Нека сега в общия случай имаме следната партия: 145 | \begin{center} 146 | \dc{$v_1$} \dc{$v_2$} \dt{$\dots$} \dc{$v_{n - 1}$} \dc{$v_n$} 147 | \end{center} 148 | Ще се опитаме да сведем тази по-сложна партия, до няколко по-прости. 149 | Имаме две възможности: 150 | \begin{enumerate} 151 | \item Ако изберем първата монета, опонента ще трябва да избира в конфигурацията: 152 | \begin{center} 153 | \dc{$v_2$} \dc{$v_3$} \dt{$\dots$} \dc{$v_{n - 1}$} \dc{$v_n$} 154 | \end{center} 155 | Той също има две възможности: 156 | \begin{enumerate} 157 | \item Ако опонента избере първата монета, ни оставя в конфигурацията: 158 | \begin{center} 159 | \dc{$v_3$} \dc{$v_4$} \dt{$\dots$} \dc{$v_{n - 1}$} \dc{$v_n$} 160 | \end{center} 161 | Тук можем да си мислим, че ние сме си заделили на страна печалба $v_1$, опонента си е заделил на страна печалба $v_2$, и играта започва наново в горната конфигурация. 162 | \item Ако пък избере последната монета, ни оставя в конфигурацията: 163 | \begin{center} 164 | \dc{$v_2$} \dc{$v_3$} \dt{$\dots$} \dc{$v_{n - 2}$} \dc{$v_{n - 1}$} 165 | \end{center} 166 | Тук можем да си мислим, че ние сме си заделили на страна печалба $v_1$, опонента си е заделил на страна печалба $v_n$, и играта започва наново в горната конфигурация. 167 | \end{enumerate} 168 | \item Ако изберем последната монета, опонента ще трябва да избира в конфигурацията: 169 | \begin{center} 170 | \dc{$v_1$} \dc{$v_2$} \dt{$\dots$} \dc{$v_{n - 2}$} \dc{$v_{n - 1}$} 171 | \end{center} 172 | Той също има две възможности: 173 | \begin{enumerate} 174 | \item Ако опонента избере първата монета, ни оставя в конфигурацията: 175 | \begin{center} 176 | \dc{$v_2$} \dc{$v_3$} \dt{$\dots$} \dc{$v_{n - 2}$} \dc{$v_{n - 1}$} 177 | \end{center} 178 | Тук можем да си мислим, че ние сме си заделили на страна печалба $v_n$, опонента си е заделил на страна печалба $v_1$, и играта започва наново в горната конфигурация. 179 | \item Ако пък избере последната монета, ни оставя в конфигурацията: 180 | \begin{center} 181 | \dc{$v_1$} \dc{$v_2$} \dt{$\dots$} \dc{$v_{n - 3}$} \dc{$v_{n - 2}$} 182 | \end{center} 183 | Тук можем да си мислим, че ние сме си заделили на страна печалба $v_n$, опонента си е заделил на страна печалба $v_{n - 1}$, и играта започва наново в горната конфигурация. 184 | \end{enumerate} 185 | \end{enumerate} 186 | 187 | Нека $\operatorname{MP}(i, j)$ е максималната печалба, която може да се спечели от първия играч (в първоначалната игра), ако може да събира монети със стойности съответно $v_i, \dots, v_j$. 188 | По предните разсъждения можем да пресметнем тази печалба рекурсивно така: 189 | \[ 190 | \operatorname{MP}(i, j) = \begin{cases} 191 | v_i & \text{, ако } i = j \\ 192 | \max \{ v_i, v_j\} & \text{, ако } i = j + 1 \\ 193 | \!\begin{aligned} 194 | \max \{ & v_i + \min \{ \operatorname{MP}(i + 2, j), \operatorname{MP}(i + 1, j - 1) \}, \\ 195 | & v_j + \min \{ \operatorname{MP}(i, j - 2), \operatorname{MP}(i + 1, j - 1) \} 196 | \} 197 | \end{aligned} & \text{, иначе} 198 | \end{cases} 199 | \] 200 | Във хода на втория играч минимизираме, защото той печели възможно най-много, когато ние печелим възможно най-малко. 201 | За задача на читателя оставяме да пресметне $\operatorname{MP}(1, n)$ итеративно. 202 | 203 | \section{Задачи} 204 | 205 | \begin{problem} 206 | Да се напише колкото се може по-бърз алгоритъм, който пресмята ${n \choose k}$ с рекурентната формула от по-горе. 207 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 208 | \end{problem} 209 | 210 | \begin{problem} 211 | Да се напише колкото се може по-бърз алгоритъм, който при подадено естествено число $n$, намира броят на начини човек да се изкачи по тях, като може да изкачва най-много $3$ стъпала наведнъж. 212 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 213 | \end{problem} 214 | 215 | \begin{problem} 216 | Да се напише колкото се може по-бърз алгоритъм, който при подадена булева матрица $A[1 \dots n, 1 \dots m]$ намира броя на пътищата (движейки се само надясно и надолу) от $(1, 1)$ до $(n, m)$, които не минават през $1$ в $A$. 217 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 218 | \end{problem} 219 | 220 | \begin{problem} 221 | Да се напише колкото се може по-бърз алгоритъм, който при подадена матрица от естествени числа $A[1 \dots n, 1 \dots m]$ намира минималната цена на път (движейки се само надясно и надолу) от $(1, 1)$ до $(n, m)$, като под цена на път разбираме сумата на всичките $A[i, j]$, срещнати по пътя. 222 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 223 | \end{problem} 224 | 225 | 226 | \begin{problem} 227 | Да се напише колкото се може по-бърз алгоритъм, който при подаден целочислен масив $A[1 \dots n]$ намира дължината на най-дългата строго растяща негова подредица. 228 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 229 | \end{problem} 230 | 231 | \begin{problem} 232 | Да се напише колкото се може по-бърз алгоритъм, който при подаден масив от естествени числа $A[1 \dots n]$ и естествено число $s$ намира броя на начините, по които могат да се изберат елементи на $A$, така че да сумата им да е $s$. 233 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 234 | \end{problem} 235 | 236 | \begin{problem} 237 | За масив от положителни числа $A[1 \dots n]$ и $1 \leq i < j \leq n$ казваме, че можем да стигнем от $i$ до $j$ в $A$ за една стъпка, ако и $j - i \leq A[i]$. 238 | Да се напише колкото се може по-бърз алгоритъм, който при подаден масив от положителни числа $A[1 \dots n]$ намира минималния брой стъпки, с който можем да стигнем от $1$ до $n$ в $A$. 239 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 240 | \end{problem} 241 | 242 | \begin{problem} 243 | Да се напише колкото се може по-бърз алгоритъм, който при подадено $n$ и число $k$ пресмята броят на начините да се стигне до $k$ чрез хвърляния на зар с $n$ страни, на които пише числата от $1$ до $n$. 244 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 245 | \end{problem} 246 | 247 | \begin{problem} 248 | Формула ще наричаме всеки низ от вида $B_0 \sigma_1 B_1 \sigma_2 B_2 \dots B_{n - 1} \sigma_n B_n$, където $B_i \in \{ \T, \F \}$ и $\sigma_i \in \{ \lor, \land, \oplus \}$. 249 | Например низът $\T \land \T \oplus \F \lor \T$ е формула. 250 | В зависимост от това как слагаме скобите, оценката на израза може да е различна. 251 | Например изразът $(\T \land (\T \oplus (\F \lor \T)))$ се остойностява като $\F$, докато изразът $(\T \land ((\T \oplus \F) \lor \T)))$ се остойностява като $\T$, въпреки че и двата израза се получават от примерната формула. 252 | Да се напише колкото се може по-бърз алгоритъм, който при подадена формула да върне броят на различни скобувания, за които съответния израз се остойностява като $\T$. 253 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 254 | \end{problem} 255 | 256 | \begin{problem} 257 | Имаме професионален крадец, който иска да ограби къщите в дадена улица. 258 | Проблемът е, че ако той ограби две съседни къщи, алармата ще се активира и полицията ще дойде. 259 | Той не иска това, защото в такъв случай няма да спечели нищо. 260 | Да се напише колкото се може по-бърз алгоритъм, който при подаден масив от естествени числа $L[1 \dots n]$, където $L[i]$ е печалбата от къща $i$, връща максималната печалба на крадеца. 261 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 262 | \end{problem} 263 | 264 | \begin{problem} 265 | Да се напише колкото се може по-бърз алгоритъм, който при подадена булева матрица $M[1 \dots n, 1 \dots m]$ намира най-голямото квадратче в $M$, съставено от $1$, и връща неговото лице. 266 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 267 | \end{problem} 268 | 269 | \begin{problem} 270 | Нека $n, l, r \in \N$ и $l \leq r$. 271 | Един целочислен масив $A[1 \dots n]$ ще наричаме $(n, l, r)$-интересен, ако: 272 | \begin{itemize} 273 | \item $l \leq A[i] \leq r$ за всяко $1 \leq i \leq n$; 274 | \item $\sum\limits_{i = 1}^n A[i] \equiv 0 \; (\operatorname{mod} 3)$. 275 | \end{itemize} 276 | Да се напише колкото се може по-бърз алгоритъм, който при подадени $n, l, r \in \N$, за които $l \leq r$, връща броя на $(n, l, r)$-интересни масиви. 277 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 278 | \end{problem} 279 | 280 | \begin{problem} 281 | Един целочислен масив $A[1 \dots n]$ наричаме аритметичен, ако за всяко $1 \leq i \leq n - 2$ е изпълнено, че $A[i + 2] - A[i + 1] = A[i + 1] - A[i]$. 282 | Да се напише колкото се може по-бърз алгоритъм, който при подаден целочислен масив $A[1 \dots n]$ намира броя на подредиците на $A$, които са аритметични масиви. 283 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 284 | \end{problem} 285 | 286 | \begin{problem} 287 | Имаме $n$ на брой къщи, които искаме да боядисаме със цветове $c_1, c_2, c_3$, като не може две съседни къщи да имат еднакъв цвят. 288 | Масив на цените ще наричаме всеки двумерен масив от положителни числа $P[1 \dots n, 1 \dots 3]$, където $P[i, j]$ ще бъде цената за боядисване на къща $i$ със цвят $c_j$. 289 | Да се напише колкото се може по-бърз алгоритъм, който при подаден двумерен масив от положителни числа, намира минималната цена за боядисване на всички къщи. 290 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 291 | \end{problem} 292 | 293 | \begin{problem} 294 | Върху един масив от естествени числа $A[1 \dots n]$ можем да прилагаме следните две операции: 295 | \begin{itemize} 296 | \item да увеличим $A[i]$ с единица за някое $1 \leq i \leq n$; 297 | \item да намалим $A[i]$ с единица за някое $1 \leq i \leq n$. 298 | \end{itemize} 299 | Да се напише колкото се може по-бърз алгоритъм, който при подаден масив от естествени числа $A[1 \dots n]$ връща минималния брой операции, които са нужни, за да стане масива монотонно ненамаляващ. 300 | След това да се докаже неговата коректност, и да се изследва сложността му по време. 301 | \end{problem} --------------------------------------------------------------------------------