<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Алгоритми та Структури Даних</title>
<link>https://www.stefaniuk.website/tags/algorithms-and-data-structures/</link>
<description>Список нотаток про популярні та цікаві алгоритми і структури даних</description>
<author></author>
<language>uk</language>
<generator>Aegea 11.2 (v4116)</generator>

<itunes:subtitle>Список нотаток про популярні та цікаві алгоритми і структури даних</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Очередь с приоритетом</title>
<guid isPermaLink="false">271</guid>
<link>https://www.stefaniuk.website/all/ordered-queue-and-heap/</link>
<pubDate>Tue, 05 Jul 2022 16:29:29 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/ordered-queue-and-heap/</comments>
<description>
&lt;p&gt;Представим что нужно спроектировать список задач. У каждой задачи есть приоритет, это значит, что вначале должны быть самые приоритетные задачи. Сами задачи можно обрабатывать только по одной, мы не можем взять задачу с центра списка.&lt;/p&gt;
&lt;div style="max-width: 450px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-pic-1.png" width="628" height="240" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Такой список достаточно просто реализовать с помощью обычного массива. Задачи будем хранить в случайном порядке, а в момент получения текущей задачи будем искать самую приоритетную. В таком случае добавление новой задачи будет иметь сложность O(1), а вот получение O(n), потому что каждый раз нужно искать элемент с самым большим приоритетом.&lt;/p&gt;
&lt;p&gt;Второй вариант тоже использует обычный массив, только элементы в нем хранятся уже в отсортированном виде. В таком случае сложность меняется зеркально. Добавление нового элемента будет занимать O(n), а получение O(1).&lt;/p&gt;
&lt;p&gt;Оба варианта не совсем эффективны, особенно когда очень много данных. Здесь на помощь приходят очереди с приоритетом.&lt;/p&gt;
&lt;p&gt;Очереди с приоритетом строятся на базе такой структуры данных как куча. Поэтому важно рассмотреть ее устройство. Куч есть большое множество, но нас сейчас интересует только двоичная куча.&lt;/p&gt;
&lt;h2&gt;Двоичная куча&lt;/h2&gt;
&lt;p&gt;Двоичная куча представляет из себя двоичное дерево где каждый элемент имеет два дочерних элемента и хранит значение не меньшее чем в дочерних. Они также делятся на два типа: максимальная и минимальная. В первой хранятся элементы в порядке убывания, а во второй в порядке возрастания.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-max-min-2.png" width="1587" height="712" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Для хранения данных кучи удобно использовать обычные массивы. Такой подход занимает мало места и сам по себе достаточно элегантный. На как определить какой элемент массива на какой ссылается? Для этого есть две простые формулы.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2i + 1 — позволяет найти позицию левого дочернего элемента&lt;/li&gt;
&lt;li&gt;2i + 2 — позволяет найти позицию правого дочернего элемента&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-array-structure-3.png" width="1500" height="552" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Сортировка дерева&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-sorting-4.png" width="2104" height="695" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Когда добавляем или удаляем элемент из очереди, свойства кучи могут нарушиться. В таком случае нужно привести ее в порядок. Представим, что есть метод MakeHeap, который принимает на вход индекс элемента и он сортирует его поддерево.&lt;/p&gt;
&lt;p&gt;Берем элемент по текущему индексу и сравниваем его с дочерними элементами. Если какой-то дочерний элемент больше текущего, меняем его местами с текущим и спускаемся дальше, чтобы проверить поддерево. Таким образом, мы доходим до конца изначального поддерева сортируя все его поддеревья.&lt;/p&gt;
&lt;p&gt;Как это выглядит в коде:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;private void SortHeap(int i)
{
var size = values.Count;
var left = 2 * i + 1;
var right = 2 * i + 2;
var largest = i;

if (left &amp;lt; size &amp;amp;&amp;amp; values[left] &amp;gt; values[largest])
largest = left;

if (right &amp;lt; size &amp;amp;&amp;amp; values[right] &amp;gt; values[largest])
largest = right;

if (largest != i)
{
var temp = values[i];
values[i] = values[largest];
values[largest] = temp;
SortHeap(largest);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Делаем кучу из обычного массива&lt;/h2&gt;
&lt;p&gt;Мы умеем сортировать элементы в поддеревьях, теперь применим этот метод для того, чтобы с обычного массива сделать кучу. Так как потомки гарантированно есть у первых (n/2 — 1) элементов, то достаточно только для них вызвать метод SortHeap.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;private void MakeHeap()
{
for (int i = values.Count / 2 - 1; i &amp;gt;= 0; i--)
{
SortHeap(i);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Добавление нового элемента&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-insert-5.png" width="2225" height="650" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Новый элемент сначала добавляется в конец массива, после чего запускаем сортировку дерева. Алгоритм получается достаточно простым:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;if (values.Count == 0)
{
// Просто добавляем в элемент в массив, если он пустой
values.Add(value);
}
else
{
// Добавляем элемент в конец массива и запускаем сортировку дерева
values.Add(value);
for (int i = values.Count / 2 - 1; i &amp;gt;= 0; i--)
{
SortHeap(i);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Удаление элемента&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/heap-remove-5.png" width="2183" height="652" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Чтобы удалить элемент нужно поменять его с последним элементов в куче, потом удалить и запустить сортировку кучи. Так как мы делаем очередь, то будем всегда удалять первый (корневой) элемент кучи.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var size = values.Count;

var last = values.Count - 1;
var temp = values[0];
values[0] = values[last];
values[last] = temp;
values.RemoveAt(last);

for (int i = size / 2 - 1; i &amp;gt;= 0; i--)
SortHeap(i);

return temp;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Итого&lt;/h2&gt;
&lt;p&gt;Куча обладает необходимыми свойствами для создания очереди с приоритетом. Она хранит данные в отсортированном виде, работает быстрее чем обычный массив. Достаточно реализовать такие же методы как в обычной очереди и у нас получиться очередь с приоритетом:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enqueue — обычная вставка в кучу, ничего даже менять не нужно.&lt;/li&gt;
&lt;li&gt;Dequeue — обычное удаление элемента с кучи, только удаляем мы всегда элемент с 0 индексов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Посмотреть полную реализацию можно на &lt;a href="https://dotnetfiddle.net/Smd2bH"&gt;dotnetfiddle&lt;/a&gt;.&lt;br /&gt;
 &lt;/p&gt;
</description>
</item>

<item>
<title>Бинарный поиск и бинарное дерево поиска</title>
<guid isPermaLink="false">269</guid>
<link>https://www.stefaniuk.website/all/binary-search-and-binary-search-tree/</link>
<pubDate>Sat, 11 Jun 2022 14:00:56 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/binary-search-and-binary-search-tree/</comments>
<description>
&lt;p&gt;Бинарный поиск один из самых популярных и простых алгоритмов поиска. Он активно используется во многих системах, например в базах данных для быстрого поиска и индексирования.&lt;/p&gt;
&lt;p&gt;Первое что нужно знать — алгоритм работает только на отсортированных данных. Поэтому перед поиском нужно отсортировать массив.&lt;/p&gt;
&lt;p&gt;Начинается алгоритм с того что берем элемент из середины массива и сравниваем с искомым значением. Если искомый элемент больше среднего, тогда искомое значение лежит в правой части массива. Идем в правую часть массива, опять берем элемент из середины и сравниваем его с искомым. В этот раз искомый элемент меньше среднего, тогда он скорее всего находится в левой части этого под массива.&lt;/p&gt;
&lt;p&gt;Таким образом мы постоянно сравниваем искомое значение со значением из середины массива. После этого откидываем половину значений. Это здорово ускоряет поиск, потому что не нужно проверять все элементы массива. На каждом этапе отбрасывается половина элементов.&lt;/p&gt;
&lt;p&gt;В итоге всех операций наступает момент когда искать приходиться в массиве из одного элемента. В таком случае просто сравниваем с искомым, если равны, тогда мы нашли элемент, если не, тогда не нашли.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-1.png" width="524" height="215" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;А теперь живой пример. Есть отсортированный массив и нужно найти число 9. Берем элемент из середины — 8, сравниваем, 9 больше 8, значит нужно продолжить поиск в правой части.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-2.png" width="497" height="200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Снова берем элемент из середины — 11, 9 меньше 11, значит нужно искать в левой части нашего подмассива.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-3.png" width="740" height="200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Берем элемент из середины — 9, сравниваем, элементы равны, число 9 есть в массиве и храниться по индексу 4. Мы нашли нужный элемент всего за 3 шага.&lt;/p&gt;
&lt;p&gt;Если пробовать оценить скорость работы алгоритма, то получается что она равна O(log n). Для сравнения, обычный поиск перебором имеет скорость O(n), это значит что нужно перебрать все элементы массива. Что это значит на практике?&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-speed.png" width="968" height="170" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Посмотреть реализацию на C# можно на &lt;a href="https://dotnetfiddle.net/LjCl90"&gt;dotnetfiddle&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Как оптимизировать сортировку?&lt;/h2&gt;
&lt;p&gt;Массив всегда должен быть отсортированным чтобы поиск работал, а это значит что при каждом добавлении нового элемента нужно запускать сортировку. Возникает вопрос, а что если сразу вставлять новый элемент в правильное место? Тогда у нас всегда будет отсортированный массив и не нужно каждый раз прогонять сортировку.&lt;/p&gt;
&lt;p&gt;Так же подумали умные ребята и придумали бинарное дерево поиска. Это структура данных, которая основывается на работе бинарного поиска. По сути это реализация бинарного поиска в виде структуры данных.&lt;/p&gt;
&lt;p&gt;Структура дерева достаточно простая: каждый элемент может иметь только два дочерних элемента. Их еще часто называют левым и правым. В левом дочернем элементе храниться значение, меньше чем в родительском, а в правом — больше.&lt;/p&gt;
&lt;h2&gt;Как работает поиск в дереве?&lt;/h2&gt;
&lt;p&gt;Сравниваем искомое значение со значением в корневом элементе. Если оно больше, тогда мы идем к правому дочернему элементу и повторяем процедуру. Таким образом мы начинаем с самого верха и плавно опускаемся пока не найдем элемент с таким же значением.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-tree-1.png" width="399" height="324" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Вставка нового элемента работает так же как и поиск. Сравниваем новый элемент с текущим и опускаемся по дереву вниз пока не найдем подходящее место. В итоге при каждом добавлении у нас будет отсортированный набор данных.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-tree-2.png" width="468" height="410" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Но что будет если всегда добавлять элементы с все большим и большим значением? Наше дерево будет выглядеть вот так:&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/binary-search-tree-3.png" width="562" height="512" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;По сути для поиска значения на нужно обойти все элементы дерева, а это значит что скорость работы упадет до O(n). Получается что бинарное дерево поиска в среднем работает со скоростью O(log n), а в худшем O(n).&lt;/p&gt;
&lt;p&gt;Такие деревья еще называют не сбалансированными. Чтобы исправить ситуацию существуют специальные алгоритмы балансировки деревьев, которые приводят их в нормальный вид. Но рассматривать здесь я их не буду.&lt;/p&gt;
&lt;h2&gt;Итог&lt;/h2&gt;
&lt;p&gt;Бинарный поиск один из самых простых и элегантных алгоритмов поиска, который привел к созданию новой структуры данных — бинарного дерева поиска. Данная структура является очень популярной и используется по многих системах. Особенно активно в базах данных для создания индексов и оптимизации выборки данных.&lt;/p&gt;
&lt;p&gt;Если тема интересная, то можно копнуть дальше и разобраться с:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Красно черными деревьями&lt;/li&gt;
&lt;li&gt;Кучами&lt;/li&gt;
&lt;li&gt;B-деревьями&lt;/li&gt;
&lt;li&gt;и т. д.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Бонус&lt;/h2&gt;
&lt;p&gt;На сайте &lt;a href="https://idea-instructions.com"&gt;&lt;a href="https://idea-instructions.com"&gt;https://idea-instructions.com&lt;/a&gt;&lt;/a&gt; есть объяснения разных алгоритмов в стиле инструкций с икеи. Например &lt;a href="https://idea-instructions.com/avl-tree/"&gt;AVL дерево&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Коллекции в C#</title>
<guid isPermaLink="false">252</guid>
<link>https://www.stefaniuk.website/all/collection-in-csharp/</link>
<pubDate>Wed, 21 Apr 2021 11:10:51 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/collection-in-csharp/</comments>
<description>
&lt;p&gt;В C# для хранения набора однотипных данных можно использовать массивы. Но с ними не всегда удобно работать потому, что они имеют фиксированный размер и часто бывает сложно угадать, какого размера нужен массив.&lt;/p&gt;
&lt;p&gt;Для решения этих задач в C# есть коллекции. Они позволяют динамически изменять свой размер. Также они удобны тем что некоторые из них представляют из себя готовые реализации стандартных структур данных, таких как список, хеш таблица, стек, очередь.&lt;/p&gt;
&lt;p&gt;Все коллекции лежат в нескольких пространствах имен:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;System.Collections — простые необобщенные коллекции.&lt;/li&gt;
&lt;li&gt;System.Collections.Generic — обобщенные коллекции.&lt;/li&gt;
&lt;li&gt;System.Collections.Specialized — специальные коллекции.&lt;/li&gt;
&lt;li&gt;System.Collections.Concurrent — коллекции для работы в многопоточной среде.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Устройство коллекций&lt;/h2&gt;
&lt;p&gt;Все коллекции, так или иначе, реализую интерфейс ICollection, некоторые реализуют интерфейсы IList и IDictionary (которые внутри наследуют ICollection). Этот интерфейс предоставляет минимальный набор методов, которые позволяют реализовать коллекцию.&lt;/p&gt;
&lt;p&gt;В свою очередь, ICollection расширяет интерфейс IEnumerable. Он предоставляет нумератор, который позволяет обходить коллекции элемент за элементом. Именно этот интерфейс позволяет использовать коллекции в цикле foreach.&lt;/p&gt;
&lt;div style="max-width:440px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/collections-interfaces.png" width="1496" height="900" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Вместительность коллекций&lt;/h2&gt;
&lt;p&gt;Одна из ключевых особенностей коллекций это изменяемый размер. Когда создается экземпляр коллекции она внутри себя инициализирует какую-то структуру данных, зачастую это массив. По умолчанию этот массив имеет определённую вместительность, которую можно просмотреть с помощью свойства Capacity.&lt;/p&gt;
&lt;div style="max-width:440px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/collections-capacity.png" width="900" height="607" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;После активного наполнения коллекции наступает момент, когда внутренний массив заполнен и мы не можем добавить новый элемент. В таком случае коллекция создаёт новый массив с большей вместительностью (обычно в два раза больше) и копирует туда данные со старого массива.&lt;/p&gt;
&lt;div style="max-width:440px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/collections-capacity-copy.png" width="900" height="554" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Поэтому коллекции, которые основаны на массивах имеют сложность вставки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O(1) — когда вместительности достаточно.&lt;/li&gt;
&lt;li&gt;O(n) — когда вместительности недостаточно и нужно копировать данные в массив побольше.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Чтобы избежать уменьшения производительности нужно при создании коллекции указать необходимую нам вместительности. Это позволит уменьшить количество копирований.&lt;/p&gt;
&lt;h2&gt;Сравнивание и сортировка элементов коллекций&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Сравнение&lt;/b&gt;&lt;br /&gt;
Такие методы как Contains, IndexOf, LastIndexOf, and Remove используют сравнение элементов для свое работы. Если коллекция является обобщенной, то используются два механизма сравнения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Если тип реализует интерфейс IEquatable тогда механизм сравнения использует метод Equals этого интерфейса.&lt;/li&gt;
&lt;li&gt;Если тип не реализует интерфейс IEquatable тогда для сравнения используется Object.Equals&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Некоторые коллекции имеют конструктор, который принимает имплементацию IEqualityComparer&lt;T&gt; который используется для сравнения.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Сортировка&lt;/b&gt;&lt;br /&gt;
Сортировка работает похожим образом, как и сравнение, но делиться на два вида: явную сортировку и сортировка по умолчанию.&lt;/p&gt;
&lt;p&gt;Сортировка по умолчанию подразумевает, что типы хранящиеся в коллекции реализуют интерфейс IComparable, чьи методы под капотом используют коллекции для сравнения.&lt;/p&gt;
&lt;p&gt;Явная сортировка подразумевает, что наши элементы не реализуют интерфейс IComparable, поэтому в качестве параметра метода сортировки нужно передать объект, который реализует интерфейс IComparer.&lt;/p&gt;
&lt;p&gt;Если тип не реализует интерфейс IComparable и мы не передали явно тип, который реализует IComparer, то при вызове метода сортировки вылетит исключение.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;System.InvalidOperationException: Failed to compare two elements in the array.
System.ArgumentException: At least one object must implement IComparable.&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Алгоритмическая сложность коллекций&lt;/h2&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/collection-compexity-4.png" width="1056" height="414" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Источник: &lt;a href="http://c-sharp-snippets.blogspot.com/2010/03/runtime-complexity-of-net-generic.html"&gt;Runtime Complexity of .NET Generic Collection&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Список List&lt;T&gt;&lt;/h2&gt;
&lt;p&gt;Класс List&lt;T&gt; представляет из себя простейший список однотипных элементов, которые можно получить по индексу. Предоставляет методы для поиска, сортировки и изменения списка.&lt;/p&gt;
&lt;!--
&lt;div style="width:400px;"&gt;
collections-list.png
&lt;/div&gt;
--&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var linkedList = new List&amp;lt;string&amp;gt;();
linkedList.Add(&amp;quot;A&amp;quot;);
linkedList.Add(&amp;quot;B&amp;quot;);
linkedList.Add(&amp;quot;C&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для своей работы списки используют обычные массивы. Это значит что могут быть проблемы с производительностью из-за частого создания нового массива. Также у списков есть еще два нюанса:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Вставка элемента в середину списка приведет к созданию нового массива и копирование данных, что негативно влияет на производительность.&lt;/li&gt;
&lt;li&gt;Списки не могут хранить экстремально огромное количество элементов из-за фрагментации адресного пространства.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если эти проблемы существенны для вас, то стоит присмотреться к LinkedList&lt;T&gt; или ImmutableList&lt;T&gt;&lt;/p&gt;
&lt;h2&gt;Связанный список LinkedList&lt;T&gt;&lt;/h2&gt;
&lt;p&gt;Класс LinkedList&lt;T&gt; реализует простой двухсвязный список, каждый элемент которого имеет ссылка на предыдущий и следующий элемент.&lt;/p&gt;
&lt;p&gt;Каждый элемент списка оборачивается в специальный класс LinkedListNode&lt;T&gt;, который имеет ссылку на следующий элемент (Next), на предыдущий элемент (Previous) и само значение (Value).&lt;/p&gt;
&lt;!--
&lt;div style="width:400px;"&gt;
collections-linked-list.png
&lt;/div&gt;
--&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var linkedList = new LinkedList&amp;lt;string&amp;gt;();
linkedList.AddFirst(&amp;quot;A&amp;quot;);
linkedList.AddLast(&amp;quot;B&amp;quot;);
linkedList.AddLast(&amp;quot;C&amp;quot;);

Console.WriteLine(linkedList.First.Previous == null); // True
Console.WriteLine(linkedList.Last.Next == null);   // True&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Связанный список позволяет вставлять и удалять элементы со сложностью O (1). Также мы можем удалить элемент и заново вставить в тот же или другой список без дополнительного выделения памяти.&lt;/p&gt;
&lt;h2&gt;Словарь Dictionary&lt;TKey, TValue&gt;&lt;/h2&gt;
&lt;p&gt;Словари хранят данные в виде ключ-значение. Каждый элемент словаря представляет из себя объект структуры KeyValuePair&lt;TKey, TValue&gt;.&lt;/p&gt;
&lt;p&gt;В качестве ключа можно использовать любой объект. Ключи должны быть уникальными в рамках коллекции.&lt;/p&gt;
&lt;!--
&lt;div style="width:400px;"&gt;
collectios-dict.png
&lt;/div&gt;
--&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var linkedList = new Dictionary&amp;lt;string, string&amp;gt;();
linkedList.Add(&amp;quot;key1&amp;quot;, &amp;quot;A&amp;quot;);
linkedList.Add(&amp;quot;key2&amp;quot;, &amp;quot;B&amp;quot;);
linkedList.Add(&amp;quot;key3&amp;quot;, &amp;quot;C&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри словари построены на базе хеш-таблицы, что позволяет очень быстро вставлять элементы и получать по ключу (сложность O (1)). Сами же хеш-таблицы, в свою очередь, реализованы с помощью массивов.&lt;/p&gt;
&lt;p&gt;Для адресации значений внутри коллекции используются хеш коды ключей. Это значит что объект ключа не должен изменяться, потому что это приведет к изменению хеш кода, что в свою очередь приведет к потере данных.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/collections/generic/dictionary.cs"&gt;Исходники Dictionary&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Стек Stack&lt;T&gt; и Очередь Queue&lt;T&gt;&lt;/h2&gt;
&lt;p&gt;Очереди и стеки полезны, когда нужно временно хранить какие-то элементы, то есть удалять элемент после его извлечения. Также они позволяют определить строгую очередность записи и извлечения элементов.&lt;/p&gt;
&lt;p&gt;Стеки Stack&lt;T&gt; — реализуют подход LIFO (last in — first out).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var stack = new Stack&amp;lt;int&amp;gt;();
stack.Push(1); // stack = [1]
stack.Push(2); // stack = [1,2]
var item = stack.Pop(); // stack = [1], item = 2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Очереди Queue&lt;T&gt; — реализуют подход (first in — first out).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var queue = new Queue&amp;lt;int&amp;gt;();
queue.Enqueue(1); // queue = [1]
queue.Enqueue(2); // queue = [1,2]
item = queue.Dequeue(); // queue = [2], item = 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри они реализованы с помощью обычных массивов.&lt;/p&gt;
&lt;h2&gt;Множества HashSet&lt;T&gt; и SortedSet&lt;T&gt;&lt;/h2&gt;
&lt;p&gt;Эти классы реализуют математические множества. По своей природе множество — набор уникальных элементов с общими характеристиками, в нашем случае одного типа.&lt;/p&gt;
&lt;p&gt;Также множества отличаются от обычных списков тем что они предоставляют набор методов, которые реализуют операции с &lt;a href="https://ru.wikipedia.org/wiki/Теория_множеств"&gt;теории множеств&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Внутренняя реализация этих классов отличается:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HashSet&lt;T&gt; — множество, построенное на базе хеш-таблицы.&lt;/li&gt;
&lt;li&gt;SortedSet&lt;T&gt; — отсортированное множество, построенное на базе красно-черного дерева.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ISet&amp;lt;int&amp;gt; set = new HashSet&amp;lt;int&amp;gt; { 1, 2, 3, 4, 5 };
 
set.UnionWith(new[] { 5, 6 });              // set = { 1, 2, 3, 4, 5, 6 }
set.IntersectWith(new[] { 3, 4, 5, 6, 7 }); // set = { 3, 4, 5, 6 }
set.ExceptWith(new[] { 6, 7 });             // set = { 3, 4, 5 }
set.SymmetricExceptWith(new[] { 4, 5, 6 }); // set = { 3, 6 }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Можно заметить что LINQ предоставляет несколько похожих операций (Distinct, Union, Intersect, Except), которые можно выполнить с любой коллекцией. Но HastSet предоставляет намного больший набор операций с множествами.&lt;/p&gt;
&lt;p&gt;Основная разница в том что методы множеств изменяют текущую коллекцию, в то время как LINQ методы всегда создают новый экземпляр коллекции.&lt;/p&gt;
&lt;h2&gt;KeyedCollection&lt;TKey,TItem&gt;&lt;/h2&gt;
&lt;p&gt;KeyedCollection это абстрактный класс, который позволяет построить собственную коллекцию.&lt;/p&gt;
&lt;p&gt;Эта коллекция является гибридом между списками (IList&lt;T&gt;) и словарями (IDictionary&lt;T&gt;). От списков ей досталась возможность получать элементы по индексу, а от словарей возможность получать элемент по ключу.&lt;/p&gt;
&lt;p&gt;В отличие от словарей, элемент KeyedCollection коллекции не является парой ключ-значение, вместо этого весь элемент является значением, а в качестве ключа используется его поле, свойство или любое другое значение. Для получения ключа используется абстрактный метод, который является обязательным для реализации. GetKeyForItem&lt;/p&gt;
&lt;!--
&lt;div style="width:400px;"&gt;
collections-keyed.png
&lt;/div&gt;
--&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var keyedCollection = new UserCollection();
keyedCollection.Add(new User {
Id = 1,
Name = &amp;quot;A&amp;quot;
});
keyedCollection.Add(new User {
Id = 2,
Name = &amp;quot;B&amp;quot;
});

Console.WriteLine(keyedCollection[2].Name); // B

public class UserCollection: KeyedCollection&amp;lt;int, User&amp;gt;
{
protected override int GetKeyForItem(User user) =&amp;gt; user.Id;
}

public class User
{
public int Id {get;set;}
public string Name {get;set;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри KeyedCollection построен на базе двух структур данных. Для быстрого получения данных по ключу используется словарь, а для получение элемента по индексу используется массив.&lt;/p&gt;
&lt;h2&gt;NameValueCollection&lt;/h2&gt;
&lt;p&gt;Представляет из себя коллекцию, которая похожа на словарь но в качестве ключей и значений используются строки. Элементы можно получить как по индексу так и по ключу.&lt;/p&gt;
&lt;p&gt;Особенной эту коллекцию делает то что однин ключ может содержать несколько эллементов.&lt;/p&gt;
&lt;!--
&lt;div style="width:400px;"&gt;
collections-named.png
&lt;/div&gt;
--&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var namedCollection = new NameValueCollection();
namedCollection.Add(&amp;quot;key1&amp;quot;, &amp;quot;value1&amp;quot;);
namedCollection.Add(&amp;quot;key2&amp;quot;, &amp;quot;value2&amp;quot;);
namedCollection.Add(&amp;quot;key1&amp;quot;, &amp;quot;value3&amp;quot;);

Console.WriteLine(namedCollection.Count);   // 2
Console.WriteLine(namedCollection[&amp;quot;key1&amp;quot;]); // value1,value3&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Иммутабельные коллекции&lt;/h2&gt;
&lt;p&gt;Иммутабельные коллекции не входят в стандартную библиотеку классов (BCL). Для их использования нужно установить System.Collections.Immutable NuGet пакет.&lt;/p&gt;
&lt;p&gt;Они позволяют безопасно работать в многопоточной среде. Вместо того чтобы использовать блокировки синхронизации, как это делают многопоточные коллекции, неизменяемые коллекции не могут быть изменены после создания. Это автоматически делает их безопасными для использования в многопоточных сценариях, так как нет возможности другому потоку изменить коллекцию.&lt;/p&gt;
&lt;p&gt;Сами же коллекции можно поделить на несколько видов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mutable — обычные коллекции которые поддерживают изменения.&lt;/li&gt;
&lt;li&gt;Immutable — коллекции, которые полностью запрещают изменения. Хотя на самом деле любое изменение иммутабельной коллекции приводит к созданию новой.&lt;/li&gt;
&lt;li&gt;ReadOnly — обертки над стандартными коллекциями, которые не дают поменять данные. Из-за того что это всего лишь обертка мы можем поменять данные в оригинальной коллекции и ead only коллекция подтянет изменения.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Детальнее можно ознакомиться в статье: &lt;a href="https://devblogs.microsoft.com/premier-developer/read-only-frozen-and-immutable-collections/"&gt;Read only, frozen, and immutable collections&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Иммутабельные стеки ImmutableStack&lt;T&gt; и очереди ImmutableQueue&lt;T&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Ничем не отличаются от обычных стеков и очередей, кроме того, что являются не изменяемыми.&lt;/p&gt;
&lt;p&gt;Для реализации иммутабельных стеков/очередей массивы не подойдут. Причина заключается в том, что на каждое изменение придётся делать полную копию массива что очень неэффективно.&lt;/p&gt;
&lt;p&gt;Поэтому для работы иммутабельных стеков/очередей используется связанный список. При изменении коллекции нужно всего лишь создать новый элемент, который ссылается на предыдущее значение. В итоге не происходит никакого копирования и экономится память.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Иммутабельные списки ImmutableList&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Под капотом используют сбалансированное бинарное дерево вместо массива или связанного списка.&lt;/p&gt;
&lt;p&gt;Массивы не подходят из-за накладных расходов на их использование. Связанный список тоже не подойдет, потому что ImmutableList поддерживает обращение по индексу из-за чего нужно долго перебирать связанные элементы. Поэтому для нормальной работы иммутабельных списков используют сбалансированное бинарное дерево.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Иммутабельные массивы ImmutableArray&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;По сути, это небольшая прослойка над обычными массивами и все. Любые мутабельные операции приводят к копированию массива. Из-за этого скорость добавления элементов равна O (n), но в то же время получение элемента по индексу занимает O (1).&lt;/p&gt;
&lt;p&gt;Итерация по массиву работает в несколько раз быстрее чем у других неизменяемых коллекциях.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Иммутабельные словари ImmutableDictionary&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Неизменяемые словари внутри работают на базе сбалансированного дерева, но с одной особенностью. Каждый элемент внутри коллекции представлен в виде отдельного дерева (ImmutableList&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;). Так что по своей сути иммутабельные словари — это деревья деревьев.&lt;/p&gt;
&lt;p&gt;Из-за своей особенности иммутабельные словари потребляют очень много памяти и долго работают. Поэтому стоит аккуратно их использовать.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Особенности использования&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Как было выше сказано все операции изменения коллекций приводят к созданию новой коллекции. Нужно помнить, что необходимо использовать новый экземпляр вместо старого.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var immutableList = new[] { 1, 2, 3 }.ToImmutableList();
immutableList = immutableList.Add(4);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;По идее чтобы было проще, мы можем объединить все изменения в цепочку:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;immutableList = immutableList
    .Add(5)
    .Add(6)
    .Add(7);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Однако такого способа стоит избегать, потому что каждый вызов метода создает новый экземпляр коллекции. Хотя неизменяемые коллекции реализованы таким образом, чтобы повторно использовать как можно большую часть оригинальной коллекции при создании нового экземпляра, некоторые выделения памяти все же необходимы. Это означает больше работы для сборщика мусора.&lt;/p&gt;
&lt;p&gt;Чтобы минимизировать проблему стоит использовать методы, которые могут выполнить нужные изменения за один вызов. Например:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;immutableList = immutableList.AddRange(new[] { 5, 6, 7 });&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Но такие методы позволяют сделать только один вид изменения. Например у нас нет единого метода, который позволяет добавить и удалить одну запись:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;immutableList = immutableList
    .Add(6)
    .Remove(2);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для решения этой проблемы иммутабельные коллекции предоставляют билдеры (builders).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var builder = immutableList.ToBuilder();
builder.Add(6);
builder.Remove(2);
immutableList = builder.ToImmutable();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри себя билдеры используют соответствующую мутабельную коллекцию, что позволяет выполнить все операции над одним экземпляром коллекции. Только после вызова метода ToImmutable экземпляр снова будет неизменяемым. Таким образом можно сократить объем работы уборщика мусора.&lt;/p&gt;
&lt;h2&gt;Источники и доп. материалы&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dotnetcurry.com/csharp/1466/csharp-dotnet-collection-class"&gt;How to Choose the Right .NET Collection Class?&lt;/a&gt; Отличная статья про то какую коллекцию выбрать в .NET. И вообще хорошо хоть и поверхностно описаны конкурентные и неизменяемые коллекции.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://staticvoidmain.cognitioab.se/2013/11/28/net-collections-comparing-performance-and-memory-usage/"&gt;.NET Collections: comparing performance and memory usage&lt;/a&gt;. Сравнивались коллекции-словари и среди них лучше всего отработал Dictionary, SortedList в свою очередь в среднем потреблял в два раза меньше памяти чем обычный словарь. Хуже всего себя показал отсортированный словарь SortedDictionary.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/collections/"&gt;Collections and Data Structures&lt;/a&gt;. Отличное описание коллекций на MSDN.&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Небольшая заметка об индексах в ms sql</title>
<guid isPermaLink="false">140</guid>
<link>https://www.stefaniuk.website/all/indexes-in-ms-sql/</link>
<pubDate>Sat, 21 Sep 2019 06:20:03 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/indexes-in-ms-sql/</comments>
<description>
&lt;p&gt;Индексы это важная часть большинства серверов баз данных. Они позволяют ускорить доступ к данным. Суть индексов хорошо описывает следующий пример: оглавление книги, оно позволяет найти нужную нам страницу вместо перебора всех страниц. Но у индексов есть недостатки: они заполняют дополнительное место (бывают случаи что индексы занимают в 3 раза больше места чем сами данные), также замедляются операции изменения данных, так как теперь нам надо менять еще и индексы.&lt;/p&gt;
&lt;h2&gt;Что из себя представляет индекс?&lt;/h2&gt;
&lt;p&gt;Внутри сервера они представлены в виде B-tree, где B означает сбалансированное, а не бинарное дерево.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/b-tree-index-view.png" width="572" height="437" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Например мы хотим найти запись с Id = 2581.&lt;br /&gt;
Начиная с корня, выполняется поиск наименьшего значения ключа, большего или равного требуемому. Так мы найдем узел 18316, потом спустимся в узел 9031 и там мы увидим что есть прямая ссылка на лежащие данные по ключу 2581, после чего осуществляем вычитку данных.&lt;/p&gt;
&lt;h2&gt;Кластерный индекс&lt;/h2&gt;
&lt;p&gt;Кластерный индекс позволяет определить порядок данных в таблице. Таким образом индекс содержит не только указатели на строки но и сами данных. Из этого выходит что у таблицы может быть только один кластерный индекс, так как нельзя физически упорядочить элементы более чем одним способом. Индекс создается автоматически для каждой таблицы, в которой определен первичный ключ.&lt;/p&gt;
&lt;h2&gt;Не кластерный индекс&lt;/h2&gt;
&lt;p&gt;Структура такая же как и кластерного индекса, но с двумя отличиями:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Не изменяет физическое упорядочивание данных.&lt;/li&gt;
&lt;li&gt;Страницы индекса состоят из ключа индекса и ссылки на строку.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SQL Server использует индекс для нахождения записей, совпадающих с условиями запроса.&lt;/p&gt;
&lt;h2&gt;Составной ключ в индексе&lt;/h2&gt;
&lt;p&gt;SQL Server позволяет создавать индексы по нескольким колонкам. Но в таком случае у нас появляется ограничение. Длинна составного ключа не должна быть больше 900 байт. Но бывают исключения, например у нас есть две колонки, каждая из которых длинной в 500 байт. Сервер создаст индекс, в случае если нет данных, которые будут превышать длину в 900 байт.&lt;/p&gt;
&lt;p&gt;Также стоит помнить что индексы типа (Col1, Col2) и (Col2, Col1) разные.&lt;/p&gt;
&lt;h2&gt;Уникальные индексы&lt;/h2&gt;
&lt;p&gt;Такие индексы создаются для реализации целостности данных. Таким образом сервер гарантирует уникальные значение для указанной колонки или составного ключа.&lt;/p&gt;
&lt;h2&gt;Статьи, которые советую почитать чтобы глубже разобраться в теме&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;a href="https://professorweb.ru/my/sql-server/2012/level3/3_5.php"&gt;Очень хорошая статья о всех типах индексов, когда их стоит создавать и как использовать&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sql.ru/articles/mssql/03013101indexes.shtml"&gt;Индексы. Теоретические основы.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>GIN индекс</title>
<guid isPermaLink="false">138</guid>
<link>https://www.stefaniuk.website/all/gin-indeks/</link>
<pubDate>Mon, 09 Sep 2019 05:34:33 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/gin-indeks/</comments>
<description>
&lt;p&gt;GIN (Generalized Inverted Index) — инвертированный индекс, который позволяет реализовать что-то на подобии полнотекстового поиска. Активно используется в PostgreSQL.&lt;/p&gt;
&lt;p&gt;Механизм работы индекса достаточно прост. Берем текст, разбиваем его на слова. Дальше эти слова добавляются в хеш-таблицу в качестве ключей, а в качестве значений массив с айдишниками записей, в которых встречается это слово.&lt;/p&gt;
&lt;p&gt;Чтобы найти записи достаточно разбить поисковую строку на слова. Дальше для этих слов достать айдишники записей из хеш-таблицы и объединить их. В итоге получаем список найденных записей.&lt;/p&gt;
&lt;p&gt;На в реальных системах этот индекс работает немного сложнее. В индексе хранятся не сами слова, а их лексемы. Они определяют основу слова, исключают разные языковые вариации и позволяют более широко искать слова в тексте.&lt;/p&gt;
&lt;p&gt;Для создания лексем обычно есть словарь, который на вход принимает слово а результатом является лексема.&lt;/p&gt;
&lt;p&gt;Вообще GIN индекс похожий на алфавитный указатель в конце книги, где для каждого термина приведен список страниц в которых этот термин упоминается.&lt;/p&gt;
&lt;div style="max-width: 600px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/gin-index-view.png" width="2150" height="1192" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ниже небольшая реализация самого простого индекса на C#:&lt;/p&gt;
&lt;script src="https://gist.github.com/bohdanstefaniuk/4e6a75fd83e17b61f2d79bd48a446a3d.js"&gt;&lt;/script&gt;
</description>
</item>

<item>
<title>Алгоритм поиска Ли</title>
<guid isPermaLink="false">133</guid>
<link>https://www.stefaniuk.website/all/algorithm-li-in-graph/</link>
<pubDate>Fri, 06 Sep 2019 03:32:10 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/algorithm-li-in-graph/</comments>
<description>
&lt;p&gt;Алгоритм поиска пути в планарном графе. Зачастую используется в схемотехнике и в играх (стратегиях) для поиска кратчайшего пути.&lt;/p&gt;
&lt;p&gt;Алгоритм состоит из 3 шагов:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Инициализация&lt;/li&gt;
&lt;li&gt;Распространение волны&lt;/li&gt;
&lt;li&gt;Восстановление пути&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Также есть 2 способа поиска пути: ортогональный и ортогонально-диагональный. Ниже на скриншотах можно увидеть работу этих двух способов.&lt;/p&gt;
&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/ortogonal-li-demo.png" width="450" height="573" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Ортогональный поиск.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://www.stefaniuk.website/pictures/orto-diag-li-demo.png" width="450" height="573" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Ортогонально-диагональный поиск.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Псевдокод&lt;/h2&gt;
&lt;p&gt;Взято из &lt;a href="https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B8"&gt;Википедии&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Инициализация&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Пометить стартовую ячейку 
d := 0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Распространение волны&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ЦИКЛ
  ДЛЯ каждой ячейки loc, помеченной числом d
    пометить все соседние свободные непомеченные ячейки числом d + 1
  КЦ
  d := d + 1
ПОКА (финишная ячейка не помечена) И (есть возможность распространения волны)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Восстановление пути&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ЕСЛИ финишная ячейка помечена
ТО
  перейти в финишную ячейку
  ЦИКЛ
    выбрать среди соседних ячейку, помеченную числом на 1 меньше числа в текущей ячейке
    перейти в выбранную ячейку и добавить её к пути
  ПОКА текущая ячейка — не стартовая
  ВОЗВРАТ путь найден
ИНАЧЕ
  ВОЗВРАТ путь не найден&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://github.com/teamkiller7112/algoritms-playgroud/tree/master/Graph.Li_Agorithm"&gt;Пример кода&lt;/a&gt;, который реализует алгоритм и выводит на экран путь (C#).&lt;/p&gt;
&lt;style&gt;
h2 {font-size: 20px !important;}
.category-block { width: 48%;  display: inline-block; vertical-align: top; margin-right: 1%; margin-bottom: 20px;}
@media (max-width: 900px) {
.category-block { width: 100%; margin-right: unset;}
}
&lt;/style&gt;
</description>
</item>

<item>
<title>Хеш-таблица</title>
<guid isPermaLink="false">107</guid>
<link>https://www.stefaniuk.website/all/hash-table/</link>
<pubDate>Wed, 21 Aug 2019 03:51:53 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/hash-table/</comments>
<description>
&lt;p&gt;У хеш таблицы есть две особенности, которые делают ее особенной структурой данных: возможность хранить данные в формате ключ-значение и быстродействие, в среднем все операции занимают константное время O(1).&lt;/p&gt;
&lt;p&gt;Большинство хешей построены на базе массивов и специальной хеш-функции. Массивы позволяю почти моментально получать данные по индексу, а хеш-функция позволяет вычислить индекс в этом массиве на основании ключа.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Хеш-функция и коллизии&lt;/b&gt;&lt;br /&gt;
Хорошая хеш-функция должна давать как можно меньше коллизий, равномерно распределять ключи по хешу и должна быть последовательной. То есть для одинакового ключа должен быть одинаковый хеш.&lt;/p&gt;
&lt;p&gt;Коллизии возникают когда для разных ключей генерируется одинаковый хеш. Такая ситуация является вполне нормальной, потому-что невозможно создать идеальную хеш функцию.&lt;/p&gt;
&lt;p&gt;Самый простой и популярный способ работы с коллизиями является использование в качестве значения в массиве связанные список. В таком случае если элементы будут иметь одинаковый хеш, то они просто складываются в этот список.&lt;/p&gt;
&lt;p&gt;Потом когда нам нужно получить значение по ключу, то сначала мы вычисляем хеш и находим нужную ячейку в массиве. Потом мы проходимся по связанному списку и находим значение для нужного нам ключа. Но в случае коллизий скорость работы хеша может упасть до O(n). Потому что поиск элемента в массиве занимает O(1), но вот поиск по связанному списку — O(n).&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Коэффициент заполнения&lt;/b&gt;&lt;br /&gt;
Показывает насколько заполнена хеш-таблица, он используется для того чтобы знать когда увеличивать внутренний массив. Считается как отношение между количеством элементов в таблице и общей вместительностью. Оптимальным считается значение 0.7, если оно больше, то нужно увеличивать внутренний массив.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Когда использовать?&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Для хранения данных в виде ключ-значения.&lt;/li&gt;
&lt;li&gt;Для исключения дубликатов. С помощью хеш-таблицы можно очень эффективно находить дубликаты.&lt;/li&gt;
&lt;li&gt;Для реализации разных словарей и кэша.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Простая реализация: &lt;a href="https://stackoverflow.com/questions/625947/what-is-an-example-of-a-hashtable-implementation-in-c"&gt;What is an example of a Hashtable implementation in C#?&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Алгоритмы сортировки</title>
<guid isPermaLink="false">100</guid>
<link>https://www.stefaniuk.website/all/sorting-algorithms/</link>
<pubDate>Wed, 31 Jul 2019 16:05:53 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/sorting-algorithms/</comments>
<description>
&lt;h2&gt;Классификация&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;По степени роста сложности&lt;/li&gt;
&lt;li&gt;По использованию дополнительной памяти&lt;/li&gt;
&lt;li&gt;Рекурсивный или нет&lt;/li&gt;
&lt;li&gt;По устойчивости&lt;/li&gt;
&lt;li&gt;По методу сортировки: вставка, слияние, обмен&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Распространенные алгоритмы&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Сортировка пузырьком&lt;/li&gt;
&lt;li&gt;Сортировка вставками&lt;/li&gt;
&lt;li&gt;Сортировка выбором&lt;/li&gt;
&lt;li&gt;Сортировка слиянием&lt;/li&gt;
&lt;li&gt;Быстрая сортировка&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ссылка на сайт с гифками, которые демонстрируют работу алгоритмов: &lt;a href="https://www.toptal.com/developers/sorting-algorithms"&gt;https://www.toptal.com/developers/sorting-algorithms&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Сортировка пузырьком&lt;/h2&gt;
&lt;p&gt;Самая стандартная и простая сортировка. Выполняет проходы по массиву и перемещает наибольший элемент в конец массива.&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;/td&gt;
&lt;td&gt;Лучший вариант&lt;/td&gt;
&lt;td&gt;Средний вариант&lt;/td&gt;
&lt;td&gt;Худший вариант&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Степень сложности&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Рост памяти&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public void Sort(int[] inputArray)
{
for (int i = 0; i &amp;lt; inputArray.Length; i++) {
for (int j = 1; j &amp;lt; inputArray.Length; j++) {
if (inputArray[j-1] &amp;gt; inputArray[j]) {
Swap(inputArray, j-1, j);
}
}
}
}

public void Swap(int[] inputArray, int indexLeft, int indexRight)
{
if (indexLeft == indexRight) return;
var temp = inputArray[indexLeft];
inputArray[indexLeft] = inputArray[indexRight];
inputArray[indexRight] = temp;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Сортировка вставками&lt;/b&gt;&lt;br /&gt;
Также достаточно простой алгоритм, на каждом этапе которого выбирается один элемент массива, для которого осуществляется поиск позиции для вставки.&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;/td&gt;
&lt;td&gt;Лучший вариант&lt;/td&gt;
&lt;td&gt;Средний вариант&lt;/td&gt;
&lt;td&gt;Худший вариант&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Степень сложности&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Рост памяти&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public static void Sort(int[] inputArray)
{
int currentElementIndex= 1;
while (currentElementIndex &amp;lt; inputArray.Length)
{
if (inputArray[currentElementIndex] &amp;lt; inputArray[currentElementIndex - 1]) {
var newIndex = FindIndex(inputArray, inputArray[currentElementIndex]);
Insert(inputArray, newIndex, currentElementIndex);
}
currentElementIndex++;
}
}

public static int FindIndex(int[] inputArray, int element)
{
for (int i = 0; i &amp;lt; inputArray.Length; i++)
{
if (inputArray[i] &amp;gt; element) {
return i;
}
}
return 0;
}

public static void Insert(int[] inputArray, int indexLeft, int indexRight)
{
if (indexLeft == indexRight) return;
var temp = inputArray[indexLeft];
inputArray[indexLeft] = inputArray[indexRight];
for (int current = indexRight; current &amp;gt; indexLeft; current--) {
inputArray[current] = inputArray[current-1];
}
inputArray[indexLeft + 1] = temp;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Сортировка выбором&lt;/b&gt;&lt;br /&gt;
На каждом своем шаге отыскивает наименьший элемент в неотсортированной части массива и устанавливает его в соответствующую позицию массива.&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;/td&gt;
&lt;td&gt;Лучший вариант&lt;/td&gt;
&lt;td&gt;Средний вариант&lt;/td&gt;
&lt;td&gt;Худший вариант&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Степень сложности&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Рост памяти&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public static void Sort(int[] input) {
for (int i = 0; i &amp;lt; input.Length; i++) {
var smallestElementIndex = FindIndexOfSmallestElement(input, i);
Swap(input, smallestElementIndex, i);
}
}

public static int FindIndexOfSmallestElement(int[] input, int startFrom = 0) {
var smallestElementValue = input[startFrom];
var smallestElementIndex = startFrom;
for (int i = startFrom + 1; i &amp;lt; input.Length; i++)
{
if (smallestElementValue &amp;gt; input[i]) {
smallestElementValue = input[i];
smallestElementIndex = i;
}
}
return smallestElementIndex;
}

public static void Swap(int[] input, int indexLeft, int indexRight)
{
if (indexLeft == indexRight) return;
var temp = input[indexLeft];
input[indexLeft] = input[indexRight];
input[indexRight] = temp;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Сортировка слиянием&lt;/b&gt;&lt;br /&gt;
Разбивает массив на подмассивы примерно одинакового размера, рекурсивно разбивает до тех пор, пока размер массива будет равен 1. После этого происходит сортировка частей и их соединение в один.&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;/td&gt;
&lt;td&gt;Лучший вариант&lt;/td&gt;
&lt;td&gt;Средний вариант&lt;/td&gt;
&lt;td&gt;Худший вариант&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Степень сложности&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Рост памяти&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public static void Sort(int[] input) {
if (input.Length &amp;lt;= 1) {
return;
}

// Поделили массивы
var leftSize = input.Length / 2;
var rightSize = input.Length - leftSize;
var left = new int[leftSize];
var right = new int[rightSize];

// Скопировали данные из основного массива
Array.Copy(input, 0, left, 0, leftSize);
Array.Copy(input, leftSize, right, 0, rightSize);

Sort(left);
Sort(right);

Merge(input, left, right);
}

public static void Merge(int[] input, int[] left, int[] right) {
var leftIndex = 0;
var rightIndex = 0;
var leftAndRightSize = left.Length + right.Length;

for (int i = 0; i &amp;lt; leftAndRightSize; i++) {
if (leftIndex &amp;gt;= left.Length) {
input[i] = right[rightIndex];
rightIndex++;
}
else if (rightIndex &amp;gt;= right.Length) {
input[i] = left[leftIndex];
leftIndex++;
}
else if (left[leftIndex] &amp;lt; right[rightIndex]) {
input[i] = left[leftIndex];
leftIndex++;
}
else {
input[i] = right[rightIndex];
rightIndex++;
}
}
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Быстрая сортировка&lt;/b&gt;&lt;br /&gt;
Один из самых быстрых алгоритмов, о чем говорит название.&lt;/p&gt;
&lt;p&gt;Этапы сортировки:&lt;br /&gt;
• Случайный выбор опорного элемента массива (pivotValue), относительно которого переупорядочиваются элементы массива.&lt;br /&gt;
• Переместить все значения, которые больше опорного, вправо, а все значения, что меньше опорного, влево.&lt;br /&gt;
• Повторить алгоритм для неотсортированной левой и правой части массива, пока каждый элемент не окажется на своей позиции.&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;/td&gt;
&lt;td&gt;Лучший вариант&lt;/td&gt;
&lt;td&gt;Средний вариант&lt;/td&gt;
&lt;td&gt;Худший вариант&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Степень сложности&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Рост памяти&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public static void Sort(int[] input, int leftIndex, int rightIndex) {
var i = leftIndex;
var j = rightIndex;
var pivot = input[(leftIndex + rightIndex) &amp;gt;&amp;gt; 1];

while (i &amp;lt;= j) {
while (input[i] &amp;lt; pivot) {
i++;
}
while (input[j] &amp;gt; pivot) {
j--;
}

if (i &amp;lt;= j) {
Swap(input, i, j);
j--;
i++;
}

if (leftIndex &amp;lt; j) {
Sort(input, leftIndex, j);
}

if (rightIndex &amp;gt; i) {
Sort(input, i, rightIndex);
}
}
}

public static void Swap(int[] input, int indexLeft, int indexRight)
{
if (indexLeft == indexRight) return;
var temp = input[indexLeft];
input[indexLeft] = input[indexRight];
input[indexRight] = temp;
}&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Структуры данных: связанные списки</title>
<guid isPermaLink="false">77</guid>
<link>https://www.stefaniuk.website/all/data-structures-linked-lists/</link>
<pubDate>Mon, 24 Jun 2019 14:18:13 -0400</pubDate>
<author></author>
<comments>https://www.stefaniuk.website/all/data-structures-linked-lists/</comments>
<description>
&lt;p&gt;Связанные списки — структура данных, элемент которой содержит собственные данные, так и одну или две ссылки на следующий и/или предыдущий элемент. Связанный список работает как массив, который растет и уменьшаться при необходимости из произвольной точки массива.&lt;/p&gt;
&lt;p&gt;Наиболее используемые типы списков:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Односвязный список&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;+-------+------+    +-------+-------+ 
| Hello |  *---+---&amp;gt;| world | null  +
+-------+------+    +-------+-------+&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;Двусвязный список&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;+----------+---------+       +----------+----------+ 
|          |     *---|------&amp;gt;|          |          |
|  Hello   |         |       |          |  world   |
|          |         |&amp;lt;------|---*      |          |
+----------+---------+       +----------+----------+&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Кольцевой список&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;.        +-------&amp;lt;----------&amp;lt;--------------&amp;lt;--------+
         |                                          |
+-----+--+---+    +-----+------+    +-----+-----+   |
| 12  |  *---+---&amp;gt;| 15  |   *--+---&amp;gt;| 25  |  *--+---+
+-----+------+    +-----+------+    +-----+-----+&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;br /&gt;
&lt;b&gt;Быстродействие&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Добавляет элемент в конец/начало списка — O(1)&lt;/li&gt;
&lt;li&gt;Удаляет первый элемент списка со значением, равным переданному — O(n)&lt;/li&gt;
&lt;li&gt;Поиск элемента — O(n)&lt;/li&gt;
&lt;li&gt;Копирование а массив — O(n)&lt;/li&gt;
&lt;li&gt;Получить количество — O(1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Преимущества&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Элементы могут быть удалены или добавлены из середины списка&lt;/li&gt;
&lt;li&gt;Нет необходимости объявлять разvер списка при инициализации&lt;/li&gt;
&lt;li&gt;Эффективное удаление и добавление элементов&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;Недостатки&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Связанные списки не имеют возможности произвольного доступа к элементам — т. е. нет возможности получить элемент внутри списка, без того что бы пройтись по всем элементам до него.&lt;/li&gt;
&lt;li&gt;Для работы списков требуется динамическое выделение памяти, что может привести к утечкам памяти.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;Ссылки&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;a href="https://metanit.com/sharp/algoritm/2.1.php"&gt;https://metanit.com/sharp/algoritm/2.1.php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rtfm.co.ua/c-svyazannye-spiski/"&gt;https://rtfm.co.ua/c-svyazannye-spiski/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ru.wikipedia.org/wiki/Связный_список"&gt;https://ru.wikipedia.org/wiki/Связный_список&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tproger.ru/translations/linked-list-for-beginners/"&gt;https://tproger.ru/translations/linked-list-for-beginners/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/outco/reversing-a-linked-list-easy-as-1-2-3-560fbffe2088"&gt;https://medium.com/outco/reversing-a-linked-list-easy-as-1-2-3-560fbffe2088&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>


</channel>
</rss>