Рубрики
Без рубрики

Полное введение в 30 наиболее важных структур и алгоритмов данных

Структуры и алгоритмы данных (DSA) часто считаются пугающей темой-распространенная мисс … Tagged с помощью компьютерной науки, CPP, Python, производительности.

Структуры и алгоритмы данных (DSA) часто считаются пугающей темой-распространенным неправильным? Создавая основу самых инновационных концепций в технологиях, они важны как в путешествии кандидатов на работу/стажировки, так и в путешествии опытных программистов. Мастеринг DSA подразумевает, что вы можете использовать свое вычислительное и алгоритмическое мышление, чтобы решить никогда невиданные проблемы и внести свой вклад в ценность любой технологической компании (включая свою собственную!). Понимая их, вы можете улучшить обслуживание, расширяемость и эффективность вашего кода.

Говорят, я решил централизовать все темы DSA, которые я публиковал в Twitter во время моего #100daysOfCode вызов. Эта статья стремится сделать DSA не таким пугающим, как считается. Он включает в себя 15 наиболее полезных структур данных и 15 наиболее важных алгоритмов, которые могут помочь вам получить ваши собеседования и улучшить ваши навыки конкурентного программирования. Каждая глава включает в себя полезные ссылки с дополнительной информацией и практическими проблемами. Темы DS сопровождаются графическим представлением и ключевой информацией. Каждый алгоритм реализуется в постоянно обновлять GitHub Репо Анкет На момент написания, он содержит реализации псевдокода, C ++, Python и Java (все еще в процессе) каждого упомянутого алгоритма (и не только). Это Репозиторий Расширяется благодаря другим талантливым и страстным разработчикам, которые вносят свой вклад, добавив новые алгоритмы и новые реализации языков программирования.

Содержимое

I. Структуры данных

  1. Массивы
  2. Связанные списки
  3. Стеки
  4. Очереди
  5. Карты и хэш -таблицы
  6. Графики
  7. Деревья
  8. Бинарные деревья и бинарные поисковые деревья
  9. Самобалансирующие деревья (avl Trees, красные черные деревья, Splay Trees)
  10. Куча
  11. Пытается
  12. Деревья сегмента
  13. Деревья Фенвика
  14. Несомненно, установил союз
  15. Минимальные охватывающие деревья

II Алгоритмы

  1. Разделите и победите
  2. Алгоритмы сортировки (Сорта пузырьков, сортировка, быстрая сортировка, сортировка слияния, Radix sort)
  3. Поиск алгоритмов (линейный поиск, бинарный поиск)
  4. Сито Эратостена
  5. Кнут-Моррис-Пратт алгоритм
  6. Жадный I (максимальное количество непересекающих интервалов на оси)
  7. Greedy II (проблема фракционного рюкзака)
  8. Динамическое программирование I (проблема рюкзака 0–1)
  9. Динамическое программирование II (самая длинная общая подпоследовательность)
  10. Динамическое программирование III (самое длительное увеличение подпоследовательности)
  11. Выпуклая оболочка
  12. Тровяние графика (поиск в ширине, поиск по глубине)
  13. Флойд-Варшалл/Рой-Флойд Алгоритм
  14. Алгоритм Дейкстры и алгоритм Беллман-Форд
  15. Топологическая сортировка

I. Структуры данных

1. Массивы

Массивы являются самыми простыми и наиболее распространенными структурами данных. Они характеризуются облегченным доступом элементов по индексу (позиции).

Для чего они используются?

Представьте, что у вас есть ряд театрального кресла. Каждый стул назначил позицию (слева направо), поэтому каждый зритель назначит номер со стула (ов), на котором он будет сидеть. Это массив. Разверните проблему до всего театра (ряды и колонны стульев), и у вас будет 2D -массив (матрица)!

Характеристики

  • Значения элементов размещаются в порядке и доступны по их индексу от 0 до длины массива-1;
  • Массив – это непрерывный блок памяти;
  • Они обычно изготавливаются из элементов того же типа (это зависит от языка программирования);
  • Доступ и добавление элементов быстро; Поиск и удаление не выполняются в O (1).

Полезные ссылки

2. Связанные списки

Связанные списки – это линейные структуры данных, как массивы. Основное различие между связанными списками и массивами заключается в том, что элементы связанного списка не хранятся в смежных местах памяти. Он состоит из узлов-объектов, которые хранят значение текущего элемента и ссылку на адрес на следующий элемент. Таким образом, элементы связаны с указателями.

Для чего они используются?

Одним из соответствующих применений связанных списков является реализация предыдущей и следующей страницы браузера. Двойной связанный список – это идеальная структура данных для хранения страниц, отображаемых поиском пользователя.

Характеристики

  • Они бывают трех типов: по отдельности, вдвойной и круговой;
  • Элементы не хранятся в смежном блоке памяти;
  • Идеально подходит для отличного управления памятью (использование указателей подразумевает динамическое использование памяти);
  • Вставка и удаление быстрая; Доступ и поиск элементов выполняется в линейное время.

Полезные ссылки

3. Стеки

Стек является абстрактным типом данных, который формализует концепцию сбора ограниченного доступа. Ограничение следует правилу Lifo (последнее, сначала). Поэтому последний элемент, добавленный в стеке, является первым элементом, который вы удалили из него. Стеки могут быть реализованы с использованием массивов или связанных списков.

Для чего они используются?

В наиболее распространенном примере реальной жизни используются пластины, размещенные один над другим в столовой. Пластина, которая находится наверху, первой, которая будет удалена. Тарелка, размещенная в Bottommost, является той, которая остается в стеке в течение самого длительного периода времени. Одна ситуация, когда стеки наиболее полезны, это когда вам нужно получить обратный порядок заданных элементов. Просто толкните их всех в стек, а затем выпейте их. Еще одним интересным приложением является действительная проблема скобок. Учитывая строку парарантов, вы можете проверить, что они сопоставлены с использованием стека.

Характеристики

  • Вы можете получить доступ к последнему элементу только за один раз (тот, который вверху);
  • Одним из недостатков является то, что после того, как вы появляетесь с вершины, чтобы получить доступ к другим элементам, их значения будут потеряны из памяти стека;
  • Доступ других элементов выполняется в линейное время; Любая другая операция находится в O (1).

