Что такое алгоритм? Оценка сложности
Алгоритм - это набор последовательных действий, которые решают какую-либо задачу.
Сложность алгоритма высчитывают через O(n), где n - количество действий для выполнения операции. Чем меньше действий, тем лучше.
На графе ниже представлено отображение, где от красного до фиолетового идёт последовательность от лучшего к худшему
Мы ищем число 7 внутри отсортированного массива из десяти элементов. Если мы будем пользоваться линейным поиском и искать перебирая все элементы слева направо, то потратим 7 действий, а если будем искать через бинарный (двоичный) поиск, то нам потребуется всего 3 действия, так как мы два раза поделим массив пополам
Бинарный поиск имеет сложность O(log2n)
Конкретно бинарный поиск в сравнении с линейным будет отрабатывать разные количество времени и искать крайне разное время
Тут находится шпаргалка с описанием скоростей выполнения различных операций
Линейный поиск
Линейный поиск представляет собой перебор всех элементов массива, начиная с самого начала
Линейный алгоритм всегда имеет количество действий O(n) - линейное количество
Перебор идёт каждого элемента !
Бинарный поиск. Итеративный подход (цикл)
Бинарный поиск - это поиск внутри остортированного массива посредством выборки среднего значения из представленных
Данный поиск представляет из себя деление всего массива на два и откидывание лишних значений. Если мы ищем 4 среди 10 элементов, то сначала выберится 5, потом, так как 5 больше 4, то откинется половина массива после 5, потом выберится 3, откинется всё до 3 и у нас останется 3 - 4- 5, где серединой останется искомое 4
Сложность у данного алгоритма O(log2n) - логарифмическая сложность
Способ решения через рекурсию:
Сортировка выбором. SelectionSort
Сортировка выбором представляет из себя сортировку, при которой сначала находится самый меньший элемент и он подставляется в начало, заменяя начальный
Сложность данного алгоритма равна O(n^2), так как мы имеем 1/2 от n^2 - 276 итераций на 24 элемента
Сортировка пузырьком. BubbleSort
Сортировка пузырьком - переставление элемента больше правее, на место элемента меньшего порядка
Этот же алгоритм является самым неэффективным, так как он имеет сложность ровно O(n^2). Тут на 24 элемента массива было совершено 576 итераций, что = 24
Рекурсия. Рекурсивные функции. Факториал. Числа Фибоначчи
Рекурсия - это алгоритм, при котором функция зацикливает своё выполнение ровно до тех пор, пока она не достигнет финального результата
Примером является вычисление факториала, который представляет из себя умножение самого на себя число меньшего на 1 до 1 или поиск числа Фибоначчи, которое представляет из себя сложение всех сумм цифр числа
Быстрая сортировка. Сортировка Хоара
Быстрая сортировка предполагает под собой рекурсивное разделение массива данных на две части для поиска опорных чисел и сортировки
Это самый быстрый способ сортировки
Сложность алгоритма равна O(log2n * n). На 24 элемента количество итераций равно 104 - то есть наименьшее число действий.
Графы. Поиск в ширину
Поиск в ширину представляет собой поиск пути по графу от начальной точки до целевой и возможность в принципе найти данный путь
Основная суть задачи - узнать, существует ли путь из точки A в точку G
Структура данных Очередь
Для реализации алгоритма поиска по графу, будет использоваться структура данных - Очередь. Основной её особенностью является то, что мы добавляем элемент в конец и достаём из начала
Тут уже представлен простейший алгоритм очереди, где мы перебираем массив массивов точек графа
Матрица смежности
Матрица смежности - это способ представления графа в виде квадратной матрицы, где строки и столбцы матрицы соответствуют вершинам графа, а ячейки содержат информацию о том, есть ли ребро между этими двумя вершинами или нет.
Для взвешенного графа, вместо 0 или 1 в ячейках используются числа, которые обозначают вес или стоимость ребра между соответствующими вершинами.
Матрица смежности может быть использована для быстрого определения, есть ли связь между двумя вершинами, и получения информации о структуре графа. Однако, если количество вершин графа очень большое, то матрица смежности занимает много памяти, и может быть неэффективной в использовании.
Алгоритм Дейкстры для поиска кратчайшего пути
Алгоритм Дейстры представляет из себя поиск кратчайшего пути в графе, когда в учёт между точками ещё берётся ещё и расстояние между ними
- Создается объект
graph
, который представляет ваш взвешенный граф. Каждый ключ этого объекта соответствует вершине графа, а значение - объект, который содержит пары “соседняя вершина: расстояние до нее”. - Определена функция
shortPath(graph, start, end)
, которая принимает три параметра:graph
- ваш взвешенный граф,start
- начальная вершина иend
- конечная вершина. Функция возвращает объектcosts
, который содержит кратчайший путь от начальной вершиныstart
до конечной вершиныend
. - Объявляются три переменные:
costs
,processed
иneighbors
. Начальное значение переменнойcosts
- это пустой объект, который будет использоваться для хранения стоимости кратчайшего пути до каждой вершины графа от начальной вершины. Переменнаяprocessed
- это массив, который будет содержать все узлы, которые были уже проверены. Это нужно, чтобы убедиться, что мы не рассмотрим узел дважды. Переменнаяneighbors
- это объект, который будет содержать всех соседей узла, который мы в данный момент рассматриваем. - Цикл
forEach
используется для итерации по списку ключей графа. Для каждого узла графа, кроме начального узла, заполняется таблицаcosts
записью: имя узла в качестве ключа, а значение - расстояние от начальной вершины до этого узла. Если расстояние не определено (нет соединения между текущим узлом и начальным узлом), то значение устанавливается на достаточно большое число (1000000). - Функция
findNodeLowestCost(costs, processed)
используется для поиска узла с наименьшей стоимостью пути из таблицыcosts
. Она ищет наименьшее число пути и возвращает соответствующий узел, который еще не был обработан. - В основном цикле
while
, мы используем функциюfindNodeLowestCost()
для нахождения узла с наименьшей стоимостью пути. Затем мы получаем все соседние вершины этого узла и итерируемся по ним, чтобы проверить, можем ли мы добраться до соседней вершины через текущий узел по более короткому пути. Если мы можем, то обновляем таблицуcosts
с новой стоимостью пути. Мы также добавляем проверенный узел в массивprocessed
. Затем мы повторяем цикл до тех пор, пока не достигнем конечной точки или не будет больше узлов для обработки. - В конце функция
shortPath()
возвращает объектcosts
, который содержит кратчайший путь от начальной вершиныstart
до конечной вершиныend
.
Рекурсивный обход дерева n-размерности
Деревья - это рекурсивная структура данных, где каждый узел является так же деревом
Таким образом можно представить дерево
Оно же будет использоваться для следующих примеров
Рекурсивный обход дерева выглядит следующим образом:
Структура данных Стек
Стек подразумевает под собой сбор самого свежего вошедшего элемента. Если при FIFO уходит первый пришедший, то тут, LIFO - уходит последний пришедший
Итеративный обход дерева n-размерности
Данная функция iteration
представляет собой итеративный алгоритм обхода дерева, где каждый узел представлен объектом, содержащим его значение v
и массив его дочерних узлов c
.
Алгоритм начинается с проверки наличия элементов в дереве. Если дерево пустое, то функция возвращает 0.
Затем создается переменная sum
, которая будет хранить сумму значений всех узлов дерева. Также создается массив stack
, в который помещаются все узлы первого уровня дерева.
Далее запускается цикл while
, который будет выполняться до тех пор, пока stack
не станет пустым. На каждой итерации цикла извлекается последний узел из stack
и добавляется его значение к sum
. Затем проверяется наличие дочерних узлов у текущего узла. Если дочерние узлы есть, то они добавляются в конец stack
.
После того как все узлы дерева будут обработаны, функция вернет сумму значений всех узлов.
Таким образом, данная функция реализует обход дерева в глубину (Depth-First Search) при помощи стека.
Кеширование вычислений
Алгоритм кеширования вычислений подразумевает под собой, что мы должны закешировать результат вычисления
Конкретно тут идёт проверка по введённому аргументу, если аргумент есть в кеше, то просто возвращается его результат, если переданного n
нет в объекте cache
, то вычисления происходят снова и они записываются в кеш
Массивы. Сложность основных операций
Массив
Его плюсом является то, что мы знаем, где располагается каждый элемент и он имеет константное время для получения элемента
Его минусом является то, что массив фиксированный и у него заранее определён размер, и чтобы добавить новый элемент в массив, нужно будет создать новый, перенести все значения и удалить старый массив
Связный список. Простая реализация и теория
Связанный список представляет из себя структуру данных, где каждый элемент занимает свою ячейку памяти и хранит в себе ссылку на положение следующего элемента в списке.
Чтобы найти следующий элемент, нужно проитерироваться по всему списку и как в массивах быстро найти элемент не получится. Зато в список легко добавить новый элемент в любое место.
Данный код представляет реализацию простого связанного списка на JavaScript.
Класс LinkedList
(связный список) имеет три метода:
- конструктор, который инициализирует размер списка (
size
) и корневой элемент (root
); - метод
add
, который добавляет новый элемент в связный список; - метод
print
, который выводит все значения элементов связного списка.
add(value)
- добавляет новый элемент в спискок. Если список пуст, то создается корневой элемент (Node) со значением переданным в качестве аргумента. В противном случае, происходит проход по всем элементам списка до тех пор, пока не будет найден последний элемент, после чего создается новый элемент и он добавляется в конец списка.
getSize()
- метод, который возвращает размер связного списка.
print()
- метод, который выводит все элементы связного списка в виде массива.
Класс Node
(узел) используется для хранения значений элементов, которые добавляются в связанный список. Он имеет два свойства: value
, который хранит значение элемента, и next
, который хранит ссылку на следующий элемент в связанном списке.
В конце кода создается экземпляр класса LinkedList, вызываются методы add()
для добавления нескольких элементов в связный список, а затем вызывается метод print()
, чтобы вывести значения всех элементов связного списка в консоль.
Бинарное дерево поиска. Простая реализация и теория
Бинарное дерево поиска - это структура данных, которая состоит из узлов, каждый из которых содержит значение и два потомка: левого и правого. Значения в этих узлах упорядочены таким образом, что для любого узла в левом поддереве его значения будут меньше, чем значение узла, а для любого узла в правом поддереве - больше.
Принцип работы бинарного дерева поиска заключается в том, чтобы искать элемент путем последовательного сравнения значения элемента со значением текущего узла и перехода в левое или правое поддерево в зависимости от результата этого сравнения.
Представление бинарного дерева поиска может быть рекурсивным или итеративным. Рекурсивное представление определяет дерево как объединение корня, левого поддерева и правого поддерева, каждое из которых является бинарным деревом поиска. Итеративное представление использует стек для сохранения текущего узла и продвижения по дереву.
Бинарные деревья поиска используются для быстрого поиска, вставки и удаления элементов из упорядоченных коллекций данных. Кроме того, они могут быть использованы для решения нескольких других задач, таких как поиск наибольшего и наименьшего элементов или подсчет количества элементов в дереве.
Данный код реализует бинарное дерево поиска, которое позволяет хранить и быстро находить значения в отсортированном порядке.
Класс BinaryTree
имеет метод add
, который принимает значение и добавляет его в дерево. Если дерево пустое, то создается новый узел с переданным значением и ставится в корень дерева. В противном случае происходит поиск места для нового узла путем обхода дерева начиная от корня.
Метод print
класса BinaryTree
осуществляет обход дерева в предпорядке (корень левое поддерево правое поддерево) и выводит значения узлов на экран.
Класс TreeNode
представляет узел дерева и имеет свойства value
, left
и right
, которые представляют значение узла и ссылки на левого и правого потомков.
В конце кода создается новый экземпляр дерева и к нему последовательно добавляются значения 5, 2, 6, 2, 1. Затем вызывается метод print
, который выводит значения узлов дерева в следующем порядке: 5, 2, 1, 6.
Set и Map
03 - Дополнительные основы JavaScript
Map - это объект, ключом которого может быть и символ и другой объект и любой другой тип данных
Set - это массив, значения которого всегда уникальны