├── .gitignore
├── go.mod
├── img
├── go-alg.png
├── img1.png
├── big-o-graph.png
└── sort
│ ├── BubbleSort.gif
│ └── InsertionSort.gif
├── main.go
├── sort
├── insertion.go
├── bubble.go
├── INSERTION.md
└── BUBBLE.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/goavengers/go-algorithms
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/img/go-alg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goavengers/go-algorithms/HEAD/img/go-alg.png
--------------------------------------------------------------------------------
/img/img1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goavengers/go-algorithms/HEAD/img/img1.png
--------------------------------------------------------------------------------
/img/big-o-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goavengers/go-algorithms/HEAD/img/big-o-graph.png
--------------------------------------------------------------------------------
/img/sort/BubbleSort.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goavengers/go-algorithms/HEAD/img/sort/BubbleSort.gif
--------------------------------------------------------------------------------
/img/sort/InsertionSort.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goavengers/go-algorithms/HEAD/img/sort/InsertionSort.gif
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/goavengers/go-algorithms/sort"
4 |
5 | func main() {
6 | // sort.UseBubbleSort()
7 | sort.UseInsertionSort()
8 | }
9 |
--------------------------------------------------------------------------------
/sort/insertion.go:
--------------------------------------------------------------------------------
1 | package sort
2 |
3 | import "fmt"
4 |
5 | func UseInsertionSort() {
6 | unsorted := []int{1, 3, 2, 4, 8, 6, 7, 2, 3, 0}
7 | fmt.Println(unsorted)
8 | sorted := InsertionSort(unsorted)
9 | fmt.Println(sorted)
10 | }
11 |
12 | func InsertionSort(array []int) []int {
13 | length := len(array)
14 |
15 | for i := 1; i < length; i++ {
16 | j := i
17 |
18 | for j > 0 {
19 | if array[j-1] > array[j] {
20 | array[j-1], array[j] = array[j], array[j-1]
21 | }
22 | j = j - 1
23 | }
24 | }
25 |
26 | return array
27 | }
28 |
--------------------------------------------------------------------------------
/sort/bubble.go:
--------------------------------------------------------------------------------
1 | package sort
2 |
3 | import "fmt"
4 |
5 | func UseBubbleSort() {
6 | unsorted := []int{1, 3, 2, 4, 8, 6, 7, 2, 3, 0}
7 | fmt.Println(unsorted)
8 | sorted := BubbleSort(unsorted)
9 | fmt.Println(sorted)
10 | }
11 |
12 | func BubbleSort(array []int) []int {
13 | changed := true
14 | length := len(array)
15 |
16 | // Массив в алгоритме считается отсортированным. При первой замене доказывается обратное и запускается еще одна итерация.
17 | // Цикл останавливается, когда все пары элементов в массиве пропускаются без замен
18 | for changed {
19 | changed = false
20 |
21 | for i := 1; i < length; i++ {
22 | // если порядок не верный
23 | if array[i-1] > array[i] {
24 | // меняем местами элементы
25 | array[i], array[i-1] = array[i-1], array[i]
26 |
27 | // продолжаем сортировку
28 | changed = true
29 | }
30 | }
31 | }
32 |
33 | return array
34 | }
35 |
--------------------------------------------------------------------------------
/sort/INSERTION.md:
--------------------------------------------------------------------------------
1 | # Сортировка вставками (Insertion sort)
2 |
3 | Этот алгоритм разделяет оригинальный массив на сортированный и несортированный подмассивы.
4 |
5 | Длина сортированной части равна 1 в начале и соответствует первому (левому) элементу в массиве.
6 | После этого остается итерировать массив и расширять отсортированную часть массива одним элементом с каждой новой итерацией.
7 |
8 | После расширения новый элемент помещается на свое место в отсортированном подмассиве.
9 | Это происходит путём сдвига всех элементов вправо, пока не встретится элемент, который не нужно двигать.
10 |
11 | В приведенном ниже массиве жирная часть отсортирована в порядке возрастания. Посмотрите что произойдет в этом случае:
12 |
13 | - __3 5 7 8__ 4 2 1 9 6: выбираем 4 и помним, что это элемент, который нужно вставить. __8 > 4__, поэтому сдвигаем.
14 | - __3 5 7 x 8__ 2 1 9 6: здесь x – нерешающее значение, так как элемент будет перезаписан (на 4, если это подходящее место, или на 7, если смещение). __7 > 4__, поэтому сдвигаемся.
15 | - __3 5 x 7 8__ 2 1 9 6
16 | - __3 x 5 7 8__ 2 1 9 6
17 | - __3 4 5 7 8__ 2 1 9 6
18 |
19 | Теперь вы видите, что отсортированная часть дополнилась элементом. Каждая следующая итерация делает то же самое, и к концу вы получите отсортированный массив!
20 |
21 | ## Сложность
22 |
23 | | Name | Best | Average | Worst | Memory | Stable | Comments |
24 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- |
25 | | **Insertion sort** | n | n2 | n2 | 1 | Yes | |
26 |
27 | ## Реализация
28 |
29 | ```go
30 | func InsertionSort(array []int) []int {
31 | length := len(array)
32 |
33 | for i := 1; i < length; i++ {
34 | j := i
35 |
36 | for j > 0 {
37 | if array[j-1] > array[j] {
38 | array[j-1], array[j] = array[j], array[j-1]
39 | }
40 | j = j - 1
41 | }
42 | }
43 |
44 | return array
45 | }
46 | ```
47 |
48 | ## Визуализация
49 |
50 | 
--------------------------------------------------------------------------------
/sort/BUBBLE.md:
--------------------------------------------------------------------------------
1 | # Пузырьковая сортировка (Bubble sort)
2 |
3 | Алгоритм состоит из повторяющихся проходов по сортируемому массиву.
4 | За каждый проход элементы последовательно сравниваются попарно и, если порядок в паре неверный, выполняется обмен элементов.
5 | Проходы по массиву повторяются N-1 раз или до тех пор, пока на очередном проходе не окажется, что обмены больше не нужны, что означает — массив отсортирован.
6 | При каждом проходе алгоритма по внутреннему циклу, очередной наибольший элемент массива ставится на своё место в конце массива рядом с предыдущим «наибольшим элементом», а наименьший элемент перемещается на одну позицию к началу массива («всплывает» до нужной позиции, как пузырёк в воде — отсюда и название алгоритма).
7 |
8 | Вот шаги для сортировки массива чисел от наименьшего к большему:
9 |
10 | - __4 2__ 1 5 3: два первых элемента расположены в массиве в неверном порядке. Меняем их.
11 | - 2 __4 1__ 5 3: вторая пара элементов тоже «не в порядке». Меняем и их.
12 | - 2 1 __4 5__ 3: а эти два элемента в верном порядке (4 < 5), поэтому оставляем как есть.
13 | - 2 1 4 __5 3__: очередная замена.
14 | - 2 1 4 3 5: результат после одной итерации.
15 |
16 | Для полной сортировки нужен еще один шаг. Третья итерация пройдет уже без замены. Так вы поймете, что массив отсортирован.
17 |
18 | Но причём тут пузырьки? Посмотрите снова на пример, и вы увидите, что алгоритм как бы смещается вправо. По этому поведению элементов в массиве и возникла аналогия с «пузырьками», всплывающими на «поверхность».
19 |
20 | ## Сложность
21 |
22 | | Name | Best | Average | Worst | Memory | Stable | Comments |
23 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- |
24 | | **Bubble sort** | n | n2 | n2 | 1 | Yes | |
25 |
26 | ## Реализация
27 |
28 | ```go
29 | func BubbleSort(array []int) []int {
30 | changed := true
31 | length := len(array)
32 |
33 | // Массив в алгоритме считается отсортированным. При первой замене доказывается обратное и запускается еще одна итерация.
34 | // Цикл останавливается, когда все пары элементов в массиве пропускаются без замен
35 | for changed {
36 | changed = false
37 |
38 | for i := 1; i < length; i++ {
39 | // если порядок не верный
40 | if array[i-1] > array[i] {
41 | // меняем местами элементы
42 | array[i], array[i-1] = array[i-1], array[i]
43 |
44 | // продолжаем сортировку
45 | changed = true
46 | }
47 | }
48 | }
49 |
50 | return array
51 | }
52 | ```
53 |
54 | ## Визуализация
55 |
56 | 
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
Подборка алгоритмов, которые правят миром
4 |
Вместе мы разберемся!
5 |
6 |
7 | ## Содержание
8 |
9 | 1. #### [Вступление](#introduction)
10 | 1. [Вчём цель?](#what_is_target)
11 | 2. [Оценка сложности алгоритмов, или Что такое Big O](what_is_big_o)
12 | 3. [Закрепление примерами](#examples)
13 | 4. [Заключение](#conclusion)
14 | 5. [Производительность относительно данных](#performance)
15 | 2. #### [Алгоритмы сортировки](#sort_algorithms)
16 | 1. [Пузырьковая сортировка](https://github.com/goavengers/go-algorithms/blob/master/sort/BUBBLE.md)
17 | 2. Сортировка выборкой
18 | 3. [Сортировка вставками](https://github.com/goavengers/go-algorithms/blob/master/sort/INSERTION.md)
19 | 4. Пирамидальная сортировка
20 | 5. Сортировка слиянием
21 | 6. Быстрая сортировка
22 | 3. #### [Алгоритмы поиска](#search_algorithms)
23 | 1. Поиск в глубину
24 | 2. Поиск в ширину
25 | 3. Двоичный поиск
26 | 4. Линейный поиск
27 |
28 | ## Прежде чем начать, зачем мне изучать концепцию Big O?
29 |
30 | - Концепцию Big O необходимо понимать, чтобвы видеть и исправлять неоптимальный код.
31 | - Ни один серьезный проект или собеседование не могут обойтись без вопросов о Big O.
32 | - Не понимание Big O ведет к серьезной потери производительности ваших алгоритмов.
33 |
34 | ## Вчём цель?
35 |
36 | - Цель проста — научиться понимать концепцию Big O.
37 |
38 | __Big O__ (_в рамках Computer Science_) показывает __верхнюю границу__ зависимости между __входными параметрами__ функции и __количеством опрераций__, которые выполнит процессор. Т.е, показывает зависимость, допустим, между массивом в 1000 элементов и количеством тактов, которое необходимо выполнить процессору, чтобы обработать эти 1000 элементов.
39 |
40 | ## Вступление, это не относится непосредственно к языку программирования Golang, но почитать или освежить память никому не помешает
41 |
42 | ### Оценка сложности алгоритмов, или Что такое Big O.
43 |
44 | Сложность алгоритмов обычно оценивают по времени выполнения или по используемой памяти. В обоих случаях сложность зависит от размеров входных данных: массив из 100 элементов будет обработан быстрее, чем аналогичный из 1000. При этом точное время мало кого интересует: оно зависит от процессора, типа данных, языка программирования и множества других параметров. Важна лишь асимптотическая сложность, т. е. сложность при стремлении размера входных данных к бесконечности.
45 |
46 | Допустим, некоторому алгоритму нужно выполнить 4n3 + 7n условных операций, чтобы обработать n элементов входных данных. При увеличении n на итоговое время работы будет значительно больше влиять возведение n в куб, чем умножение его на 4 или же прибавление 7n. Тогда говорят, что временная сложность этого алгоритма равна О(n3), т. е. зависит от размера входных данных кубически.
47 |
48 | Использование заглавной буквы О (или так называемая О-нотация) пришло из математики, где её применяют для сравнения асимптотического поведения функций. Формально O(f(n)) означает, что время работы алгоритма (или объём занимаемой памяти) растёт в зависимости от объёма входных данных не быстрее, чем некоторая константа, умноженная на f(n).
49 |
50 | #### O(1) — константная сложность
51 |
52 | Порядок роста O(1) означает, что вычислительная сложность алгоритма не зависит от размера входных данных. Следует помнить, однако, что единица в формуле не значит, что алгоритм выполняется за одну операцию или требует очень мало времени. Он может потребовать и микросекунду, и год. Важно то, что это время не зависит от входных данных.
53 |
54 | ```go
55 | func GetCount(items []int) int {
56 | return len(items)
57 | }
58 | ```
59 |
60 | #### O(n) — линейная сложность
61 |
62 | Такой сложностью обладает, например, алгоритм поиска наибольшего элемента в не отсортированном массиве. Нам придётся пройтись по всем n элементам массива, чтобы понять, какой из них максимальный.
63 |
64 | Порядок роста O(n) означает, что сложность алгоритма линейно растет с увеличением входного массива. Если линейный алгоритм обрабатывает один элемент пять миллисекунд, то мы можем ожидать, что тысячу элементов он обработает за пять секунд.
65 |
66 | Такие алгоритмы легко узнать по наличию цикла по каждому элементу входного массива.
67 |
68 | ```go
69 | func GetSum(items []int) int {
70 | sum := 0
71 |
72 | for _, item := range items {
73 | sum += item
74 | }
75 |
76 | return sum
77 | }
78 | ```
79 |
80 | #### O(log n) — логарифмическая сложность
81 |
82 | Порядок роста O( log n) означает, что время выполнения алгоритма растет логарифмически с увеличением размера входного массива. (Прим. пер.: в анализе алгоритмов по умолчанию используется логарифм по основанию 2). Большинство алгоритмов, работающих по принципу «деления пополам», имеют логарифмическую сложность. Метод Contains бинарного дерева поиска (binary search tree) также имеет порядок роста O(log n).
83 |
84 | Простейший пример — бинарный поиск. Если массив отсортирован, мы можем проверить, есть ли в нём какое-то конкретное значение, методом деления пополам. Проверим средний элемент, если он больше искомого, то отбросим вторую половину массива — там его точно нет. Если же меньше, то наоборот — отбросим начальную половину. И так будем продолжать делить пополам, в итоге проверим log n элементов.
85 |
86 | #### O(n^2) — квадратичная сложность
87 |
88 | Время работы алгоритма с порядком роста O(n^2) зависит от квадрата размера входного массива. Несмотря на то, что такой ситуации иногда не избежать, квадратичная сложность — повод пересмотреть используемые алгоритмы или структуры данных. Проблема в том, что они плохо масштабируются. Например, если массив из тысячи элементов потребует
89 | 1 000 000 операций, массив из миллиона элементов потребует 1 000 000 000 000 операций. Если одна операция требует миллисекунду для выполнения, квадратичный алгоритм будет обрабатывать миллион элементов 32 года. Даже если он будет в сто раз быстрее, работа займет 84 дня.
90 |
91 | Такую сложность имеет, например, алгоритм сортировки вставками. В канонической реализации он представляет из себя два вложенных цикла: один, чтобы проходить по всему массиву, а второй, чтобы находить место очередному элементу в уже отсортированной части. Таким образом, количество операций будет зависеть от размера массива как n * n, т. е. n^2.
92 |
93 | ```go
94 | // не совсем яркий пример: содержит ли вектор (своеобразный массив) A размера n два одинаковых значения
95 |
96 | func DuplicateExist(A []int) bool {
97 | n := len(A)
98 |
99 | for i :=0; i < n; i++ {
100 | for j:= 0 j < n; j++ {
101 | if i != j && A[i] == A[j] {
102 | return true
103 | }
104 | }
105 | }
106 |
107 | return false
108 | }
109 |
110 | ```
111 |
112 | Два вложенных цикла дадут нам асимптотику вида f(n) = O(n^2).
113 |
114 | __Практическая рекомендация:__ простые программы можно анализировать с помощью подсчёта в них количества вложенных циклов:
115 |
116 | - Одиночный цикл в n итераций даёт f(n) = O(n).
117 | - Цикл внутри цикла — f(n) = O(n^2).
118 | - Цикл внутри цикла внутри цикла — f(n) = O(n^3). И так далее.
119 |
120 | __Практическая рекомендация:__ если у нас имеется серия из последовательных for-циклов, то асимптотическое поведение программы определяет наиболее медленный из них. Два вложенных цикла, идущие за одиночным, асимптотически тоже самое, что и вложенные циклы сами по себе. Говорят, что вложенные циклы доминируют над одиночными.
121 |
122 | ```go
123 | f(n) = O(n^2)
124 |
125 | func Asymptotic() {
126 | for i := 0; i < N; i ++ {
127 | // do stuff
128 | }
129 |
130 | // доминирующая сложность
131 | for i := 0; i < N; i++ {
132 | for j := 0; j < N; j++ {
133 | // do stuff
134 | }
135 | }
136 | }
137 | ```
138 |
139 | __Примечания:__
140 |
141 | - Константы всегда отбрасываются: 2n => O(n), 6n^2 => O(n^2), потому что концепция Big O описывает скорость выполнения алгоритма, стремящийся к бесконочности, 2 бесконечности, 5 бесконечностей — это одна и та же бесконечность.
142 |
143 | ### Полезные ресурсы
144 |
145 | - [Know Thy Complexities!](https://www.bigocheatsheet.com/)
146 |
147 | ### Что мы измеряем?
148 |
149 | При измерении сложности алгоритмов и структур данных мы обычно говорим о двух вещах: количество операций, требуемых для завершения работы (вычислительная сложность), и объем ресурсов, в частности, памяти, который необходим алгоритму (пространственная сложность).
150 |
151 | Алгоритм, который выполняется в десять раз быстрее, но использует в десять раз больше места, может вполне подходить для серверной машины с большим объемом памяти. Но на встроенных системах, где количество памяти ограничено, такой алгоритм использовать нельзя.
152 |
153 | ### Закрепление примерами
154 |
155 | __1. Как вы думаете, какая сложность у двух следующих алгоритмов?__
156 |
157 | ```go
158 | func sum(n int) int {
159 | if n == 1 {
160 | return 1
161 | }
162 |
163 | return n + sum(n - 1)
164 | }
165 | ```
166 |
167 | ```go
168 | func pairSumSequence(n int) int {
169 | sum := 0
170 |
171 | for i := 0; i < n; i++ {
172 | sum += pairSum(i, i + 1)
173 | }
174 |
175 | return sum
176 | }
177 |
178 | func pairSum(a, b int) int {
179 | return a + b
180 | }
181 | ```
182 |
183 | __Ответ:__ у обеих алгоритмов линейное быстродействие O(n)
184 |
185 | __2. Какой код выполнится быстрее?__
186 |
187 | ```go
188 | const MaxInt = int(MaxUint >> 1)
189 | const MinInt = -MaxInt - 1
190 |
191 | // Первый
192 | func MaxAndMin(n []int) (int, int) {
193 | for _, x := range n {
194 | if x < MinInt {
195 | min = x
196 | }
197 |
198 | if x > MaxInt {
199 | max = x
200 | }
201 | }
202 |
203 | return min, max
204 | }
205 |
206 | // второй
207 | func MaxAndMinSecond(n []int) (int, int) {
208 | for _, x := range n {
209 | if x < MinInt {
210 | min = x
211 | }
212 | }
213 |
214 | for _, x := range n {
215 | if x > MaxInt {
216 | max = x
217 | }
218 | }
219 |
220 | return min, max
221 | }
222 | ```
223 |
224 | Логично, что первая 1 цикл и один проход по циклу и действительно, если смотреть на команды процессора второй пример является __медленее__. Но в данном случае это было бы неверно. Концепция Big O показывает как ведут себя эти алгоритмы.
225 |
226 | __Ответ:__ у обоих алгоритмов сложность O(n)
227 |
228 | __3. Как быть со сложностью O(n^2 + n)?__
229 |
230 | Надо понимать как отсеивать неважную сложность:
231 |
232 | - N не является константой, следовательно ее нельзя отбросить
233 | - Но мы понимаем, что `O(n^2 + n^2)` == `O(n^2)`
234 | - Так же мы понимаем, что `O(n^2)` > `O(n)`, следовательно если она меньше то не влияет на сложность алгоритма
235 |
236 | __Ответ:__ Сложность данного алгоритма O(n^2)
237 |
238 | Несколько примеров, чтобы осталось в голове:
239 |
240 | - `O(n^2 + n)` мы уже знаем что это `O(n^2)`
241 | - `O(n + log n)` это `O(n)`, т.к. `O(log n)` гораздо меньше `O(n)`
242 | - `O(5 * 2^n + 10 * 2^100)` константы мы помним можно отбрасывать -> `O(2^n * 2^100)` = `O(2^n)`, т.к. экспоненциальное время `O(2^n)` намного больше степенной.
243 | - `O(n^2 + B)` - считаете что `O(n^2)`? На самом деле ответ будет `O(n^2 + B)`, т.к. мы ничего не можем сказать о `B` и мы не можем выбросить ее из нашей сложности.
244 |
245 | __4. Откуда берется сложность алгоритма O(log n)?__
246 |
247 | Время выполнения __log n__ на примере бинарного поиска, когда __каждый раз__ берется половина элементов:
248 |
249 | - `2^k = N `// 2^4 = 16
250 | - `k = log2 n`
251 | - `O(k) = O(log2 n)`
252 | - `O(k) = O(log n)`, отбрасывае двойку, т.к. мы помним что она является константой и ее можно отбросить
253 |
254 | __Ответ:__ для алгоритма, где __на каждой итерации берется половина элементов__ сложность будет включать `O(log n)`, она так же может быть `O(A * log n)` или `O(A + B * log n)`, главное что __каждый раз__ берется половина элементов.
255 |
256 | __5. Какая сложность у следующего алгоритма?__
257 |
258 | ```go
259 | func foo(n []int) {
260 | for n-раз
261 | for n-раз
262 | }
263 | ```
264 |
265 | __Ответ:__ O(n + n ) = O(n)
266 |
267 | __6. Какая сложность у следующего алгоритма?__
268 |
269 | ```go
270 | func foo(n []int) {
271 | for n-раз {
272 | for n-раз {
273 |
274 | }
275 | }
276 | }
277 | ```
278 |
279 | __Ответ:__ O(n * n) = O(n^2)
280 |
281 | __7. Какая сложность у следующего алгоритма?__
282 |
283 | ```go
284 | func foo(n []int) {
285 | for i := 0; i < len(n); i++ {
286 | for j := i; j < len(n); j++ {
287 | // do stuff
288 | }
289 | }
290 | }
291 |
292 | // всевдо
293 |
294 | func foo(n []int) {
295 | for n-раз {
296 | for n, n-1, n-2, n-3, ..., 6, 7 - раз
297 | }
298 | }
299 | ```
300 |
301 | - Код внешнего цикла выполняется n-раз
302 | - Код внутреннего цикла выполняется n, n-1, n-2 и т.д. раз
303 | - Тогда сложность можно описать как: `O(n + (n - 1) + (n - 2) + ... + 2 + 1)`, как его упросить?
304 |
305 | 
306 |
307 | __Ответ:__ Сложность алгоритма O(n^2)
308 |
309 | __8. Какая сложность у следующего алгоритма?__
310 |
311 | ```go
312 | func foo(a, b []int) {
313 | for _, x := range a {
314 | for _, z := range b {
315 | // do stuff
316 | }
317 | }
318 | }
319 |
320 | func foo(a, b []int) {
321 | for len(a)-раз {
322 | for len(b)-раз {
323 | // do stuff
324 | }
325 | }
326 | }
327 | ```
328 |
329 | __Ответ:__ O(a * b)
330 |
331 | __9. Какая сложность у следующего алгоритма?__
332 |
333 | ```go
334 | func foo(a, b []int) {
335 | for _, x := range a {
336 | for _, z := range b {
337 | for k := 0; k < 100000; k++ {
338 | // do stuff
339 | }
340 | }
341 | }
342 | }
343 |
344 | ```
345 |
346 | __Ответ:__ O(a * b), почему? 100000 это конечно много, но это константа и ее можно отбросить, помни это
347 |
348 | __10. Какая сложность у следующего алгоритма?__
349 |
350 | ```go
351 | func reverse(n []int) {
352 | length := len(n)
353 |
354 | for i := 0; i < length / 2; i++ {
355 | other := length - i - 1
356 | temp := n[i]
357 |
358 | n[i] = n[other]
359 | n[other] = temp
360 | }
361 | }
362 |
363 | func reverse(n []int) {
364 | for n / 2 раз
365 | }
366 | ```
367 |
368 | Думаете __O(log n)__? Казалось бы да, но нет.
369 |
370 | __Ответ:__ O(n/2), т.к. мы просто __один раз__ берем половину элементов массива и итерируем их, скажем было 100 элементов взяли половину - 50, для Big O это все те же n элементов, следовательно сложность алгоритма O(n)
371 |
372 | ### Какие выводы мо можем теперь сделать?
373 |
374 | - Big O показывает __темп роста функции__. Следовательно мы можем не учитывать константы и "неважную сложность".
375 | - Последовательность действие - это __сложение__, вложенные действия - __умножение__.
376 | - Для алгоритмов, где на кажлой итерации берется __половина__ элементов сложность\будет включать O(log n)
377 |
378 | ### Производительность относительно данных
379 |
380 | 
381 |
382 | **Источник** : [Big O Cheat Sheet](http://bigocheatsheet.com/).
383 |
384 | Ниже приведен список некоторых наиболее часто используемых обозначений Big O и их сравнение производительности с различными размерами входных данных.
385 |
386 | | Big O Notation | Расчеты для 10 элементов | Расчеты для 100 элементов | Расчеты для 1000 элементов |
387 | | -------------- | ---------------------------- | ----------------------------- | ------------------------------- |
388 | | **O(1)** | 1 | 1 | 1 |
389 | | **O(log N)** | 3 | 6 | 9 |
390 | | **O(N)** | 10 | 100 | 1000 |
391 | | **O(N log N)** | 30 | 600 | 9000 |
392 | | **O(N^2)** | 100 | 10000 | 1000000 |
393 | | **O(2^N)** | 1024 | 1.26e+29 | 1.07e+301 |
394 | | **O(N!)** | 3628800 | 9.3e+157 | 4.02e+2567 |
395 |
--------------------------------------------------------------------------------