Полезные ссылки

4. Очереди

Очередь – это еще один тип данных из сбора ограниченного доступа, как и ранее обсуждаемый стек. Основное отличие состоит в том, что очередь организована после модели FIFO (сначала, сначала): первый вставленный элемент в очереди является первым элементом, который будет удален. Очерки могут быть реализованы с использованием массива фиксированной длины, круговой массивы или связанного списка.

Для чего они используются?

Лучшее использование этого абстрактного типа данных (ADT), конечно же, моделирование очереди в реальной жизни. Например, в приложении Call Center очередь используется для сохранения клиентов, которые ждут, чтобы получить помощь от консультанта-эти клиенты должны получить помощь в обращении, который они вызвали. Один особенный и очень важный тип очереди – это приоритетная очередь. Элементы вводятся в очередь на основе «приоритета», связанного с ними: элемент с наивысшим приоритетом является первым в очереди. Этот ADT имеет важное значение во многих алгоритмах графика (алгоритм Dijkstra, BFS, алгоритм PRIM, Кодирование Хаффмана-больше о них ниже). Он реализован с использованием кучи. Другой особый тип очереди – это Deque ( каламбур Это произносится «колода»). Элементы могут быть вставлены/удалены из обоих окончаний очереди.

Характеристики

  • Мы можем непосредственно получить доступ только к «самым старым» элементу;
  • Поиск элементов удалит все доступные элементы из памяти очереди;
  • Заскочить/толкать элементы или получить переднюю часть очереди выполняются в постоянное время. Поиск линейный.

Полезные ссылки

5. Карты и хэш -таблицы

Карты (словаря) – это абстрактные типы данных, которые содержат набор ключей и набор значений. Каждый ключ имеет значение, связанное с ним. Хэш -таблица является конкретным типом карты. Он использует хэш -функцию для генерации хэш -кода, в массив ведра или слотов: ключ хэшируется, и полученный хэш указывает, где хранится значение. Наиболее распространенная функция хэш (среди многих) – это постоянная функция модуля. е – грамм. Если константа – 6, значение ключа x – это x%6 Анкет В идеале, хэш -функция назначит каждый ключ к уникальному ведру, но в большинстве их конструкций используется несовершенная функция, которая может вести с столкновением между ключами с тем же сгенерированным значением. Такие столкновения всегда в некотором роде.

Для чего они используются?

Наиболее известным применением карт является языковой словарь. Каждое слово из языка присваивало его определение. Он реализован с использованием упорядоченной карты (ее клавиши упорядочены в алфавитном порядке). Контакты также это карта. Каждое имя имеет номер телефона, назначенный ему. Другим полезным применением является нормализация значений. Допустим, мы хотим назначать на каждую минуту дня (24 минуты) индекс от 0 до 1439. Хэш -функция будет h (x) .hour*60+x.minute .

Характеристики

  • Ключи уникальны (без дубликатов);
  • Сопротивление столкновения: должно быть трудно найти два разных входа с одним и тем же ключом;
  • Сопротивление до изображения: Учитывая значение H, должно быть трудно найти ключ X, такой, что h (x) = h ;
  • Второе сопротивление предварительного изображения: учитывая ключ и его значение, должно быть трудно найти другой ключ с тем же значением;
  • терминология:
  • * “карта”: Java, C ++;
  • * «Словарь»: Python, Javascript, .net;
  • * “Ассоциативный массив”: PHP.
  • Поскольку карты реализованы с использованием самобалансирующих красных черных деревьев (объяснены ниже), все операции выполняются в O (log n); Все операции хеш -таблицы постоянны.

Полезные ссылки

6. Графики

График-это нелинейная структура данных, представляющая пару из двух наборов: G = {v, e} , где V – набор вершин (узлов) и e набор краев (стрелки). Узлы являются значениями, взаимосвязанными ребрами-линиями, которые изображают зависимость (иногда связанную с стоимостью/расстоянием) между двумя узлами. Существует два основных типа графиков: направленные и неправомерные. На неправомерном графике, край (x, y) доступен в обоих направлениях: (x, y) и (y, x) Анкет В направленном графике, края (x, y) называется стрелкой, и направление определяется порядок вершин в его имени: Стрелка (x, y) отличается от стрелы (y, x) Анкет

Для чего они используются?

Графики являются основой всех типов сети: социальной сети (например, Facebook, LinkedIn) или даже сети улиц из города. Каждый пользователь платформы социальных сетей-это структура, содержащая все его/ее личные данные-она представляет собой узел сети. Дружба на Facebook – это ребра на неправомерном графике (потому что он взаимный), в то время как в Instagram или Twitter взаимосвязь между учетной записью и ее последователями/следующие учетные записи – это стрелки на направленном графике (не взаимный).

Характеристики

Теория графика – это обширная область, но мы собираемся выделить несколько наиболее известных концепций:

  • Степень узла на неправомерном графике – это количество его краев инцидента;
  • Внутренняя/внешняя степень узла на направленном графике – это количество стрел, которые направляются к/из этого узла;
  • Цепь от узла x до узла Y представляет собой последовательность смежных краев, с x в качестве левой конечности и Y в качестве правого;
  • Цикл – это цепь; График может быть циклическим/ациклическим; График подключен, если есть цепь между любыми двумя узлами от V;
  • График может быть пройден и обработан с помощью первого поиска в ширине (BFS) или глубины первого поиска (DFS), оба сделанные в O (| V |+| E |), где | S | является кардиналом набора; Проверьте ссылки ниже, чтобы узнать другую важную информацию в теории графика.

Полезные ссылки

7. Деревья

Дерево – это неопределенный график, минимальный с точки зрения связности (если мы устраняем один край, график больше не будет подключена) и максимально с точки зрения ациклики (если мы добавим один край, график больше не будет ациклическим). Анкет Таким образом, любой ациклический подключенный неизорированный график – это дерево, но для простоты мы будем называть укоренившиеся деревья как деревья. Корень – это один фиксированный узел, который устанавливает направление краев на дереве, так что именно там все «начинается». Листья являются терминальными узлами дерева-вот где все “заканчивается”. Ребенок версии – это падающая версия под ним. Вертина может иметь несколько детей. Родитель версии-это версия инцидента над ней-она уникальна.

Для чего они используются?

Мы используем деревья в любое время, когда мы должны изображать иерархию. Наше собственное генеалогическое дерево является прекрасным примером. Ваш старший предок – корень дерева. Самое молодое поколение представляет листья. Деревья также могут представлять подчиненные отношения в компании, в которой вы работаете. Таким образом, вы можете узнать, кто ваш менеджер и кем вам следует управлять.

Характеристики

  • У корня нет родителей;
  • Листья нет детей;
  • Длина цепи между корнем и узлом x представляет уровень x расположен на;
  • Высота дерева является максимальным уровнем его (3 в нашем примере);
  • Наиболее распространенным методом для прохождения дерева является DFS в O (| V |+| E |), но мы также можем использовать BFS; Порядок узлов, пройденных на любом графике с использованием DFS, образуют дерево DFS, которое указывает нам время, когда был посещен узел.

Полезные ссылки

8. Бинарные деревья и бинарные поисковые деревья

Бинарное дерево – это особый тип дерева: каждая версия может иметь максимум двух детей. В строгом двоичном дереве каждый узел имеет ровно двое детей, за исключением листьев. Полное двоичное дерево с уровнями n имеет все 2ⁿ-1 Возможные узлы. Дерево бинарного поиска-это двоичное дерево, где значения узлов принадлежат полностью упорядоченному набору-любое произвольное значение выбранного узла больше, чем все значения от левого поддерево и меньше, чем из правого поддерева.

Для чего они используются?

Одним из важных применений BTS является представление и оценка логических выражений. Каждое выражение может быть разложено на переменные/константы и операторы. Этот метод написания выражения называется обратной польской нотацией (RPN). Таким образом, они могут сформировать двоичное дерево, где внутренние узлы являются операторами, а листья-переменные/константы-оно называется абстрактным синтаксическим деревом (AST). BST часто используются из -за их быстрого поиска свойства Keys. AVL Trees, красные черные деревья, заказанные наборы и карты реализованы с использованием BSTS.

Характеристики

  • Существует три типа DFS Travers для BTS:
  • * Предварительный заказ (корень, слева, справа);
  • * Inorder (слева, корень, справа);
  • * Postorder (слева, справа, корень); все сделано в (n) времени;
  • Обход на заказ дает нам все узлы в дереве в порядке возрастания;
  • Самый левый узел является минимальным значением в BST, а самый правый-максимум;
  • Обратите внимание, что RPN является обходом AST;
  • BST имеет преимущества отсортированного массива, но недостаток логарифмической вставки-все его операции выполняются во время O (log n).

Полезные ссылки

9. Самобалансирующие деревья

Все эти типы деревьев являются самобалансирующими бинарными деревьями поиска. Разница в том, как они уравновешивают свою высоту в логарифмическое время. AVL Деревья самобалансируются после каждой вставки/делеции, потому что разница в модуле между высоты левого поддерею и правой поддереем узла максимум 1. AVLs названы в честь их изобретателей: Адельсон-Увелки и Лэндис. В красных черных деревьях каждый узел хранит дополнительный бит, представляющий цвет, используемый для обеспечения баланса после каждой операции вставки/удаления. В Splay Trees, недавно доступные узлы могут быть быстро включены снова, поэтому амортизированная сложность любой операции все еще остается O (log n).

Для чего они используются?

AVL, кажется, является лучшей структурой данных в теории базы данных. RBT используются для организации кусков сопоставимых данных, таких как фрагменты текста или числа. В версии 8 Java HashMaps реализованы с использованием RBT. Структуры данных в вычислительной геометрии и функциональном программировании также построены с RBT. Splay Trees используются для кэшей, распределителей памяти, коллекционеров мусора, сжатия данных, веревок (замена строки, используемой для длинных текстовых строк), в Windows NT (в виртуальной памяти, сети и коде системы файловой системы).

Характеристики

  • Амортизированная временная сложность любой операции в любой самобалансировании BST-O (log n);
  • Максимальная высота AVL в худшем случае составляет 1,44 * log2n (Почему? *Подсказка: подумайте о случае AVL со всеми полными уровнями, за исключением последнего, у которого есть только один элемент);
  • AVLS являются самыми быстрыми на практике для поиска элементов, но вращение подтереев для самобалансировки является дорогостоящим;
  • Между тем, RBT обеспечивают более быстрые вставки и удаления, потому что нет вращений;
  • Splay Trees не нужно хранить какие -либо данные о бухгалтерском учете.

Полезные ссылки

10.Heaps

Min-Heap-это двоичное дерево, где каждый узел имеет свойство, что его значение больше или равное значению его родителя: val [par [x]] [x] , с x узлом кучи, где val [x] его значение и par [x] его родитель. Существует также максимум, который реализует противоположную связь. Двоичная куча – это полное двоичное дерево (все его уровни заполнены, за исключением, может быть, для последнего уровня).

Для чего они используются?

Как мы обсуждали об этом несколькими днями ранее, приоритетные очереди могут быть эффективно реализованы с использованием двоичной кучи, поскольку она поддерживает операции insert (), delete (), extractmax () и demoresekey () во время O (log n). Таким образом, кучи также важны в алгоритмах графика (из -за очереди приоритета). В любое время, когда вам понадобится быстрый доступ к максимальному/минимальному элементу, куча – лучший вариант. Кучи также являются основой алгоритма кучи.

Характеристики

  • Это всегда сбалансировано: в любое время мы удаляем/вставляем элемент в структуру, мы просто должны «просеять»/«охватить» его до тех пор, пока он не окажется в правильном положении;
  • родитель узла k> 1 это [k/2] (где [x] является целой частью x), а его дети – 2*k и 2*k+1 ;
  • Альтернатива приоритетной очереди установлена, упорядоченный_map (в C ++) или любая другая упорядоченная структура, которая может легко допустить доступ к минимальному/максимальному элементу;
  • Корень приоритет, поэтому временная сложность его доступа составляет O (1), вставка/делеция выполняется в O (log n); Создание кучи выполняется в O (n); Heapsort в O (n*log n).

Полезные ссылки

11.tre

Три – это эффективная структура данных поиска информации. Также известный как дерево префикса, это дерево поиска, которое позволяет вставить и искать в сложности времени O (L), где L – длина ключа. Если мы храним ключи в хорошо сбалансированном BST, вам потребуется время, пропорциональное для L * log n, где n – количество ключей в дереве. Таким образом, TRIE – это более быстрая структура данных (с O (L)) по сравнению с BST, но штраф находится в требованиях к хранению Trie.

Для чего они используются?

Три в основном используется для хранения струн и их ценностей. Одним из самых крутых приложений является автозаполнение типирования и автосуггированных веществ в строке поиска Google. Trie – лучший выбор, потому что это самый быстрый вариант: более быстрый поиск более ценен, чем хранилище, если мы не использовали Trie. Ортографическая автозаректировка напечатанных слов также выполняется с использованием TRIE, ищет слово в словаре или, возможно, для других случаев его в том же тексте.

Характеристики

  • У этого есть ассоциация ключей; Ключ обычно – это слово или префикс этого, но это может быть любой упорядоченный список;
  • Корень имеет пустую строку в качестве ключа;
  • Разница в длине между значением узла и значениями его детей составляет 1; Таким образом, дети корня будут хранить значение длины 1; В заключение мы можем сказать, что узел X с уровня k имеет значение длины k;
  • Как мы уже говорили, временная сложность вставки/поисковых операций составляет O (L), где L – длина ключа, которая намного быстрее, чем o (log n) BST, но сравнимый с хэштетом;
  • Сложность пространства на самом деле является недостатком: o (alphabet_size*l*n).

Полезные ссылки

12. Деревья сегмента

Дерево сегмента – это полное двоичное дерево, которое позволяет эффективно отвечать на вопросы, в то же время легко изменяя его элементы. Каждый элемент по индексу I в данном массиве представляет лист, помеченный [i, i] интервал. Узел с надписью детей с надписью [x, y] соответственно [y, z] , будет иметь [x, z] интервал как ярлык. Следовательно, с учетом n элементов (0-индексация) корень дерева сегмента будет помечен [0, n-1] Анкет

Для чего они используются?

Они чрезвычайно полезны в задачах, которые могут быть решены с использованием Divide & Conquer (концепция первых алгоритмов, которую мы собираемся обсудить), а также могут потребоваться обновления их элементов. Таким образом, при обновлении элемента, любой интервал, содержащий его, также модифицируется, поэтому сложность является логарифмической. Например, сумма/максимум/минимум n, данных элементов, являются наиболее распространенными применениями деревьев сегмента. Бинарный поиск также может использовать дерево сегмента, если происходит обновления элементов.

Характеристики

  • Будучи бинарным деревом, узел X будет иметь 2*x и 2*x+1 Как дети и [x/2] Как родитель, где [x] целочисленная часть x;
  • Один эффективный метод обновления целого диапазона в дереве сегмента называется «ленивое распространение», а также выполняется в O (log n) (см. Ссылки ниже для реализации операций);
  • Они могут быть K-меры: например, имея Q-запросы о поиске суммы заданных подводных приложений одной матрицы, мы можем использовать двухмерное дерево сегмента;
  • Обновление элементов/диапазонов требует времени O (log n); Ответ на запрос постоянный (O (1));
  • Сложность пространства является линейной, что является большим преимуществом: O (4*N).

Полезные ссылки

13. Деревья Фенвика

Дерево Фенвика, также известное как двоичное индексированное дерево (бит), представляет собой структуру данных, которая также используется для эффективных обновлений и запросов. По сравнению с деревьями сегмента, биты требуют меньше места, и их легче реализовать.

Для чего они используются?

Биты используются для расчета сумм префикса – сумма префикса элемента в положении ITH – это сумма элементов от первой позиции в ITH. Они представлены с использованием массива, где каждый индекс представлен в бинарной системе. Например, индекс 10 эквивалентен индексу 2 в десятичной системе.

Характеристики

  • Конструкция дерева является наиболее интересной частью: во-первых, массив должен быть 1-индексированным; Чтобы найти родителя узла X, вы должны преобразовать его индекс X в бинарную систему и перевернуть наиболее значимый бит; бывший. родитель узла 6 составляет 4; 6*2²+1 *2¹+0*2⁰ => 1 “1” 0 (flip) =>*2²+0*2¹+0*2⁰ ;
  • Наконец, и элементы, каждый узел должен содержать интервал, который может быть добавлен в сумму префикса (подробнее о конструкции и реализации в ссылках ниже);
  • Сложность времени по -прежнему является (log n) для обновлений и O (1) по вопросам, но сложность пространства является еще большим преимуществом: O (n) по сравнению с O (4*n).

Полезные ссылки

14. Несомненно, установил союз

Нам дают n элементов, каждый из которых представляет отдельный набор. Несоответствие Set Set Union (DSU) позволяет нам выполнять две операции:

  1. Союз – объединить любые два набора (или объединить наборы двух разных элементов, если они не из одного и того же набора);
  2. Найдите – найдите набор, из которого идет элемент.

Для чего они используются?

DSU очень важны в теории графика. Вы можете проверить, поступают ли две вершины из одного и того же подключенного компонента или, возможно, даже объединить два подключенных компонента. Давайте возьмем пример городов и городов. Поскольку соседние города с демографическим и экономическим ростом расширяются, они могут легко создать мегаполис. Поэтому два города объединены, и их жители живут в одном мегаполисе. Мы также можем проверить, в каком городе живет человек, позвонив функции находки.

Характеристики

  • Они представлены с использованием деревьев; Как только два набора объединены, один из двух корней становится основным корнем, а родитель другого корня – один из листьев другого дерева;
  • Одним из видов практической оптимизации является сжатие деревьев по их высоту; Таким образом, объединение составлено самым большим деревом, чтобы легко обновить оба их данных (см. Реализацию ниже);
  • Все операции выполняются во время O (1).

Полезные ссылки

15. Минимальные охватывающие деревья

Учитывая подключенный и неистовый график, охвативное дерево этого графика представляет собой подграф, который является деревом и соединяет все узлы вместе. Один график может иметь много разных охраняемых деревьев. Минимальное дерево охватывающего (MST) для взвешенного, подключенного и неистового графика представляет собой охватывающее дерево с весом (стоимостью), меньше или равным весу любого другого дерева, охваченного. Вес штурманого дерева – это сумма весов, придаваемой каждому краю охраняющего дерева.

Для чего они используются?

Проблема MST – это проблема оптимизации, проблема минимальной стоимости. Наличие сети маршрутов, мы можем рассмотреть, что одним из факторов, которые влияют на установление национального маршрута между N городами, является минимальное расстояние между двумя соседними городами. Таким образом, национальный маршрут представлен графиком сети дорог.

Характеристики

  • Быть деревом, MST графика с n вершинами имеет n-1 края ; это может быть решено с помощью:
  • * Алгоритм PRIM-лучший вариант для плотных графиков (графики с n узлами и количество краев близки к n (n-1)/2 );
  • * Алгоритм Крускала – в основном используется; Это жадный алгоритм, основанный на союзе с неразрешающимся набором (мы также будем обсуждать об этом);
  • Временная сложность построения – это O (n log n) или O (n log m) для Kruskal (это зависит от графика) и O (N²) для Prim.

Полезные ссылки

II Алгоритмы

1. Разделите и победите

Divide and Conquer (DAC) не является конкретным алгоритмом, а важной категорией алгоритмов, которые необходимо понимать, прежде чем погрузиться в другие темы. Он используется для решения задач, которые можно разделить на подпроекты, которые похожи на исходную проблему, но меньше по размеру. Затем DAC рекурсивно решает их и, наконец, объединяет результаты, чтобы найти решение проблемы. У него три этапа:

  • Разделение – проблемы на подпроекты;
  • Завоевание – подзадачи с использованием рекурсии;
  • MERGE – результаты подзадачи в окончательное решение.

Для чего это используется?

Одним из практических применений ЦАП является параллельное программирование с использованием нескольких процессоров, поэтому подзадачи выполняются на разных машинах. DAC является основой многих алгоритмов, таких как быстрая сортировка, сортировка слияния, двоичный поиск или быстрые алгоритмы умножения.

Характеристики

  • Каждая проблема ЦАП может быть написана как рецидивовое отношение; Таким образом, важно найти основной случай, который останавливает рекурсию;
  • Его сложность T (n) = d (n)+c (n)+m (n) Это означает, что на каждом этапе есть другая сложность в зависимости от проблемы.

Полезные ссылки

2. Сортировка алгоритмов

Алгоритм сортировки используется для перестройки заданных элементов (из массива или списка) в соответствии с оператором сравнения по элементам. Когда мы ссылаемся на отсортированный массив, мы обычно думаем о восходящем порядке (оператор сравнения «<»). Существуют различные типы сортировки, с разными сложностями времени и пространства. Некоторые из них основаны на сравнении, другие нет. Вот самые популярные/эффективные методы сортировки:

Пузырьковые сортировки

Bubble Sort – один из самых простых алгоритмов сортировки. Он основан на повторном обмене между соседними элементами, если они находятся в неправильном порядке. Он стабилен, его временная сложность – O (N²), и он нуждается в вспомогательном пространстве O (1).

Счет

Подсчет сортировки не является сортировкой на основе сравнения. В основном он использует частоту каждого элемента (своего рода хэшинг), определяя минимальное и максимальное значение, а затем итерация между ними, чтобы поместить каждый элемент на основе его частоты. Это сделано в O (n), а пространство пропорционально диапазону данных. Это эффективно, если диапазон ввода не значительно больше, чем количество элементов.

Быстрый сортировка

Быстрый сортировка – это применение разделения и завоевания. Он основан на выборе элемента в качестве поворота (сначала, последней или медианой), а затем замены элементов, чтобы разместить поворот между всеми элементами меньше, чем он, и всеми элементами больше, чем он. Он не имеет дополнительного пространства и O (n*log n) сложности времени – лучшая сложность для методов, основанных на сравнении. Вот демонстрация с выбором The Pivot в качестве последнего элемента:

Сортировка слиянием

Сорт -сортировка также является приложением Divide & Conquer. Он делит массив на две половинки, сортирует каждую половину, а затем объединяет их. Его временная сложность также – O (n*log n), поэтому она также очень быстро, как быстрый сортировка, но, к сожалению, ему нужно (n) дополнительное пространство для хранения двух субаррей одновременно и, наконец, объединить их.

Radix sort

Radix Sort использует сортировку подсчета в качестве подпрограммы, поэтому это не алгоритм на основе сравнения. Откуда мы знаем, что CS недостаточно? Предположим, мы должны сортировать элементы в [1, N²] Анкет Используя CS, это потребует нас O (N²). Нам нужен линейный алгоритм – O (N+K), где элементы находятся в диапазоне [1, k] Анкет Он сортирует цифру элементов по цифр, начиная с наименее значимой (единицы), в наибольшее количество (десятки, сотни и т. Д.). Дополнительное пространство (от CS): O (n).

Полезные ссылки

3. Поиск алгоритмов

Алгоритмы поиска предназначены для проверки наличия элемента в структуре данных и даже вернуть его. Есть несколько методов поиска, но вот самые популярные два:

Линейный поиск

Подход этого алгоритма очень прост: вы начинаете искать свое значение из первого индекса структуры данных. Вы сравниваете их один за другим, пока ваше значение и ваш текущий элемент не станут равны. Если это конкретное значение не в DS, верните -1. Сложность времени: O (n)

Бинарный поиск

BS – один эффективный алгоритм поиска, основанный на разделении и завоевании. К сожалению, это работает только только в отсортированных структурах данных. Будучи методом ЦАП, вы постоянно делите DS в двух половинах и сравниваете свое значение для поиска со значением среднего элемента. Если они равны, поиск закончен. В любом случае, если ваше значение больше/меньше, чем оно, поиск должен продолжаться справа/левой половины. Сложность времени: o (log n)

Полезные ссылки

4. Сито Эратостена

Учитывая целочисленное число N, распечатайте все основные числа меньше или равны n. Siethe of Eratosthenes является одним из наиболее эффективных алгоритмов, которые решают эту проблему, и она идеально подходит для n меньше, чем 10.000.000 Анкет Метод использует частотный список/карту, которая отмечает первичность каждого числа в диапазоне [0, n] : ОК [x] = 0 Если x является ярким , ОК [x] = 1 в противном случае. Мы начинаем выбирать каждый основной номер из нашего списка и отмечать его кратные из списка с 1 – таким образом, мы выбираем цифры без опознавательных (0). Наконец, мы можем легко ответить в O (1) на столько же запросов, сколько хотим. Классический алгоритм необходим во многих приложениях, но есть несколько оптимизаций, которые мы можем сделать. Во -первых, мы можем легко заметить 2 – единственное даже простое число, поэтому мы можем проверить его кратные отдельно, а затем итерация в диапазоне, чтобы найти основные числа от двух до двух. Во -вторых, очевидно, что для числа x мы ранее проверяли 2x, 3x, 4x и т. Д. Когда мы переживали 2, 3 и т. Д. Таким образом, наша кратная проверка на петлю может начинаться с X² каждый раз. Наконец, даже половина этих мультипликаций даже, и мы также итерация через нечетные первичные числа, поэтому мы можем легко итерации только от 2*x до 2*x в петле проверки мультипликации. Сложность пространства: O (n) Сложность времени: O (n*log (log n)) для классического алгоритма, O (n) для оптимизированного. Почему O (n*log (log n))? Ответ

Полезные ссылки

5. Кнут-Моррис-Пратт алгоритм

Учитывая текст длины N и шаблон длины M, найдите все входы шаблона в тексте. Алгоритм Кнут-Моррис-Пратта (КМП) является эффективным способом решения проблемы сопоставления шаблонов. Наивное решение основано на использовании «скользящего окна», где мы сравниваем символ с символом каждый раз, когда устанавливаем новый начальный индекс, начиная с индекса 0 текста с индексом N-M. Таким образом, временная сложность o (m*(n-m+1)) ~ o (n*m). KMP является оптимизацией наивного решения: это делается в O (n), и он работает лучше всего, когда у шаблона много повторяющихся подчинений. Таким образом, он также использует скользящее окно, но вместо сравнения всех символов с подстрокой он постоянно ищет самый длинный суффикс текущего подчинения, который также является его префиксом. Другими словами, в любое время, когда мы обнаруживаем несоответствие после некоторых матчей, мы уже знаем некоторых символов в тексте следующего окна. Поэтому бесполезно сравниться с ними снова, поэтому мы перезагружаем сопоставление с тем же символом в тексте с персонажем после этого префикса. Как мы узнаем, сколько персонажей мы должны пропустить? Ну, мы должны построить предварительный массив, который рассказывает нам, сколько персонажей следует пропустить.

Полезные ссылки

6. Жадный

Жадный метод в основном используется для задач, которые требуют оптимизации, а локальное оптимальное решение приводит к глобальному оптимальному решению. При этом, при использовании жадного, оптимальное решение на каждом этапе приводит к общему оптимальному решению. Однако в большинстве случаев решение, которое мы принимаем на одном шаге, влияет на список решений для следующего шага. В этом случае алгоритм должен быть математически продемонстрирован. Greedy также создает отличные решения по некоторым математическим проблемам, но не на всех (возможно, что самое оптимальное решение не гарантировано)!

Жадный алгоритм обычно имеет пять компонентов:

  • Набор кандидатов – из которого создается решение;
  • Функция отбора – выбирает лучшего кандидата;
  • функция осуществимости – может определить, способен ли кандидат внести свой вклад в решение;
  • целевая функция – назначает кандидата (частичного) решения;
  • Функция решения – зажигает решение из частичных решений.

Фракционная проблема рюкзака

Учитывая веса и значения n элементов, нам необходимо поместить эти элементы в рюкзаке емкости w, чтобы получить максимальное общее значение в рюкзаке (разрешено изделия предметов: значение произведения пропорционально его весу). Основная идея жадного подхода состоит в том, чтобы сортировать все элементы на основе их соотношения стоимости/веса. Затем мы можем добавить как можно больше целых предметов. В тот момент, когда мы находим предмет более тяжелым (W2), чем наш доступный вес, оставленный в рюкзаке (W1), мы будем фракционировать его: возьмите только W2-W1 этого, чтобы максимизировать нашу прибыль. Гарантировано, что это жадное решение верно.

Полезные ссылки

7. Динамическое программирование

Динамическое программирование (DP) является аналогичным подходом к разделению и завоеванию. Это также разбивает проблему на аналогичные подзадачи, но они на самом деле перекрываются и зависимы в коде – они не решаются независимо. Результат каждой подзадачи может быть использован в любое время позже, и она построена с использованием мемуазации (предварительно рассеяния). DP в основном используется для оптимизации (времени и пространства) и это основано на поиске повторения. Приложения DP включают серию номерных номеров Fibonacci, Башня Ханой, Рой-Флойд-Варшалл, Дейкстра и т. Д. Ниже мы собираемся обсудить решение DP -решения проблемы рюкзака 0–1.

0–1 проблема рюкзака

Учитывая веса и значения n элементов, нам необходимо поместить эти элементы в рюкзаке емкости w, чтобы получить максимальное общее значение в рюкзаке (элементы фракционирования так же, как в жадном решении не допускается). Свойство 0–1 определяется тем фактом, что мы должны либо выбрать весь элемент, либо не выбрать его вообще. Мы строим структуру DP в качестве матрицы DP [i] [CW] Хранение максимальной прибыли, которую мы можем получить, выбирая объекты I, общий вес, общий вес, CW. Легко заметить, что мы должны сначала инициализировать DP [1] [W [i]] с v [i] , где w [i] вес объекта и v [i] его ценность. Рецидив следующее: DP [i] [CW] (DP [i-1] [CW], DP [I-1] [CW-W [i]]+V [i]) Анкет Давайте немного проанализируем это. DP [I-1] [CW] Описывает случай, в котором мы не добавляем текущий элемент в рюкзаке. DP [I-1] [CW-W [i]]+V [i] это случай, когда мы добавляем элемент. Это, как сказано, DP [I-1] [CW-W [i]] является максимальной прибылью от получения элементов I-1: поэтому их вес является текущим весом без веса нашего предмета. Наконец, мы добавляем к нему значение нашего элемента. Ответ хранится в dp [n] [w] Анкет Оптимизация производится с помощью простого наблюдения: в рецидиве текущая линия (строка) зависит только от предыдущей строки. Следовательно, хранение структуры DP в матрице не требуется, поэтому мы должны выбрать массив для лучшей сложности пространства: O (n). Сложность времени: O (N*W).

Полезные ссылки

8. Самая длинная общая последующая последовательность

Учитывая две последовательности, найдите длину самой длинной подпоследовательности, присутствующей в них обоих. Последовательность – это последовательность, которая появляется в одном и том же относительном порядке, но не обязательно смежна. Например, «BCD», «ABDG», «C» являются последующими «ABCDEFG». Вот еще одно применение динамического программирования. Алгоритм LCS использует DP, чтобы решить проблему сверху. Фактическая подзадача найдет самую длинную общую последующую последовательность, которая начинается из индекса I в последовательности A, соответственно из индекса J в последовательности B. Далее мы построим структуру DP LCS [] [] (Матрица), где LCS [i] [J] является максимальной длиной общей последующей последовательности, которая начинается с индекса I в A, соответственно индекс J в B. Мы собираемся построить ее нисходящим образом. Решение, очевидно, хранится в LCS [n] [M] , где n – длина a и m длины B. Рецидивовое отношение довольно простое и интуитивно понятное. Для простоты мы рассмотрим, что обе последовательности 1-индексированные. Во -первых, мы собираемся инициализировать LCS [i] [0] , 1 <= i <= n и LCS [0] [J] , 1 <= j <= m , с 0, как основные случаи (нет последующей, которая начинается с 0). Затем мы примем во внимание два основных случая: если A [i] равен B [J] , тогда LCS [i] [j] [i-1] [J-1] +1 (Один более идентичный символ, чем предыдущий LCS). В противном случае это будет максимум между LCS [i-1] [j] (Если a [i] не принимается во внимание) и LCS [i] [J-1] (Если b [j] не принимается во внимание). Сложность времени: O (n*m) Дополнительное пространство: O (n*m)

Полезные ссылки

9. Самое длинное увеличение подпоследовательности

Учитывая последовательность a элементов, найдите длину самой длинной подпоследовательности, так что все его элементы сортируются в растущем порядке. Последовательность – это последовательность, которая появляется в одном и том же относительном порядке, но не обязательно смежна. Например, «BCD», «ABDG», «C» являются последующими «ABCDEFG». LIS – еще одна классическая проблема, которая может быть решена с использованием динамического программирования. Поиск максимальной длины увеличивающейся подпоследовательности осуществляется с использованием массива l [] Как структура DP, где l [i] максимальная длина увеличивающейся подпоследовательности, которая содержит [Я] , имея свои элементы из [A [i],…, a [n]] подпоследовательность. l [i] 1, если все элементы после A [i] меньше этого. В противном случае, это максимум 1+ между всеми элементами после [i], которые больше, чем это. Очевидно, l [n] = 1 , где n – длина А. Реализация выполняется вверх вверх (начиная с конца). Одна проблема оптимизации появляется в поиске максимума между всеми элементами после текущего элемента. Лучшее, что мы можем сделать, – это бинарный поиск максимального элемента. Чтобы также найти последующую известную максимальную длину, нам просто нужно использовать дополнительный массив ind [] , это хранит индекс каждого максимального значения. Сложность времени: O (n*log n) Дополнительное пространство: O (n)

Полезные ссылки

10. Выпуклая оболочка

Учитывая набор n точек в той же плоскости, найдите минимальный выпуклый полигон площади, который содержит все заданные точки (расположенные внутри многоугольника или по бокам). Такой многоугольник называется выпуклым корпусом. Проблема корпуса – это классическая геометрия, в которой много приложений в реальной жизни. Например, избегание столкновений: если выпуклый корпус автомобиля избегает столкновений, то и автомобиль. Вычисление путей выполняется с использованием выпуклых представлений автомобилей. Анализ формы также проводится с помощью выпуклых корпусов. Таким образом, обработка изображений легко выполняется путем сопоставления моделей с помощью их выпуклого дерева дефицита. Есть некоторые алгоритмы, используемые для поиска выпуклого корпуса, таких как алгоритм Джарвиса, сканирование Грэма и т. Д. Сегодня мы собираемся обсудить сканирование Грэма и некоторых полезных оптимизаций. Грэм сканирует точки под их полярным углом – наклон линии, определяемый определенной точкой, а другие выбранные точки. Затем стек используется для хранения выпуклого корпуса в текущий момент. Когда точка x втянута в стек, другие точки будут выскочить из стека, пока x и линия, определенная по последним двум точкам, не образуют угол меньше 180 °. Наконец, последний пункт, введенный в стек, закрывает многоугольник. Этот подход имеет временную сложность O (n*log n) из -за сортировки. Однако этот метод может создавать точные ошибки при расчете наклона. Одно улучшенное решение, которое имеет такую же сложность времени, но меньшие ошибки сортируют точки по их координатам (x, затем Y). Затем мы рассмотрим линию, сформированную самыми левыми и правыми точками, и проблема разделена на две подзадачи. Наконец, мы находим выпуклый корпус с каждой стороны линии. Выпуклым корпусом всех данных точек является воссоединение двух корпусов.

Полезные ссылки

11. График обход

Проблема переселения графиков обращается к посещению всех узлов в определенном порядке, обычно вычисляя другую полезную информацию на этом пути.

Широкий первый поиск

Алгоритм поиска (BFS) в ширине является одним из наиболее распространенных способов определить, подключен ли график или нет-или, другими словами, найти подключенный компонент исходного узла BFS. BFS также используется для вычисления кратчайшего расстояния между исходным узлом и всеми другими узлами. Другая версия BFS – алгоритм Ли, используемый для вычисления кратчайшего пути между двумя ячейками в сетке. Алгоритм начинается с посещения исходного узла, а затем и его соседей, которые будут выдвинуты в очередь. Первый элемент из очереди заскочил. Мы посетим всех его соседей и подтолкнули те, которые ранее не посещали в очередь. Процесс повторяется до тех пор, пока очередь не станет пустой. Когда очередь становится пустой, это означает, что все достижимые вершины были посещены, а алгоритм заканчивается.

Глубина-первый поиск

Алгоритм поиска в глубине (DFS) является еще одним распространенным методом обхода. На самом деле это лучший вариант, когда речь идет о проверке подключения графика. Сначала мы посещаем корневой узел и вставляем его в стек. Хотя стек не пуст, мы осматриваем узел вверху. Если у узела есть не посещаемые соседей, один из них выбран и толкен в стеке. В противном случае, если все его соседи были посещены, мы выдвигаем узел. Когда стек становится пустым, алгоритм заканчивается. После такого обхода образуется дерево DFS. Дерево DFS имеет много применений; Одним из наиболее распространенных является хранение «запуска» и «окончания» времени каждого узла – в тот момент, когда он входит в стек, соответственно в тот момент, когда он выскочил из него.

Полезные ссылки

12. Флойд-Варшалл Алгоритм

Алгоритм Floyd-Warshall/Roy-Floyd решает проблему с самым коротким путем All: найти самые короткие расстояния между каждой парой вершин на заданном направленном графике. FW – это динамическое приложение программирования. Структура DP (матрица) dist [] [] инициализируется с матрицей входного графика. Затем мы рассматриваем каждую вершину как промежуточное соединение между двумя другими узлами. Самые короткие пути обновляются между каждыми двумя парами узлов, с любым узлом k в качестве промежуточной вершины. Если k – это промежуточное место в кратчайшем пути между i и j, пыль [это] становится максимум между dist [i] [k]+dist [k] [j] и dist [i] [j] Анкет Временная сложность: O (N³) Сложность пространства: O (N²)

Полезные ссылки

13. Алгоритм Дейкстры и алгоритм Беллман-Форд

Алгоритм Дейкстры

Учитывая график и исходную вершину на графике, найдите самые короткие пути от источника ко всем вершинам на данном графике. Алгоритм Дейкстры используется для поиска таких путей на взвешенном графике, где все веса являются положительными. Dijkstra – это жадный алгоритм, который использует самое короткое дерево пути (SPT) с исходным узлом в качестве корня. SPT-это самобалансирующее двоичное дерево, но алгоритм может быть реализован с использованием кучи (или приоритетной очереди). Мы собираемся обсудить решение о куче, потому что его сложность времени o (| e |*log | V |). Идея состоит в том, чтобы работать с изображением списка смежности графика. Таким образом, узлы будут проходить во время O (| V |+| E |) с использованием BFS. Все вершины пересекаются с помощью BFS, и те, на которые кратчайшее расстояние еще не завершено, сохраняются в MIN-HEAP (приоритетная очередь). Min-HEAP создается, и каждый узел вталкивается в него вместе со значениями расстояния. Затем источник становится корнем кучи с расстоянием 0. Другие узлы будут иметь бесконечное назначение в виде расстояния. Хотя куча не пуста, мы извлекаем узел минимального значения расстояния x. Для каждой вершины Y, примыкающей к X, мы проверяем, находится ли Y в Min-Heap. В этом случае, если значение расстояния больше веса (x, y) плюс значение расстояния x, то мы обновляем значение расстояния y.

Алгоритм Беллмана-Форда

Как мы уже говорили, Dijkstra работает только на положительно взвешенных графиках. Беллман решает эту проблему. Учитывая взвешенный график, мы можем проверить, содержит ли он отрицательный цикл. Если нет, то мы также можем найти минимальные расстояния от нашего источника до других (возможных отрицательных весов). Bellman-Ford Suites хорошо для распределенных систем, хотя его сложность времени o (| V | E |). Мы инициализируем Dist [], как в Dijkstra. Для *| V | -1 раз, для каждого (x, y) Edge, if dist [y]> dist [x] + вес (x, y) , тогда мы обновляем Dist [y] с этим. Мы повторяем последний шаг, чтобы найти отрицательный цикл. Идея состоит в том, что последний шаг гарантирует минимальное расстояние, если нет отрицательного цикла. Если есть какой -либо узел, который имеет более короткое расстояние на текущем этапе, чем на последнем, то был обнаружен отрицательный цикл.

Полезные ссылки

14. Алгоритм Крускала

Ранее мы обсуждали о том, что такое минимальное дерево охватчиков. Есть два алгоритма, которые находят MST графика: PRIM (полезно для плотных графиков) и Kruskal (идеально подходит для большинства графиков). Теперь мы собираемся обсудить об алгоритме Крускала. Крускал разработал жадный алгоритм, чтобы найти MST. Это эффективно на редких графиках, потому что его сложность времени o (| e |*log | V |). Подход алгоритма является следующим: мы сортируем все края в растущем порядке их веса. Затем самый маленький край выбирается. Если он не образует цикл с текущим MST, мы включаем его. В противном случае, отбросьте это. Последний шаг повторяется до тех пор, пока в MST не появятся края | v | -1. Включение краев в MST осуществляется с использованием Dise-in-Set-Union, также ранее обсуждавшегося.

Полезные ссылки

15. Топологическая сортировка

Направленный ациклический график (DAG) – это просто направленный график, который не содержит циклов. Топологическая сортировка в DAG – это линейное упорядочение вершин, так что для каждой арки (x, y) , Узел X выходит перед узлом y. Очевидно, что первая вершина в топологической сортировке-это вершина с 0 в градусе (на нее нет арков). Другая специальная собственность заключается в том, что у DAG нет уникальной топологической сортировки. Реализация BFS следует за этой рутиной: узел с 0 в градусе найден и подтолкнул первую в сортировку. Эта вершина удаляется с графика. Поскольку новый график также является DAG, мы можем повторить процесс.

В любой момент во время DFS узел может быть в одной из этих трех категорий:

  • узлы, которые мы закончили посещать (выскочили из стека);
  • узлы, которые в настоящее время находятся в стеке;
  • узлы, которые еще не обнаружены.

Если во время DFS в DAG узел X имеет исходящий край у узла Y, то Y либо в первой, либо в третьей категории. Если y был в стеке, то (x, y) положит конец циклу, факту, который противоречит определению DAG. Это свойство фактически говорит нам, что вершина выскочила из стека после того, как все его исходящие соседи появляются. Таким образом, для топологического сортировки графика мы должны отслеживать перевернутый список заказа всплывающих вершин.

Полезные ссылки

Вау, вы добрались до конца статьи. Спасибо за внимание!:) Имейте забавное кодирование!

Оригинал: “https://dev.to/iuliagroza/complete-introduction-to-the-30-most-essential-data-structures-algorithms-43kd”