<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>GameDev Academy</title><generator>teletype.in</generator><description><![CDATA[GameDev Academy]]></description><image><url>https://img4.teletype.in/files/be/91/be917f2d-723b-465e-8c4c-6dc80dcfea87.png</url><title>GameDev Academy</title><link>https://teletype.in/@gamedev_academy_feedback</link></image><link>https://teletype.in/@gamedev_academy_feedback?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/gamedev_academy_feedback?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/gamedev_academy_feedback?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 14 May 2026 10:41:54 GMT</pubDate><lastBuildDate>Thu, 14 May 2026 10:41:54 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/usnpyDy3Q2p</guid><link>https://teletype.in/@gamedev_academy_feedback/usnpyDy3Q2p?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/usnpyDy3Q2p?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>21. Алгоритмизация и структуры данных. Примеры задач.</title><pubDate>Mon, 04 Oct 2021 06:02:34 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/9f/39/9f398560-04fc-4286-8a3c-2a2d055dd3f1.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img1.teletype.in/files/80/2a/802a88f8-92f2-4a29-868d-82229e7c4bb4.png"></img>Давайте попробуем существенно ускорить стандартный алгоритм Дейкстры, используя для выбора вершины с минимальным весом из не рассмотренных двоичную кучу.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="x5jz">1. Дейкстра с двоичной кучей.</h2>
  <p id="Wuly">Давайте попробуем существенно ускорить стандартный алгоритм Дейкстры, используя для выбора вершины с минимальным весом из не рассмотренных двоичную кучу.</p>
  <h2 id="98BL">2. Дерево поиска.</h2>
  <p id="z9xD">Вам повезло! Созданная вами игра набирает огромное количество пользователей. Но с некоторых пор игроки стали жаловаться, что приходится очень долго ждать авторизации на сервере. Проблема в том, что большое количество игроков существенно замедляет процесс проверки правильности логина и пароля конкретного пользователя. Сервер не справляется и ставит в очередь пытающихся залогиниться игроков. Давайте попробуем использовать бинарное дерево поиска для хранения и проверки логина и пароля игроков.</p>
  <p id="AI48">Задан список запросов к серверу. В каждом запросе сначала указывается логин, затем пароль и третьим параметром тип запроса: 0 – для авторизации, 1 – для регистрации. Программа должна быстро выводить логин и фразу «Доступ открыт!» или «Доступ запрещен» для каждого запроса авторизации.</p>
  <h2 id="nbRr">3. Гороскоп.</h2>
  <p id="5vMo">Пользователь вводит число и месяц рождения. Программа выводит его знак зодиака и предсказание на 2022 год. Пользователей может быть очень много, поэтому надо попытаться максимально оптимизировать программу: в идеале до одной конструкции if для каждого пользователя.</p>
  <figure id="yVSS" class="m_original">
    <img src="https://img1.teletype.in/files/80/2a/802a88f8-92f2-4a29-868d-82229e7c4bb4.png" width="560" />
  </figure>
  <hr />
  <h2 id="BrNl">Приложение к лекции.</h2>
  <pre id="zJiV" data-lang="clike">using System;
using System.Collections.Generic;

namespace ConsoleApplication47
{
    internal class Program
    {
        private static int[,] _graf;
        private static List&lt;Vertex&gt; _list;

        public class Vertex
        {
            public Vertex(int index, int distance)
            {
                Index = index;
                Distance = distance;
            }

            public int Index;
            public int Distance;
        }

        public static void Main(string[] args)
        {
            int start = 0;
            int end = 0;
            //Console.Write(&quot;Введите N=&quot;);
            //int n = Convert.ToInt32(Console.ReadLine());
            int n = 7;

            int[,] graf = new int[n, n];
            _list = new List&lt;Vertex&gt;();

            //Console.Write(&quot;Введите M=&quot;);
            //int m = Convert.ToInt32(Console.ReadLine());
            int m = 10;

            /*
            for (int i = 0; i &lt; m; i++)
            {
                Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
                start = Convert.ToInt32(Console.ReadLine());
                Console.Write(&quot;Номер города, в который идет дорога: &quot;);
                end = Convert.ToInt32(Console.ReadLine());
                Console.Write(&quot;Номер города, в который идет дорога: &quot;);
                int size = Convert.ToInt32(Console.ReadLine());
                graf[start, end] = size;
                graf[end, start] = size;
            }
            */

            graf[0, 3] = 25;
            graf[3, 0] = 25;
            graf[0, 5] = 23;
            graf[5, 0] = 23;
            graf[0, 6] = 27;
            graf[6, 0] = 27;
            graf[1, 3] = 22;
            graf[3, 1] = 22;
            graf[1, 4] = 29;
            graf[4, 1] = 29;
            graf[2, 4] = 24;
            graf[4, 2] = 24;
            graf[2, 6] = 23;
            graf[6, 2] = 23;
            graf[3, 4] = 54;
            graf[4, 3] = 54;
            graf[4, 5] = 59;
            graf[5, 4] = 59;
            graf[4, 6] = 49;
            graf[6, 4] = 49;

            //Console.Write(&quot;Введите X=&quot;);
            //start = Convert.ToInt32(Console.ReadLine());
            start = 0;

            //Console.Write(&quot;Введите Y=&quot;);
            //end = Convert.ToInt32(Console.ReadLine());
            end = 4;

            int[] path = new int[n];
            for (int i = 0; i &lt; n; i++)
            {
                path[i] = Int32.MaxValue;
            }

            Boolean[] uses = new Boolean[n];


            path[start] = 0;
            int usesCount = 0;

            Vertex ver = new Vertex(start, 0);
            Add(ver);

            while (usesCount &lt; n)
            {
                /*   int min = Int32.MaxValue;
                   int current = 0;
                   for (int i = 0; i &lt; n; i++)
                   {
                       if (!uses[i] &amp;&amp; path[i] &lt; min)
                       {
                           min = path[i];
                           current = i;
                       }
                   }*/
                int current = GetMax();

                for (int i = 0; i &lt; n; i++)
                {
                    if (graf[current, i] &gt; 0 &amp;&amp; !uses[i])
                    {
                        if (path[current] + graf[current, i] &lt; path[i])
                        {
                            path[i] = path[current] + graf[current, i];
                            Add(new Vertex(i, path[i]));
                        }
                    }
                }

                uses[current] = true;
                usesCount++;
            }

            for (int i = 0; i &lt; n; i++)
            {
                Console.WriteLine(i + &quot; - &quot; + path[i]);
            }

            Console.WriteLine(&quot;Ответ: &quot; + path[end]);
        }

        private static void Add(Vertex value)
        {
            _list.Add(value);
            int i = HeapSize() - 1;
            int parent = (i - 1) / 2;

            while (i &gt; 0 &amp;&amp; _list[parent].Distance &gt; _list[i].Distance)
            {
                Vertex temp = _list[i];
                _list[i] = _list[parent];
                _list[parent] = temp;

                i = parent;
                parent = (i - 1) / 2;
            }
        }

        private static void Heapify(int i)
        {
            int leftChild;
            int rightChild;
            int largestChild;

            for (;;)
            {
                leftChild = 2 * i + 1;
                rightChild = 2 * i + 2;
                largestChild = i;

                if (leftChild &lt; HeapSize() &amp;&amp; _list[leftChild].Distance &lt; _list[largestChild].Distance)
                {
                    largestChild = leftChild;
                }

                if (rightChild &lt; HeapSize() &amp;&amp; _list[rightChild].Distance &lt; _list[largestChild].Distance)
                {
                    largestChild = rightChild;
                }

                if (largestChild == i)
                {
                    break;
                }

                Vertex temp = _list[i];
                _list[i] = _list[largestChild];
                _list[largestChild] = temp;
                i = largestChild;
            }
        }

        private static int GetMax()
        {
            int result = _list[0].Index;
            _list[0] = _list[HeapSize() - 1];
            _list.RemoveAt(HeapSize() - 1);
            Heapify(0);
            return result;
        }

        private static int HeapSize()
        {
            return _list.Count;
        }

        private static void Print()
        {
            int i = 0;
            int k = 1;
            while (i &lt; HeapSize())
            {
                while ((i &lt; k) &amp;&amp; (i &lt; HeapSize()))
                {
                    Console.Write(_list[i].Distance + &quot; &quot;);
                    i++;
                }

                Console.WriteLine();
                k = k * 2 + 1;
            }
        }
    }
}</pre>
  <pre id="zJiV" data-lang="clike">using System;
using System.Collections.Generic;

namespace ConsoleApplication47
{
    internal class Program
    {
        private static int[,] _graf;

        public class User
        {
            public User(string login, string password)
            {
                Login = login;
                Password = password;
            }

            public string Login;
            public string Password;
        }

        public class Inquiry
        {
            public Inquiry(string login, string password, int type)
            {
                Login = login;
                Password = password;
                Type = type;
            }

            public string Login;
            public string Password;
            public int Type;
        }

        public static void Main(string[] args)
        {
            List&lt;Inquiry&gt; inquiries = new List&lt;Inquiry&gt;();
            inquiries.Add(new Inquiry(&quot;admin&quot;, &quot;12345&quot;, 1));
            inquiries.Add(new Inquiry(&quot;user&quot;, &quot;111&quot;, 1));
            inquiries.Add(new Inquiry(&quot;Admin&quot;, &quot;777&quot;, 1));
            inquiries.Add(new Inquiry(&quot;admin&quot;, &quot;12345&quot;, 0));
            inquiries.Add(new Inquiry(&quot;user&quot;, &quot;12345&quot;, 0));
            inquiries.Add(new Inquiry(&quot;user&quot;, &quot;111&quot;, 0));
            inquiries.Add(new Inquiry(&quot;Sasha123&quot;, &quot;12011995&quot;, 1));
            inquiries.Add(new Inquiry(&quot;admin&quot;, &quot;12345&quot;, 0));
            inquiries.Add(new Inquiry(&quot;Sasha123&quot;, &quot;12011995&quot;, 0));
            inquiries.Add(new Inquiry(&quot;Sasha123&quot;, &quot;12345&quot;, 0));

            User first = new User(&quot;&quot;, &quot;wjekljkljhsf234jhkh345h34h534j5h34kj5h3jh23j45jhewjhfsdlkjfg&quot;);
            BinaryTree tree = new BinaryTree(first, null);

            for (int i = 0; i &lt; inquiries.Count; i++)
            {
                if (inquiries[i].Type == 1)
                {
                    User user = new User(inquiries[i].Login, inquiries[i].Password);
                    tree.Add(user);
                }
                else
                {
                    User user = new User(inquiries[i].Login, inquiries[i].Password);
                    if (tree.Search(user) == null)
                    {
                        Console.WriteLine(&quot;Элемент не найден!&quot;);
                    }
                    else
                    {
                        Console.WriteLine(&quot;Элемент найден!&quot;);
                    }
                }
            }
        }

        public class BinaryTree
        {
            private BinaryTree _parent, _left, _right;
            private User _value;

            public BinaryTree(User value, BinaryTree parent)
            {
                _value = value;
                _parent = parent;
            }

            public void Add(User value)
            {
                if (String.Compare(value.Login, _value.Login) &lt; 0)
                {
                    if (_left == null)
                    {
                        _left = new BinaryTree(value, this);
                    }
                    else if (_left != null)
                    {
                        _left.Add(value);
                    }
                }
                else
                {
                    if (_right == null)
                    {
                        _right = new BinaryTree(value, this);
                    }
                    else if (_right != null)
                    {
                        _right.Add(value);
                    }
                }
            }

            private BinaryTree Search(BinaryTree tree, User value)
            {
                if (tree == null)
                {
                    return null;
                }

                if (string.Compare(value.Login, tree._value.Login) == 0 &amp;&amp;
                    string.Compare(value.Password, tree._value.Password) == 0)
                {
                    return tree;
                }

                if (string.Compare(value.Login, tree._value.Login) &gt; 0)
                {
                    return Search(tree._right, value);
                }
                else
                {
                    return Search(tree._left, value);
                }
            }

            public BinaryTree Search(User value)
            {
                return Search(this, value);
            }

            private void Print(BinaryTree node)
            {
                if (node == null) return;

                Print(node._left);

                Console.Write(node + &quot; &quot;);

                if (node._right != null)
                {
                    Print(node._right);
                }
            }

            public void Print()
            {
                Print(this);

                Console.WriteLine();
            }

            public override string ToString()
            {
                return _value.Login;
            }
        }
    }
}</pre>
  <pre id="zJiV" data-lang="clike">using System;

namespace ConsoleApplication48
{
    internal class Program
    {
        private class ZodiacSign
        {
            public ZodiacSign(int day, string sign)
            {
                Day = day;
                Sign = sign;
            }

            public int Day;
            public string Sign;
        }

        public static void Main(string[] args)
        {
            ZodiacSign[] signs = new ZodiacSign[13];

            signs[0] = new ZodiacSign(19, &quot;Козерог&quot;);
            signs[1] = new ZodiacSign(18, &quot;Водолей&quot;);
            signs[2] = new ZodiacSign(20, &quot;Рыбы&quot;);
            signs[3] = new ZodiacSign(20, &quot;Овен&quot;);
            signs[4] = new ZodiacSign(20, &quot;Телец&quot;);
            signs[5] = new ZodiacSign(20, &quot;Близнецы&quot;);
            signs[6] = new ZodiacSign(22, &quot;Рак&quot;);
            signs[7] = new ZodiacSign(22, &quot;Лев&quot;);
            signs[8] = new ZodiacSign(23, &quot;Дева&quot;);
            signs[9] = new ZodiacSign(23, &quot;Весы&quot;);
            signs[10] = new ZodiacSign(21, &quot;Скорпион&quot;);
            signs[11] = new ZodiacSign(21, &quot;Стрелец&quot;);
            signs[12] = new ZodiacSign(32, &quot;Козерог&quot;);

            while (true)
            {
                Console.WriteLine(&quot;Для выхода введите 0!&quot;);
                Console.Write(&quot;Введите номер дня рождения:&quot;);
                int day = Convert.ToInt32(Console.ReadLine());
                if (day == 0)
                {
                    break;
                }

                Console.Write(&quot;Введите номер месяца рождения:&quot;);
                int month = Convert.ToInt32(Console.ReadLine());


                string prediction = &quot;&quot;;
                if (day &lt;= signs[month - 1].Day)
                {
                    prediction = signs[month - 1].Sign;
                }
                else
                {
                    prediction = signs[month].Sign;
                }

                Console.WriteLine(prediction);
            }
        }
    }
}</pre>
  <hr />
  <h2 id="KM6x">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="f2SN">Мы заканчиваем блок, в котором только прикоснулись к огромному количеству алгоритмов и структур данных, придуманных для решения самых разных задач. К каждой задаче надо подобрать свой ключик, выбрать удобный вариант хранения данных, подобрать подходящий алгоритм. Но все алгоритмы знать невозможно! Да и нужно ли?! Гораздо интереснее самому (конечно, опираясь на изученные ранее алгоритмы и написанные программы) придумать быстрое, эффективное и короткое (ну, не всегда и короткое) решение для конкретной задачи. И самое главное, получить настоящий кайф от того, что получилось! Причем получилось очень даже неплохо… Порадовались? А вас уже ждет следующая задача… И опять проблемы и непростой поиск решения… Но это и есть путь… Настоящий путь программиста…</p>
    <p id="soK7">Думайте, пробуйте, ищите, дерзайте! Всем вам удачи!</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/ZDhI_r2F8dS</guid><link>https://teletype.in/@gamedev_academy_feedback/ZDhI_r2F8dS?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/ZDhI_r2F8dS?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>20. Двоичная куча.</title><pubDate>Mon, 04 Oct 2021 05:57:40 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/bf/f8/bff8a1dd-89cd-4a9b-84de-6b22771c49c8.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img2.teletype.in/files/98/4a/984aa32d-1f68-405f-9bee-903faf08eaba.png"></img>Ну а напоследок, мы с вами поговорим еще про одну структуру, которая представляет из себя не очень сложно реализуемое дерево, которое используется для сортировки и быстрого извлечения элементов с максимальным приоритетом.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="Quya"><strong>1. Двоичная куча.</strong></h2>
  <p id="0otw">Ну а напоследок, мы с вами поговорим еще про одну структуру, которая представляет из себя не очень сложно реализуемое дерево, которое используется для сортировки и быстрого извлечения элементов с максимальным приоритетом.</p>
  <p id="kT6N"><strong>Двоичная куча (binary heap)</strong> – достаточно просто реализуемая структура данных, позволяющая быстро (за логарифмическое время) добавлять элементы и извлекать элемент с максимальным приоритетом (например, максимальный или минимальный по значению).</p>
  <p id="zqIH">Двоичная куча представляет собой полное бинарное дерево, для которого выполняется основное свойство кучи: приоритет каждой вершины больше приоритетов её потомков. В простейшем случае приоритет каждой вершины можно считать равным её значению. В таком случае структура называется max-heap, поскольку корень поддерева является максимумом из значений элементов поддерева. А дерево называется полным бинарным, если у каждой вершины есть не более двух потомков, а заполнение уровней вершин идет сверху вниз (в пределах одного уровня – слева направо).</p>
  <figure id="iBAW" class="m_column">
    <img src="https://img2.teletype.in/files/98/4a/984aa32d-1f68-405f-9bee-903faf08eaba.png" width="956" />
  </figure>
  <p id="DLTz">Двоичную кучу удобно хранить в виде одномерного массива, причем левый потомок вершины с индексом i имеет индекс 2*i+1, а правый 2*i+2. Корень дерева – элемент с индексом 0. Высота двоичной кучи равна высоте дерева, то есть log2N, где N – количество элементов массива.</p>
  <h2 id="fSpz">2. Добавление элемента.</h2>
  <p id="23Ve">Новый элемент добавляется на последнее место в массиве, то есть позицию с индексом heapSize, где heapSize это количество элементов в куче:</p>
  <figure id="6ajS" class="m_column">
    <img src="https://img4.teletype.in/files/be/4b/be4bbe67-1023-41cd-9719-6c2ae4b1021a.png" width="946" />
  </figure>
  <p id="AbP0">Возможно, что при этом будет нарушено основное свойство кучи, так как новый элемент может быть больше родителя. В таком случае следует «поднимать» новый элемент на один уровень (менять с вершиной-родителем) до тех пор, пока не будет соблюдено основное свойство кучи:</p>
  <figure id="ICW4" class="m_column">
    <img src="https://img1.teletype.in/files/0d/ca/0dca3cd2-fd01-4e8d-9bc3-a6676af2cf58.png" width="958" />
  </figure>
  <p id="IIxq">Иначе говоря, новый элемент «всплывает», «проталкивается» вверх, пока не займет свое место. Сложность алгоритма не превышает высоты двоичной кучи (так как количество «подъемов» не больше высоты дерева), то есть равна O(log2N).</p>
  <pre id="I3Nx" data-lang="clike">public void add(int value)
{
    _list.Add(value);
    int i = heapSize - 1;
    int parent = (i - 1) / 2;

    while (i &gt; 0 &amp;&amp; _list[parent] &lt; _list[i])
    {
        int temp = _list[i];
        _list[i] = _list[parent];
        _list[parent] = temp;

        i = parent;
        parent = (i - 1) / 2;
    }
}</pre>
  <h2 id="UrmH">3. Упорядочение двоичной кучи.</h2>
  <p id="IdUU">В ходе других операций с уже построенной двоичной кучей также может нарушиться основное свойство кучи: вершина может стать меньше своего потомка.</p>
  <figure id="Lsmw" class="m_column">
    <img src="https://img1.teletype.in/files/4b/ae/4baeae4b-26a1-4fd6-979f-922204f2b075.png" width="944" />
  </figure>
  <p id="yYrD">Метод heapify восстанавливает основное свойство кучи для дерева с корнем в i-ой вершине при условии, что оба поддерева ему удовлетворяют. Для этого необходимо «опускать» i-ую вершину (менять местами с наибольшим из потомков), пока основное свойство не будет восстановлено (процесс завершится, когда не найдется потомка, большего своего родителя). Нетрудно понять, что сложность этого алгоритма также равна O(log2N).</p>
  <figure id="ayHc" class="m_column">
    <img src="https://img3.teletype.in/files/a3/1a/a31aaaf8-68e3-4a95-af3c-9253bc1b3e15.png" width="948" />
  </figure>
  <pre id="mzey" data-lang="clike">public void heapify(int i)
{
    int leftChild;
    int rightChild;
    int largestChild;

    for (; ; )
    {
        leftChild = 2 * i + 1;
        rightChild = 2 * i + 2;
        largestChild = i;

        if (leftChild &lt; heapSize &amp;&amp; _list[leftChild] &gt; _list[largestChild]) 
        {
            largestChild = _leftChild;
        }

        if (rightChild &lt; heapSize &amp;&amp; _list[rightChild] &gt; _list[largestChild])
        {
            largestChild = rightChild;
        }

        if (largestChild == i) 
        {
            break;
        }

        int temp = _list[i];
        _list[i] = _list[largestChild];
        _list[largestChild] = temp;
        i = largestChild;
    }
}</pre>
  <h2 id="5wnz">4. Построение двоичной кучи.</h2>
  <p id="ZQZm">Наиболее очевидный способ построить кучу из неупорядоченного массива – это по очереди добавить все его элементы. Временная оценка такого алгоритма O(N log2N). Однако можно построить кучу еще быстрее — за О(N). Сначала следует построить дерево из всех элементов массива, не заботясь о соблюдении основного свойства кучи, а потом вызвать метод heapify для всех вершин, у которых есть хотя бы один потомок (так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены). Потомки гарантированно есть у первых heapSize/2 вершин.</p>
  <h2 id="JZI0">5. Извлечение (удаление) максимального элемента.</h2>
  <p id="SQVB">В упорядоченном max-heap максимальный элемент всегда хранится в корне. Восстановить упорядоченность двоичной кучи после удаления максимального элемента можно, поставив на его место последний элемент и вызвав heapify для корня, то есть упорядочив все дерево.</p>
  <pre id="L9Fp" data-lang="clike">public int getMax()
{
    int result = _list[0];
    _list[0] = _list[heapSize - 1];
    _list.RemoveAt(heapSize - 1);
    heapify(0);
    return result;
}</pre>
  <h2 id="UEVP">6. Сортировка с применением двоичной кучи.</h2>
  <p id="6YaX">Заметим, что можно отсортировать массив, сначала построив из него двоичную кучу, а потом последовательно извлекая максимальные (или соответственно минимальные) элементы. Оценим временную сложность такого элемента: построение кучи – O(N), извлечение N элементов – O(N log2N). Следовательно, итоговая оценка O(N log2N). При этом дополнительная память для массива не используется.</p>
  <hr />
  <h2 id="X3Tt">Приложение к лекции.</h2>
  <pre id="RnOG" data-lang="clike">using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace ConsoleApplication46
{
    internal class Program
    {
        private static List&lt;int&gt; _list;

        public static void Main(string[] args)
        {
            _list = new List&lt;int&gt;();
            add(5);
            add(7);
            add(9);
            add(11);
            add(3);
            add(7);
            add(15);
            Print();
            int x = GetMax();
            Console.WriteLine(x);
            Print();
        }

        private static void add(int value)
        {
            _list.Add(value);
            int i = HeapSize() - 1;
            int parent = (i - 1) / 2;

            while (i &gt; 0 &amp;&amp; _list[parent] &lt; _list[i])
            {
                int temp = _list[i];
                _list[i] = _list[parent];
                _list[parent] = temp;

                i = parent;
                parent = (i - 1) / 2;
            }
        }

        private static void Heapify(int i)
        {
            int leftChild;
            int rightChild;
            int largestChild;

            for (;;)
            {
                leftChild = 2 * i + 1;
                rightChild = 2 * i + 2;
                largestChild = i;

                if (leftChild &lt; HeapSize() &amp;&amp; _list[leftChild] &gt; _list[largestChild])
                {
                    largestChild = leftChild;
                }

                if (rightChild &lt; HeapSize() &amp;&amp; _list[rightChild] &gt; _list[largestChild])
                {
                    largestChild = rightChild;
                }

                if (largestChild == i)
                {
                    break;
                }

                int temp = _list[i];
                _list[i] = _list[largestChild];
                _list[largestChild] = temp;
                i = largestChild;
            }
        }

        private static int GetMax()
        {
            int result = _list[0];
            _list[0] = _list[HeapSize() - 1];
            _list.RemoveAt(HeapSize() - 1);
            Heapify(0);
            return result;
        }

        private static int HeapSize()
        {
            return _list.Count;
        }

        private static void Print()
        {
            int i = 0;
            int k = 1;
            while (i &lt; HeapSize())
            {
                while ((i &lt; k) &amp;&amp; (i &lt; HeapSize()))
                {
                    Console.Write(_list[i] + &quot; &quot;);
                    i++;
                }

                Console.WriteLine();
                k = k * 2 + 1;
            }
        }
    }
}</pre>
  <hr />
  <h2 id="kFqb">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="N1sU">Я надеюсь, вы поняли, что в программировании куча – очень полезная и не слишком сложная структура, которая позволяет быстро последовательно извлекать, например, минимальные или максимальные элементы из наборов данных, достаточно быстро сортировать данные. Двоичная куча имеет структуру дерева логарифмической высоты (относительно количества вершин), позволяет за логарифмическое же время добавлять элементы и извлекать элемент с максимальным приоритетом за константное время. В то же время двоичная куча проста в реализации и не требует дополнительной памяти. Пример применения двоичной кучи для ускорения алгоритма Дейкстры мы обязательно рассмотрим на следующем занятии.</p>
    <p id="Sj9c">А перед следующим занятием я только скажу вам, что поплутать в поисках выхода среди графов, деревьев и куч не страшно, страшно заблудиться в трех соснах или «прошляпить» выгодный заказ, бесконечно выбирая, какой метод использовать при реализации… Но это уже как-то напоминает Буриданова осла с его копнами сена… А я как-то не слышал про ослов, которые долго проработали программистами…</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/9MVItb1Tg68</guid><link>https://teletype.in/@gamedev_academy_feedback/9MVItb1Tg68?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/9MVItb1Tg68?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>19. Сбалансированные деревья (общее понятие). Виды деревьев.</title><pubDate>Mon, 04 Oct 2021 05:49:27 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7e/8d/7e8d284e-26e1-41ea-b986-7c6199ed4851.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img1.teletype.in/files/c9/3d/c93df7f5-4207-4a55-b84e-c8810aaf29c3.png"></img>В программировании используют достаточно много видов деревьев:]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="yFkC">1. Сбалансированные деревья.</h2>
  <p id="Jd2c">В программировании используют достаточно много видов деревьев:</p>
  <p id="Qcp3"><strong>N-арное дерево</strong> — это дерево, в котором каждая вершина имеет не более чем N детей.</p>
  <p id="iOYu"><strong>2-3 дерево</strong> — сбалансированное дерево поиска, такое, что каждый узел может иметь два или три сына и глубина всех листьев одинакова.</p>
  <p id="jHQm">А еще В-деревья, деревья цифрового поиска, нагруженные деревья, patricia-деревья, синтаксические деревья и другие.</p>
  <p id="Gnys">Но все-таки чаще всего используется бинарное дерево поиска, рассмотренное нами на предыдущем занятии. Но есть одно но…<br />Время выполнения базовых операций в дереве поиска линейно зависит от его высоты. Но из одного и того же набора ключей можно построить разные деревья поиска:<br /></p>
  <figure id="HhTc" class="m_column">
    <img src="https://img1.teletype.in/files/c9/3d/c93df7f5-4207-4a55-b84e-c8810aaf29c3.png" width="854" />
  </figure>
  <p id="B8aq">В приведенном примере на рисунке оба дерева построены из одного<br />и того же набора ключей, но высота первого дерева больше, поэтому время выполнения операций над ним будет больше. Например, при поиске узла с ключом 6 в первом случае требуется просмотреть все шесть узлов, а во втором только три узла: 3-&gt;5-&gt;6. </p>
  <p id="v8bo">Второе дерево лучше сбалансировано, чем первое. Чем больше узлов в дереве, тем более очевидно преимущество дерева поиска над обычным двоичным деревом при условии, что дерево поиска хорошо сбалансировано. </p>
  <h3 id="AtrF">Как построить дерево поиска минимальной высоты? </h3>
  <p id="Tp2u">Если набор ключей известен заранее, то его надо упорядочить. Корнем поддерева становится узел с ключом, значение которого – медиана этого набора. Для упорядоченного набора, содержащего нечетное количество ключей, – это ключ, находящийся ровно в середине набора. Для набора 1,2,3,4,5,6, который содержит четное количество ключей, в качестве медианы может быть выбран ключ как со значением 3, так и со значением 4. </p>
  <p id="dmfj">Для определенности выберем 3. Узел с этим ключом будет корнем дерева.</p>
  <p id="fmNC">Далее, ключи, меньшие 3, попадут в левое поддерево, а ключи, большие 3, попадут в правое поддерево. Для построения левого и правого поддеревьев проделываем ту же процедуру на соответствующих наборах ключей. И так до тех пор, пока все ключи не будут включены в дерево. </p>
  <p id="24pH">Дерево, изображенное на рисунке выше справа является идеально сбалансированным, то есть, для каждого его узла количество узлов в левом и правом поддеревьях различается не более, чем на 1. Такое дерево можно построить для любого набора ключей. Достаточно заметить, что при выборе медианы на каждом шагу при построении дерева набор ключей разбивается на две части, и количество ключей в них может различаться не более, чем на 1.</p>
  <h3 id="revm">Но полный набор ключей не всегда известен заранее. </h3>
  <p id="MkYP">Если ключи поступают по очереди, то построение дерева поиска будет зависеть от порядка их поступления. Если, например, ключи будут поступать в порядке 1,2,3,4,5,6, то получится дерево, изображенное на рисунке слева. Высота такого дерева максимальна для этого набора ключей, следовательно, и время выполнения операций над ним также будет максимальным. Поэтому при добавлении очередного узла, возможно, дерево понадобится перестраивать, чтобы уменьшить его высоту, сохраняя тот же набор узлов. </p>
  <p id="jiYX">Идеальную балансировку поддерживать сложно. Если при добавлении очередного узла количество узлов в левом и правом поддеревьях какого-либо узла дерева станет различаться более, чем на 1, то дерево не будет являться идеально сбалансированным, и его надо будет перестраивать, чтобы восстановить свойства идеально сбалансированного дерева поиска. Поэтому обычно требования к сбалансированности дерева менее строгие.</p>
  <p id="aQyh"><strong>Рассмотрим три основных вида сбалансированных деревьев поиска:</strong></p>
  <p id="UDD5">⦁	АВЛ-деревья,<br />⦁	красно-черные деревья и<br />⦁	самоперестраивающиеся деревья (splay-деревья).</p>
  <p id="4gZS">Для АВЛ-деревьев сбалансированность определяется разностью высот правого и левого поддеревьев любого узла. Если эта разность по модулю не превышает 1, то дерево считается сбалансированным. Данное условие проверяется после каждого добавления или удаления узла, и определен минимальный набор операций перестройки дерева, который приводит к восстановлению свойства сбалансированности, если оно оказалось нарушено. </p>
  <p id="hBdr">В красно-черных деревьях каждый узел имеет дополнительное свойство – цвет, красный или черный. На дерево наложены ограничения по расположению и количеству узлов в зависимости от цвета, и определен набор операций перестройки дерева в случае нарушения этих ограничений после добавления или удаления узла. Если АВЛ-деревья накладывают достаточно строгие условия на сбалансированность, и при добавлении узлов дерево достаточно часто приходится перестраивать, то в красно-черном дереве у каждого узла высоты левого и правого поддеревьев могут отличаться не более, чем в два раза. </p>
  <p id="yIJz">И, наконец, третий вид рассматриваемых сбалансированных деревьев поиска – самоперестраивающиеся деревья. В отличие от двух предыдущих видов, у этих деревьев нет никаких ограничений на расположение узлов, а сбалансированность в среднем достигается за счет того, что каждый раз перед выполнением операции над узлом этот узел перемещается в корень дерева. Но есть вероятность того, что дерево в определенный момент может оказаться не сбалансированным.</p>
  <h2 id="rtzO">2. АВЛ-деревья.</h2>
  <p id="cksb">АВЛ-деревом называется такое дерево поиска, в котором для любого его узла высоты левого и правого поддеревьев отличаются не более, чем на 1. Эта структура данных разработана советскими учеными Адельсон-Вельским Георгием Максимовичем и Ландисом Евгением Михайловичем в 1962 году. Аббревиатура АВЛ соответствует первым буквам фамилий этих ученых. Первоначально АВЛ-деревья были придуманы для организации перебора в шахматных программах. Советская шахматная программа «Каисса» стала первым официальным чемпионом мира в 1974 году.</p>
  <p id="5dlH">В каждом узле АВЛ-дерева, помимо ключа, данных и указателей на левое и правое поддеревья (левого и правого сыновей), хранится показатель баланса – разность высот правого и левого поддеревьев. Ниже  приведен пример АВЛ-дерева. Таким образом, получается, что в АВЛ-дереве показатель баланса balance для каждого узла, включая корень, по модулю не превосходит 1. На рисунке ниже справа приведен пример дерева, которое не является АВЛ-деревом, поскольку в одном из узлов баланс нарушен, т.е. |balance|&gt;1. Здесь и далее для АВЛ-деревьев будем указывать в узле значение показателя баланса.</p>
  <figure id="9pap" class="m_column">
    <img src="https://img3.teletype.in/files/28/53/2853433f-2c10-4a7a-869f-f4cb25efe2b0.png" width="887" />
  </figure>
  <p id="I5c0">Все операции над АВЛ-деревом очень похожи на операции с бинарным деревом поиска, но каждая операция требует еще и последующей балансировки дерева. </p>
  <p id="u7zn">Но анализ возможных случаев показывает, что для исправления разбалансировки в узле p достаточно выполнить либо простой поворот влево вокруг p, либо так называемый большой поворот влево вокруг того же p. Простой поворот выполняется при условии, что высота левого поддерева узла q меньше высоты его правого поддерева: h(s)≤h(D).</p>
  <figure id="wrMN" class="m_custom">
    <img src="https://img1.teletype.in/files/4d/84/4d84da44-f6be-4835-925c-391b27c4de7a.png" width="611" />
  </figure>
  <p id="0aDw">Большой поворот применяется при условии h(s)&gt;h(D) и сводится в данном случае к двум простым — сначала правый поворот вокруг q и затем левый вокруг p.</p>
  <figure id="KLZ7" class="m_column">
    <img src="https://img1.teletype.in/files/82/71/82717633-8389-4318-a7a0-0d5bbf6e3793.png" width="927" />
  </figure>
  <h2 id="jbkV">3. Красно-черные деревья.</h2>
  <p id="fr6G">АВЛ-деревья исторически были первым примером использования сбалансированных деревьев поиска. В настоящее время более популярны красно-черные деревья (КЧ-деревья). Изобретателем красно-черного дерева считается немецкий ученый Рудольф Байер. КЧ-деревья – это двоичные деревья поиска, каждый узел которых хранит дополнительное поле color, обозначающее цвет: красный или черный, и для которых выполнены приведенные ниже свойства.</p>
  <p id="3eG3">Будем считать, что если left или right равны NULL, то это «указатели» на фиктивные листья. Таким образом, все узлы – внутренние (нелистовые).</p>
  <p id="jRfN"><strong>Свойства КЧ-деревьев:</strong></p>
  <p id="HLxP">1. каждый узел либо красный, либо черный;<br />2. каждый лист (фиктивный) – черный;<br />3. если узел красный, то оба его сына – черные;<br />4. все пути, идущие от корня к любому фиктивному листу, содержат одинаковое количество черных узлов;<br />5. корень – черный.</p>
  <p id="xAN5"><strong><em>Определение:</em></strong> Черной высотой узла называется количество черных узлов на пути от этого узла к узлу, у которого оба сына – фиктивные листья.</p>
  <p id="t3uT">Сам узел не включается в это число. Например, у дерева, приведенного на рисунке ниже, черная высота корня равна 2.</p>
  <p id="PZ9S"><strong><em>Определение:</em></strong> Черная высота дерева – черная высота его корня.</p>
  <figure id="zppo" class="m_original">
    <img src="https://img1.teletype.in/files/8c/87/8c871ebc-b81c-41f6-a212-00f239e03303.png" width="474" />
  </figure>
  <h2 id="MHjF">4. Самоперестраивающиеся деревья (splay trees).</h2>
  <p id="lYQH"><strong>Самоперестраивающееся дерево</strong> – это двоичное дерево поиска, которое, в отличие от предыдущих двух видов деревьев не содержит дополнительных служебных полей в структуре данных (баланс, цвет и т.п.). Оно позволяет находить быстрее те данные, которые использовались недавно. Самоперестраивающееся дерево было придумано Робертом Тарьяном и Даниелем Слейтером в 1983 году.</p>
  <p id="VTj7">Идея самоперестраивающихся деревьев основана на принципе перемещения найденного узла в корень дерева. После выполнения этой операции двоичное дерево перестраивается, оставаясь при этом деревом поиска, так, что:</p>
  <p id="7JiA">⦁	если узел с ключом k есть в дереве, то он становится корнем;<br />⦁	если узла с ключом k нет в дереве, то корнем становится его предшественник или последователь.</p>
  <p id="52YU">Идея перемещения найденного узла в корень основана на предположении, что если тот же самый элемент потребуется в ближайшее время, он будет найден быстрее.</p>
  <h2 id="RBA7">5. Сравнение АВЛ, красно-черных и самоперестраивающихся деревьев (splay trees).</h2>
  <p id="MCHW">В общем случае сравнение трех рассмотренных типов деревьев провести затруднительно, поскольку для разных задач и разных наборов данных лучший тип дерева может быть разным. </p>
  <p id="bpJn">По сложности реализации самые простые – АВЛ-дерево, самые сложные – КЧ-деревья, поскольку приходится рассматривать много нетривиальных случаев при вставке и удалении узла. В теории операция удаления в КЧ-дереве эффективнее, в связи с чем они больше распространены.</p>
  <p id="isd6">Самоперестраивающиеся деревья существенно отличаются от АВЛ- и КЧ-деревьев потому что не накладывают никаких ограничений на структуру дерева. Операция поиска в дереве модифицирует само дерево, поэтому в случае обращения к разным узлам самоперестраивающееся дерево может работать медленнее. К тому же, в процессе работы дерево может оказаться полностью разбалансированным. Но доказано, что если вероятности обращения к узлам фиксированы, то самоперестраивающееся дерево будет работать асимптотически не медленнее двух других рассмотренных видов деревьев. Отсутствие дополнительных полей дает преимущество по памяти.</p>
  <p id="ZUup">Различные виды сбалансированных деревьев поиска используются, в частности, в системном программном обеспечении, например, в ядрах операционных систем. </p>
  <h3 id="6Q8b">В каком случае какой вид деревьев лучше всего использовать. </h3>
  <p id="WV3S">Если входные данные полностью рандомизированы, то наилучшим вариантом оказываются деревья поиска общего вида – несбалансированные. Если входные данные в основном рандомизированные, но периодически встречаются упорядоченные наборы, то стоит выбрать КЧ-деревья. Если при вставке преобладают упорядоченные данные, то АВЛ-деревья оказываются лучше в случае, когда дальнейший доступ к элементам рандомизирован, а самоперестраивающиеся деревья – когда дальнейшие доступ последователен или кластеризован.</p>
  <hr />
  <h2 id="IfXr">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="K7iU">В лесу из деревьев, даже если они виртуальные, действительно можно заблудиться. Помните, что подобные алгоритмы необходимы только в тех случаях, когда быстродействие программы вследствие огромного количества выполняемых ей операций по обработке данных является просто жизненно необходимым (конечно, для жизни самой программы). В этом случае смело бросайтесь в изучение соответствующей литературы и поиск готовых решений с последующей их оптимизацией под вашу задачу.</p>
    <p id="dRa6">А на следующем занятии мы поймем, что программистам, как и бомжам, иногда тоже приходится разгребать кучи… Хотя о чем это я! Ведь его величество Интернет – это тоже огромная куча, в которой иногда можно найти очень ценные и полезные вещи…</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/rvlwFFqGPfW</guid><link>https://teletype.in/@gamedev_academy_feedback/rvlwFFqGPfW?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/rvlwFFqGPfW?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>18. Деревья. Двоичные деревья. Двоичное дерево поиска.</title><pubDate>Sun, 03 Oct 2021 19:32:35 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0f/e4/0fe4344a-dd25-4d56-bf2a-8c304f7ccd80.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img3.teletype.in/files/2b/c0/2bc07fb9-a347-4ca9-8afe-679102c7f565.png"></img>Давайте рассмотрим еще одну структуру данных - дерево. ]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="Auvo">1. Деревья.</h2>
  <p id="SCvY">Давайте рассмотрим еще одну структуру данных - дерево. </p>
  <p id="YAO9"><strong>Дерево </strong>– связный граф без циклов. <br />Справочник терминов:<br /><strong>Корень</strong> — самый верхний узел дерева.<br /><strong>Ребро</strong> — связь между двумя узлами.<br /><strong>Потомок</strong> — узел, имеющий родительский узел.<br /><strong>Родитель</strong> — узел, имеющий ребро, соединяющее его с узлом-потомком.<br /><strong>Лист</strong> — узел, не имеющий узлов-потомков на дереве.<br /><strong>Высота </strong>— это длина самого дальнего пути к листу.<br /><strong>Глубина </strong>— длина пути к корню.</p>
  <p id="LAuC">Например, дерево может выглядеть так:</p>
  <figure id="N4Ph" class="m_original">
    <img src="https://img3.teletype.in/files/2b/c0/2bc07fb9-a347-4ca9-8afe-679102c7f565.png" width="695" />
  </figure>
  <h3 id="L6hQ">Структура организации.</h3>
  <p id="7yc4">Это дерево показывает структуру компании. Узлы представляют людей или подразделения, линии — связи и отношения. Дерево — это самый эффективный способ представления и хранения такой информации.</p>
  <p id="WYV7">Дерево на картинке выше очень простое. Оно отражает только отношение родства категорий, но не накладывает никаких ограничений на свою структуру. У генерального директора может быть как один непосредственный подчиненный, так и несколько или ни одного. На рисунке отдел продаж находится левее отдела маркетинга, но порядок на самом деле не имеет значения. Единственное ограничение дерева — каждый узел может иметь не более одного родителя. Самый верхний узел (совет директоров, в нашем случае) родителя не имеет. Этот узел называется «корневым», или «корнем».</p>
  <h2 id="Ijmr">2. Двоичное дерево поиска.</h2>
  <p id="Ekts">Двоичное дерево поиска похоже на дерево из примера выше, но строится по определенным правилам:<br />У каждого узла не более двух детей.<br />Любое значение меньше значения узла становится левым ребенком или ребенком левого ребенка.<br />Любое значение больше или равное значению узла становится правым ребенком или ребенком правого ребенка.</p>
  <p id="clIF">Давайте посмотрим на дерево, построенное по этим правилам:</p>
  <figure id="f8Va" class="m_original">
    <img src="https://img2.teletype.in/files/5a/d8/5ad8f4e8-ec34-47bb-87dc-41756a9ccf74.png" width="335" />
  </figure>
  <p id="crsQ">Обратите внимание, как указанные ограничения влияют на структуру дерева. Каждое значение слева от корня (8) меньше восьми, каждое значение справа — больше либо равно корню. Это правило применимо к любому узлу дерева.</p>
  <p id="g6O8">Основными операциями с бинарными деревьями являются <strong>добавление элемента</strong> в дерево, <strong>удаление элемента</strong> и <strong>поиск элемента</strong> в дереве. Сложность каждой из этих операций O(log n) в лучшем случае, и O(n) в худшем. Зависит от сбалансированности дерева.</p>
  <h3 id="sxNP">Добавление элементов (метод add).</h3>
  <p id="qDJq">Учитывая это, давайте представим, как можно построить такое дерево. Поскольку вначале дерево было пустым, первое добавленное значение — восьмерка — стало его корнем.</p>
  <p id="tr4b">Мы не знаем точно, в каком порядке добавлялись остальные значения, но можем представить один из возможных путей. Узлы добавляются методом Add, который принимает добавляемое значение.</p>
  <p id="FN6q">tree.Add(8);<br />tree.Add(4);<br />tree.Add(2);<br />tree.Add(3);<br />tree.Add(10);<br />tree.Add(6);<br />tree.Add(7);</p>
  <p id="s5fE">Рассмотрим подробнее первые шаги.</p>
  <p id="ew5v">В первую очередь добавляется 8. Это значение становится корнем дерева. Затем мы добавляем 4. Поскольку 4 меньше 8, мы кладем ее в левого ребенка, согласно правилу 2. Поскольку у узла с восьмеркой нет детей слева, 4 становится единственным левым ребенком.</p>
  <p id="TVU7">После этого мы добавляем 2. 2 меньше 8, поэтому идем налево. Так как слева уже есть значение, сравниваем его со вставляемым. 2 меньше 4, а у четверки нет детей слева, поэтому 2 становится левым ребенком 4.</p>
  <p id="WNAu">Затем мы добавляем тройку. Она идет левее 8 и 4. Но так как 3 больше, чем 2, она становится правым ребенком 2, согласно третьему правилу.</p>
  <p id="RFZX">Последовательное сравнение вставляемого значения с потенциальным родителем продолжается до тех пор, пока не будет найдено место для вставки, и повторяется для каждого вставляемого значения до тех пор, пока не будет построено все дерево целиком.</p>
  <h3 id="MCsV">Удаление элементов (метод remove).</h3>
  <p id="2g9M"><strong>Поведение:</strong> Удаляет первый узел с заданным значением.<br /><strong>Сложность: </strong>O(log n) в среднем; O(n) в худшем случае.<br />Удаление узла из дерева — одна из тех операций, которые кажутся простыми, но на самом деле таят в себе немало подводных камней.</p>
  <p id="o2TB">В целом, алгоритм удаления элемента выглядит так:<br />Найти узел, который надо удалить.<br />Удалить его.<br />Первый шаг достаточно простой. После того, как мы нашли узел, который необходимо удалить, у нас возможны три случая.</p>
  <p id="ELbs"><strong><em>Случай 1:</em></strong> У удаляемого узла нет правого ребенка.</p>
  <figure id="h5j6" class="m_original">
    <img src="https://img4.teletype.in/files/39/3c/393c72e5-0f0c-422c-99cd-9d72d06537c4.png" width="329" />
  </figure>
  <p id="gaO7">В этом случае мы просто перемещаем левого ребенка (при его наличии) на место удаляемого узла. В результате дерево будет выглядеть так:</p>
  <figure id="6zVu" class="m_original">
    <img src="https://img2.teletype.in/files/d9/61/d96148c4-5b81-4629-9af9-e3f381d0ae95.png" width="239" />
  </figure>
  <p id="VMeL"><strong><em>Случай 2:</em></strong> У удаляемого узла есть только правый ребенок, у которого, в свою очередь нет левого ребенка.</p>
  <figure id="jjqi" class="m_original">
    <img src="https://img4.teletype.in/files/bb/63/bb638082-e737-4f55-97e0-b106095f2366.png" width="328" />
  </figure>
  <p id="orWC">В этом случае нам надо переместить правого ребенка удаляемого узла (6) на его место. После удаления дерево будет выглядеть так:</p>
  <figure id="H6Jd" class="m_original">
    <img src="https://img1.teletype.in/files/cb/2c/cb2c05de-e80a-4c9f-9149-b5034e415173.png" width="313" />
  </figure>
  <p id="JzfO"><strong><em>Случай 3:</em></strong> У удаляемого узла есть первый ребенок, у которого есть левый ребенок.</p>
  <figure id="4ONd" class="m_original">
    <img src="https://img2.teletype.in/files/da/7e/da7ef12e-2f1f-4940-8a8f-c1c4cea07bff.png" width="317" />
  </figure>
  <p id="tQ6h">В этом случае место удаляемого узла занимает крайний левый ребенок правого ребенка удаляемого узла.<br />После удаления узла дерево будет выглядеть так:</p>
  <figure id="S1z8" class="m_original">
    <img src="https://img4.teletype.in/files/b8/95/b89508d7-1ddb-47b9-a716-3b0a6d71c380.png" width="311" />
  </figure>
  <h3 id="MRPa">Поиск значений (метод search).</h3>
  <p id="YKJh">Возвращает само значение, если такое значение содержится в дереве. В противном случает возвращает null.<br />Сложность: O(log n) в среднем; O(n) в худшем случае.<br />Метод проходит по дереву, выполняя в каждом узле следующие шаги:<br />Если текущий узел null, вернуть null.<br />Если значение текущего узла равно искомому, вернуть текущий узел.<br />Если искомое значение меньше значения текущего узла, установить левого ребенка текущим узлом и перейти к шагу 1.<br />В противном случае, установить правого ребенка текущим узлом и перейти к шагу 1.</p>
  <h3 id="qcsK">Количество узлов в дереве.</h3>
  <p id="v9QH">Для подсчета получения количества узлов в дереве проще всего добавить переменную, значение которой инкрементируется методом add и декрементируется методом remove.</p>
  <h2 id="qQ1A">3. Обход деревьев.</h2>
  <p id="Qwcm">Обходы дерева — это семейство алгоритмов, которые позволяют обработать каждый узел в определенном порядке. Для всех алгоритмов обхода ниже в качестве примера будет использоваться следующее дерево:</p>
  <figure id="wft3" class="m_original">
    <img src="https://img3.teletype.in/files/ef/58/ef58ac0c-7f8a-4d4b-aa87-7b949fee2706.png" width="438" />
  </figure>
  <h3 id="3FWW">Префиксный обход.</h3>
  <p id="xWyx"><strong>Сложность:</strong> O(n).<br />При префиксном обходе алгоритм получает значение текущего узла перед тем, как перейти сначала в левое поддерево, а затем в правое. Начиная от корня, сначала мы получим значение 4. Затем таким же образом обходятся левый ребенок и его дети, затем правый ребенок и все его дети.<br />Порядок обхода: 4, 2, 1, 3, 5, 7, 6, 8<br />Префиксный обход обычно применяется для копирования дерева с сохранением его структуры.</p>
  <h3 id="CdaK">Постфиксный обход.</h3>
  <p id="8idp"><strong>Сложность:</strong> O(n).<br />При постфиксном обходе мы посещаем левое поддерево, правое поддерево, а потом, после обхода всех детей, переходим к самому узлу.<br />Порядок обхода: 1, 3, 2, 6, 8, 7, 5, 4<br />Постфиксный обход часто используется для полного удаления дерева, так как в некоторых языках программирования необходимо убирать из памяти все узлы явно, или для удаления поддерева. Поскольку корень в данном случае обрабатывается последним, мы, таким образом, уменьшаем работу, необходимую для удаления узлов.</p>
  <h3 id="Rh2s">Инфиксный обход.</h3>
  <p id="dXBW"><strong>Сложность:</strong> O(n).<br />Инфиксный обход используется тогда, когда нам надо обойти дерево в порядке, соответствующем значениям узлов. В примере выше в дереве находятся числовые значения, поэтому мы обходим их от самого маленького до самого большого. То есть от левых поддеревьев к правым через корень.<br />Порядок обхода: 1, 2, 3, 4, 5, 6, 7, 8<br />В примере этот обход используется в методе print.</p>
  <hr />
  <h2 id="zzru">Приложение к лекции.</h2>
  <pre id="8rUD" data-lang="clike">using System;

namespace ConsoleApplication44
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            BinaryTree tree = new BinaryTree(15, null);
            tree.Add(25);
            tree.Add(23);
            tree.Add(29);
            tree.Add(17);
            tree.Add(31);
            tree.Add(5);
            tree.Remove(23);
            
            if (tree.Search(25) == null)
            {
                Console.WriteLine(&quot;Элемент не найден!&quot;);
            }
            else
            {
                Console.WriteLine(&quot;Элемент найден!&quot;);
            }
            
            tree.Print();
        }
    }

    public class BinaryTree
    {
        private BinaryTree _parent, _left, _right;
        private int _value;

        public BinaryTree(int value, BinaryTree parent)
        {
            _value = value;
            _parent = parent;
        }

        public void Add(int value)
        {
            if (value &lt; _value)
            {
                if (_left == null)
                {
                    _left = new BinaryTree(value, this);
                }
                else if (_left != null)
                {
                    _left.Add(value);
                }
            }
            else
            {
                if (_right == null)
                {
                    _right = new BinaryTree(value, this);
                }
                else if (_right != null)
                {
                    _right.Add(value);
                }
            }
        }

        private BinaryTree Search(BinaryTree tree, int value)
        {
            if (tree == null)
            {
                return null;
            }
            
            if (value == tree._value)
            {
                return tree;
            }

            if (value &gt; tree._value)
            {
                return Search(tree._right, value);
            }
            else
            {
                return Search(tree._left, value);
            }
        }

        public BinaryTree Search(int value)
        {
            return Search(this, value);
        }

        public bool Remove(int value)
        {
            //Проверяем, существует ли данный узел
            BinaryTree tree = Search(value);
            if (tree == null)
            {
                //Если узла не существует, вернем false
                return false;
            }

            BinaryTree curTree;
            //Если удаляем корень
            if (tree == this)
            {
                if (tree._right != null)
                {
                    curTree = tree._right;
                }
                else
                {
                    curTree = tree._left;
                }

                while (curTree._left != null)
                {
                    curTree = curTree._left;
                }

                int temp = curTree._value;
                Remove(temp);
                tree._value = temp;

                return true;
            }

            //Удаление листьев
            if (tree._left == null &amp;&amp; tree._right == null &amp;&amp; tree._parent != null)
            {
                if (tree == tree._parent._left)
                {
                    tree._parent._left = null;
                }
                else
                {
                    tree._parent._right = null;
                }

                return true;
            }

            //Удаление узла, имеющего левое поддерево, но не имеющее правого поддерева
            if (tree._left != null &amp;&amp; tree._right == null)
            {
                //Меняем родителя
                tree._left._parent = tree._parent;
                if (tree._parent != null &amp;&amp; tree == tree._parent._left)
                {
                    tree._parent._left = tree._left;
                }
                else if (tree._parent != null &amp;&amp; tree == tree._parent._right)
                {
                    tree._parent._right = tree._left;
                }

                return true;
            }

            //Удаление узла, имеющего правое поддерево, но не имеющее левого поддерева
            if (tree._left == null &amp;&amp; tree._right != null)
            {
                //Меняем родителя
                tree._right._parent = tree._parent;
                
                if (tree._parent != null &amp;&amp; tree == tree._parent._left)
                {
                    tree._parent._left = tree._right;
                }
                else if (tree._parent != null &amp;&amp; tree == tree._parent._right)
                {
                    tree._parent._right = tree._right;
                }

                return true;
            }

            //Удаляем узел, имеющий поддеревья с обеих сторон
            if (tree._right != null &amp;&amp; tree._left != null)
            {
                curTree = tree._right;

                while (curTree._left != null)
                {
                    curTree = curTree._left;
                }

                //Если самый левый элемент является первым потомком
                if (curTree._parent == tree)
                {
                    curTree._left = tree._left;
                    tree._left._parent = curTree;
                    curTree._parent = tree._parent;
                    
                    if (tree._parent != null &amp;&amp; tree == tree._parent._left)
                    {
                        tree._parent._left = curTree;
                    }
                    else if (tree._parent != null &amp;&amp; tree == tree._parent._right)
                    {
                        tree._parent._right = curTree;
                    }

                    return true;
                }
                //Если самый левый элемент НЕ является первым потомком
                else
                {
                    if (curTree._right != null)
                    {
                        curTree._right._parent = curTree._parent;
                    }

                    curTree._parent._left = curTree._right;
                    curTree._right = tree._right;
                    curTree._left = tree._left;
                    tree._left._parent = curTree;
                    tree._right._parent = curTree;
                    curTree._parent = tree._parent;
                    
                    if (tree._parent != null &amp;&amp; tree == tree._parent._left)
                    {
                        tree._parent._left = curTree;
                    }
                    else if (tree._parent != null &amp;&amp; tree == tree._parent._right)
                    {
                        tree._parent._right = curTree;
                    }

                    return true;
                }
            }

            return false;
        }

        private void Print(BinaryTree node)
        {
            if (node == null) return;
            
            Print(node._left);
            
            Console.Write(node + &quot; &quot;);
            
            if (node._right != null)
            {
                Print(node._right);
            }
        }

        public void Print()
        {
            Print(this);
            
            Console.WriteLine();
        }

        public override string ToString()
        {
            return _value.ToString();
        }
    }
}</pre>
  <hr />
  <h2 id="Y0Ss">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="8WPe">Мы рассмотрели еще одну структуру данных – двоичное или бинарное дерево. Основное достоинство этой структуры в том, что основные операции добавления, поиска и удаления элементов имеют в среднем логарифмическую сложность, а значит, могут ускорить работу огромного количества программ, использующих стандартные массивы.</p>
    <p id="ZCAO">А на следующем занятии мы убедимся в том, что одно дерево хорошо, а лес лучше! Но в лесу так легко заблудиться…</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/wm4MHsf3-Yt</guid><link>https://teletype.in/@gamedev_academy_feedback/wm4MHsf3-Yt?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/wm4MHsf3-Yt?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>17. Примеры задач на графы. Связность. Компоненты связности.</title><pubDate>Mon, 27 Sep 2021 06:36:36 GMT</pubDate><category>Алгоритмы и структуры данных</category><description><![CDATA[Давайте рассмотрим несколько задач, которые можно решить, изменяя рассмотренные нами поиск в ширину или глубину на графах.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="BzFg">1. Примеры задач на графы. Связность. Компоненты связности.</h2>
  <p id="mIfM">Давайте рассмотрим несколько задач, которые можно решить, изменяя рассмотренные нами поиск в ширину или глубину на графах.</p>
  <h2 id="1sNg">2. Компоненты связности.</h2>
  <p id="Rrqx">На предприятии достаточно сложная компьютерная сеть. В каждом подразделении стоит сетевой коммуникатор - свич и все компьютеры подразделения связаны между собой через него. Некоторые из свичей связаны кабелем и тогда компьютеры этих подразделений могут «общаться между собой». Вам предложили непыльную работу сетевого администратора, но для начала поручили соединить все компьютеры предприятия между собой. Какое минимальное количество сетевых кабелей между свичами вам надо проложить, чтобы все компьютеры «видели» друг друга?</p>
  <p id="2Gw7"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="xBey" data-lang="clike">int count = 0;
//Console.Write(&quot;Введите N=&quot;);
//int n = Convert.ToInt32(Console.ReadLine());
int n = 10;

int[,] graf = new int[n, n];

//Console.Write(&quot;Введите M=&quot;);
//int m = Convert.ToInt32(Console.ReadLine());
int m = 8;

/*
for (int i = 0; i &lt; m; i++)
{
    Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
    start = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    end = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    int size = Convert.ToInt32(Console.ReadLine());
    graf[start, end] = size;
    graf[end, start] = size;
}
*/

graf[0, 3] = 1;
graf[3, 0] = 1;
graf[0, 6] = 1;
graf[6, 0] = 1;
graf[3, 6] = 1;
graf[6, 3] = 1;
graf[1, 5] = 1;
graf[5, 1] = 1;
graf[5, 4] = 1;
graf[4, 5] = 1;
graf[7, 8] = 1;
graf[8, 7] = 1;
graf[7, 9] = 1;
graf[9, 7] = 1;
graf[8, 9] = 1;
graf[9, 8] = 1;

int[] path = new int[n];
for (int i = 0; i &lt; n; i++)
{
    path[i] = Int32.MaxValue;
}

for (int start = 0; start &lt; n; start++)
{
    if (path[start] == Int32.MaxValue)
    {
        Queue&lt;int&gt; list = new Queue&lt;int&gt;();
        path[start] = 0;
        list.Enqueue(start);
        while (list.Count &gt; 0)
        {
            int u = list.Dequeue();
            for (int i = 0; i &lt; n; i++)
            {
                if (graf[u, i] &gt; 0)
                {
                    if (path[u] + graf[u, i] &lt; path[i])
                    {
                        path[i] = path[u] + graf[u, i];
                        list.Enqueue(i);
                    }
                }
            }
        }

        for (int i = 0; i &lt; n; i++)
        {
            Console.WriteLine(i + &quot; - &quot; + path[i]);
        }

        Console.WriteLine();
        count++;
    }
}

for (int i = 0; i &lt; n; i++)
{
    Console.WriteLine(i + &quot; - &quot; + path[i]);
}

Console.WriteLine();

Console.WriteLine(count);</pre>
  <h2 id="MJOM">3. Связи решают все.</h2>
  <p id="B6X2">Вам очень надо для решения важного вопроса «выйти» на конкретного человека – Ивана Ивановича через солидных знакомых. У вас есть информация, кто с кем из определенного круга лиц поддерживает достаточно близкие отношения (в том числе и вы). Есть ли у вас шанс получить нужную рекомендацию?</p>
  <p id="Kv0M">Так как нам нужен любой путь, а не кратчайший, будем использовать поиск в глубину.</p>
  <p id="lztA"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="1wsi" data-lang="clike">private static bool[] _uses;
private static int[,] _graf;
private static int _n;
private static int _start = 0;
private static int _end = 0;

public static void Main(string[] args)
{
    //Console.Write(&quot;Введите N=&quot;);
    //_n = Convert.ToInt32(Console.ReadLine());
    _n = 10;

    _graf = new int[_n, _n];

    //Console.Write(&quot;Введите M=&quot;);
    //int m = Convert.ToInt32(Console.ReadLine());
    int m = 9;

    /*
    for (int i = 0; i &lt; m; i++)
    {
        Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
        _start = Convert.ToInt32(Console.ReadLine());
        Console.Write(&quot;Номер города, в который идет дорога: &quot;);
        _end = Convert.ToInt32(Console.ReadLine());
        Console.Write(&quot;Номер города, в который идет дорога: &quot;);
        int size = Convert.ToInt32(Console.ReadLine());
        _graf[_start, _end] = size;
        _graf[_end, _start] = size;
    }
    */

    _graf[0, 3] = 1;
    _graf[3, 0] = 1;
    _graf[0, 6] = 1;
    _graf[6, 0] = 1;
    _graf[3, 6] = 1;
    _graf[6, 3] = 1;
    _graf[3, 7] = 1;
    _graf[7, 3] = 1;
    _graf[7, 8] = 1;
    _graf[8, 7] = 1;
    _graf[9, 7] = 1;
    _graf[7, 9] = 1;
    _graf[9, 8] = 1;
    _graf[8, 9] = 1;
    _graf[1, 5] = 1;
    _graf[5, 1] = 1;
    _graf[4, 5] = 1;
    _graf[5, 4] = 1;

    //Console.Write(&quot;Введите X=&quot;);
    //_start = Convert.ToInt32(Console.ReadLine());
    _start = 0;

    //Console.Write(&quot;Введите Y=&quot;);
    //_end = Convert.ToInt32(Console.ReadLine());
    _end = 9;

    _uses = new bool[_n];

    Dfs(_start);

    if (!_uses[_end])
    {
        Console.WriteLine(&quot;Путь не найден!&quot;);
    }
}
static void Dfs(int v)
{
    if (_uses[v])  // Если мы здесь уже были, то тут больше делать нечего
    {
        return;
    }
    
    _uses[v] = true;   // Помечаем, что мы здесь были
    
    if (v == _end)   // Проверяем, конец ли
    {
        Console.WriteLine(&quot;Путь найден!&quot;);
        return;
    }
    
    for (int i = 0; i &lt; _n; i++)  // Для каждого ребра
    {
        if (_graf[v, i] &gt; 0)
        {
             Dfs(i);  // Запускаемся из соседа
        }
    }
}</pre>
  <h2 id="N5EJ">4. Путь</h2>
  <p id="46tJ">Пусть надо найти и вывести кратчайший путь из вершины Start в вершину End. </p>
  <p id="XZ30">Так как нам нужен не любой путь, а именно кратчайший, будем использовать поиск в ширину.</p>
  <p id="Oizi"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="K2EX" data-lang="clike">int start = 0;
int end = 0;
int current = 0;
//Console.Write(&quot;Введите N=&quot;);
//int n = Convert.ToInt32(Console.ReadLine());
int n = 7;

int[,] graf = new int[n, n];

//Console.Write(&quot;Введите M=&quot;);
//int m = Convert.ToInt32(Console.ReadLine());
int m = 10;

/*
for (int i = 0; i &lt; m; i++)
{
    Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
    start = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    end = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    int size = Convert.ToInt32(Console.ReadLine());
    graf[start, end] = size;
    graf[end, start] = size;
}
*/

graf[0, 3] = 25;
graf[3, 0] = 25;
graf[0, 5] = 23;
graf[5, 0] = 23;
graf[0, 6] = 27;
graf[6, 0] = 27;
graf[1, 3] = 22;
graf[3, 1] = 22;
graf[1, 4] = 29;
graf[4, 1] = 29;
graf[2, 4] = 24;
graf[4, 2] = 24;
graf[2, 6] = 23;
graf[6, 2] = 23;
graf[3, 4] = 54;
graf[4, 3] = 54;
graf[4, 5] = 59;
graf[5, 4] = 59;
graf[4, 6] = 49;
graf[6, 4] = 49;

//Console.Write(&quot;Введите X=&quot;);
//start = Convert.ToInt32(Console.ReadLine());
start = 0;

//Console.Write(&quot;Введите Y=&quot;);
//end = Convert.ToInt32(Console.ReadLine());
end = 4;

int[] path = new int[n];
for (int i = 0; i &lt; n; i++)
{
    path[i] = Int32.MaxValue;
}

Boolean[] uses = new Boolean[n];
int[] previous = new int[n];

path[start] = 0;
previous[start] = -1;

int usesCount = 0;
while (usesCount &lt; n)
{
    int min = Int32.MaxValue;
    current = 0;
    for (int i = 0; i &lt; n; i++)
    {
        if (!uses[i] &amp;&amp; path[i] &lt; min)
        {
            min = path[i];
            current = i;
        }
    }

    for (int i = 0; i &lt; n; i++)
    {
        if (graf[current, i] &gt; 0 &amp;&amp; !uses[i])
        {
            if (path[current] + graf[current, i] &lt; path[i])
            {
                path[i] = path[current] + graf[current, i];
                previous[i] = current;
            }
        }
    }

    uses[current] = true;
    usesCount++;
}

for (int i = 0; i &lt; n; i++)
{
    Console.WriteLine(i + &quot; - &quot; + path[i] + &quot; &quot; + previous[i]);
}

if (path[end] != Int32.MaxValue)
{
    current = end;
    LinkedList&lt;int&gt; move = new LinkedList&lt;int&gt;();
    while (current != -1)
    {
        move.AddFirst(current);
        current = previous[current];
    }

    LinkedListNode&lt;int&gt; node;
    node = move.First;
    while (node != null)
    {
        Console.Write(node.Value + &quot; &quot;);
        node = node.Next;
    }

    Console.WriteLine(&quot;Длина пути: &quot; + path[end]);
}
else
{
    Console.WriteLine(&quot;Нет пути:!&quot;);
}</pre>
  <hr />
  <h2 id="mH5a">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="LR75">Мы рассмотрели несколько примеров, которые можно решать стандартными алгоритмами поиска в ширину и глубину на графах с небольшими изменениями. На самом деле таких задач огромное количество.</p>
    <p id="FtYp">А на следующем занятии по завету для настоящих мужчин будем строить дом, сажать дерево и растить сына… Ой нет, все сразу не успеем. Поэтому как настоящие мужчины (и леди, кстати, тоже) будем сажать деревья! А так как здесь собрались будущие программисты, то деревья будут в первую очередь двоичные…</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/0dyt5XWa5G2</guid><link>https://teletype.in/@gamedev_academy_feedback/0dyt5XWa5G2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/0dyt5XWa5G2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>16. Поиск в глубину (DFS).</title><pubDate>Mon, 27 Sep 2021 06:31:44 GMT</pubDate><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img4.teletype.in/files/f1/a1/f1a178f7-019d-46fd-81d8-db7082d3271b.png"></img>Сегодня мы продолжим разбирать алгоритмы на графах. Еще один очень распространенный алгоритм – поиск в глубину (DFS). Если объяснять просто, то в поиске в ширину, если помните, у нас из каждой вершины как бы выбегала толпа народа, которая потом снова делилась на большие толпы, а в поиске в глубину бегает только один неутомимый товарищ, который должен успеть оббежать все вершины в поисках нужной и очень быстро.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="v8Tf">1. Поиск в глубину (DFS).</h2>
  <p id="PXcC">Сегодня мы продолжим разбирать алгоритмы на графах. Еще один очень распространенный алгоритм – поиск в глубину (DFS). Если объяснять просто, то в поиске в ширину, если помните, у нас из каждой вершины как бы выбегала толпа народа, которая потом снова делилась на большие толпы, а в поиске в глубину бегает только один неутомимый товарищ, который должен успеть оббежать все вершины в поисках нужной и очень быстро.</p>
  <p id="VQ7m">На первый взгляд кажется, что поиск в ширину BFS гораздо быстрее, ведь толпа может бежать сразу в нескольких направлениях, но на самом деле для большинства задач разница не очень большая, так как перед тем, как отправить толпы в нужных направлениях необходимо «остановиться и подумать» (даже в алгоритме Дейкстры найти вершину с минимальным весом), а в поиске в глубину DFS, практически не думая, сразу бежим в свободную (не посещенную ранее) вершину с минимальным номером.</p>
  <p id="UwQD">При этом алгоритм поиска в глубину быстро находит путь, который не обязательно является кратчайшим. Он очень прост и хорошо подходит если надо проверить наличие пути (найти любой путь). Если же нужен кратчайший путь – применяется поиск в ширину, а лучше алгоритм Дейкстры.</p>
  <p id="9hsi">На каждом шаге поиска в глубину алгоритм выбирает новую вершину, смежную с текущей и продолжает поиск уже из нее. Такое «углубление» продолжается до тех пор, пока не будет найдена вершина, смежная с конечной или алгоритм не зайдет в тупик, в этом случае он возвращается к предыдущей вершине и продолжает поиск до тех пор, пока все вершины не просмотрены.</p>
  <p id="eHuc">Существуют два основных метода поиска в глубину: рекурсивный алгоритм и алгоритм с использованием стека. Чаще используется рекурсивный алгоритм.</p>
  <h2 id="8ag7">2. Алгоритм.</h2>
  <p id="LUIz">Пусть надо найти путь из вершины S в вершину F. <br />Поиск в глубину (DFS) работает следующим образом:<br />Если номера вершин S и F совпали – путь найден;<br />Если из вершины S есть дуга вершину X и вершина X не была посещена ранее – то найти путь из X в F рекурсивно.<br />Второй шаг выполняется для всех вершин, в которые можно перейти из S.<br />Если все дуги из S обработаны и ни по одной из них не был найден путь – значит такого пути нет.</p>
  <p id="GO7R">Давайте разберем пример на основе задачи про лабиринт.</p>
  <p id="iOmt"><strong><em>Постановка задачи: </em></strong>Конечно, надо найти выход в лабиринте. Но работать над «сырым» лабиринтом достаточно неудобно (граф будет слишком большим и непонятным), поэтому мы разобьем его на «комнаты», соединенные между собой перегородками. Примерно так, как показано на рисунке справа.</p>
  <figure id="prKR" class="m_original">
    <img src="https://img4.teletype.in/files/f1/a1/f1a178f7-019d-46fd-81d8-db7082d3271b.png" width="205" />
  </figure>
  <p id="4HXr">Теперь задача приняла такой вид: есть множество комнат, между некоторыми из них можно перемещаться. Требуется найти путь из комнаты A в комнату B.</p>
  <p id="dIWc">В теории графов «комнаты» называются вершинами, «переходы» между ними — ребрами. Комната А — начальная вершина, комната В — конечная.</p>
  <p id="CQhR">Граф можно перерисовать в несколько более распространенном в теории графов виде:</p>
  <figure id="JTlF" class="m_original">
    <img src="https://img4.teletype.in/files/70/12/7012219d-403a-4889-8033-069e6cde5f34.png" width="430" />
  </figure>
  <p id="WWXD">Здесь овалы — это вершины (или комнаты), линии — ребра (или переходы между ними). Вершина 1 — начальная, вершина 12 — конечная.</p>
  <p id="wtci">И как же мы будем решать эту задачу?</p>
  <p id="8DKk">Решение, которое сразу приходит на ум — помечать каждую вершину при входе в нее, и никогда не ходить в уже помеченные вершины — это и есть обход в глубину:</p>
  <p id="acrq"><strong>DFS (рекурсивный алгоритм).</strong></p>
  <p id="ehyG"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="5BRs" data-lang="clike">internal class Program
{
    private static bool[] _uses;
    private static int[,] _graf;
    private static int _n;
    private static int _start = 0;
    private static int _end = 0;
    
    public static void Main(string[] args)
    {
        //Console.Write(&quot;Введите N=&quot;);
        //_n = Convert.ToInt32(Console.ReadLine());
        _n = 13;

        _graf = new int[_n, _n];

        //Console.Write(&quot;Введите M=&quot;);
        //int m = Convert.ToInt32(Console.ReadLine());
        int m = 11;

        /*
        for (int i = 0; i &lt; m; i++)
        {
            Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
            _start = Convert.ToInt32(Console.ReadLine());
            Console.Write(&quot;Номер города, в который идет дорога: &quot;);
            _end = Convert.ToInt32(Console.ReadLine());
            Console.Write(&quot;Номер города, в который идет дорога: &quot;);
            int size = Convert.ToInt32(Console.ReadLine());
            _graf[_start, _end] = size;
            _graf[_end, _start] = size;
        }
        */

        _graf[1, 2] = 1;
        _graf[2, 1] = 1;
        _graf[2, 3] = 1;
        _graf[3, 2] = 1;
        _graf[3, 4] = 1;
        _graf[4, 3] = 1;
        _graf[4, 5] = 1;
        _graf[5, 4] = 1;
        _graf[5, 6] = 1;
        _graf[6, 5] = 1;
        _graf[6, 7] = 1;
        _graf[7, 6] = 1;
        _graf[4, 8] = 1;
        _graf[8, 4] = 1;
        _graf[8, 9] = 1;
        _graf[9, 8] = 1;
        _graf[9, 10] = 1;
        _graf[10, 9] = 1;
        _graf[10, 11] = 1;
        _graf[11, 10] = 1;
        _graf[11, 12] = 1;
        _graf[12, 11] = 1;

        //Console.Write(&quot;Введите X=&quot;);
        //_start = Convert.ToInt32(Console.ReadLine());
        _start = 1;

        //Console.Write(&quot;Введите Y=&quot;);
        //_end = Convert.ToInt32(Console.ReadLine());
        _end = 12;

        _uses = new bool[_n];

        Dfs(_start);

    }
    static void Dfs(int v)
    {
        if (_uses[v])  // Если мы здесь уже были, то тут больше делать нечего
        {
            return;
        }
        
        _uses[v] = true;   // Помечаем, что мы здесь были
        
        if (v == _end)   // Проверяем, конец ли
        {
            Console.WriteLine(&quot;Путь найден!&quot;);
            return;
        }
        
        for (int i = 0; i &lt; _n; i++)  // Для каждого ребра
        {
            if (_graf[v, i] &gt; 0)
            {
                 Dfs(i);  // Запускаемся из соседа
            }
        }
    }
}</pre>
  <p id="3tCf"><strong>Сколько ресурсов требуется алгоритму?</strong></p>
  <p id="eML6">Алгоритм просматривает каждое ребро один раз, и выполняет для каждой вершины константное число действий. Обозначая число вершин как V, а ребер — как E, получаем, что время работы — O(V+E). Кстати, такая же сложность и у алгоритма поиска в ширину.</p>
  <p id="uEFL">Глубина рекурсии, в которой мы находимся, не превышает общего числа вызовов функции DFS — числа вершин. То есть, объем памяти, необходимый для работы алгоритма, равен O(V).</p>
  <p id="IhNI"><strong>Нерекурсивный DFS.</strong></p>
  <p id="LuID">Любой рекурсивный алгоритм можно переписать в нерекурсивном виде. Вот код для DFS с использованием стека:</p>
  <p id="9pxn"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="iY7n" data-lang="clike">…
bool[] uses = new bool[n];

Stack&lt;int&gt; s = new Stack&lt;int&gt;();
s.Push(start);
while (s.Count &gt; 0)
{
    int v = s.Pop();
    for (int i = 0; i &lt; n; i++)
    {
        if (graf[v, i] &gt; 0 &amp;&amp; !uses[i])
        {
            s.Push(i);
            uses[i] = true;
            if (i == end)
            {
                s.Clear();
                Console.WriteLine(&quot;Путь найден!&quot;);
                break;
            }
        }
    }
}</pre>
  <p id="eRAy"><strong><em>Задание:</em></strong> Попробуйте самостоятельно написать алгоритм поиска в глубину с выводом в консоль номеров вершин в порядке их посещения.</p>
  <hr />
  <h2 id="w5UF">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="MDOT">Поиск в глубину и поиск в ширину используются для обхода графа.<br />DFS двигается по граням туда и обратно, а BFS распространяется по соседям в поисках цели.<br />DFS использует стек, а BFS — очередь.<br />Время выполнения обоих составляет O(V + E), а пространственная сложность — O(V).<br />Данные алгоритмы имеют разную философию, но одинаково важны для работы с графами.<br />Например, если надо найти какой-нибудь путь, то лучше использовать поиск в глубину, а если минимальный – поиск в ширину.</p>
    <p id="kS1G">А на следующем занятии будем строить пути, в том числе для связей на стороне, точнее, на всех сторонах…</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/PjyKhAl7-nN</guid><link>https://teletype.in/@gamedev_academy_feedback/PjyKhAl7-nN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/PjyKhAl7-nN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>15. Теория графов. Алгоритм Дейкстры.</title><pubDate>Mon, 27 Sep 2021 06:22:55 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/95/ba/95ba7e6d-b3d2-4a16-9396-ab72bf95e693.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img4.teletype.in/files/34/96/34962e3e-157c-4935-9e9f-ce95fbfc353f.png"></img>Сегодня мы продолжим решать задачу из предыдущего занятия. Поиск в ширину отлично работает, но его можно оптимизировать. Вспомните, мы пытались двигаться из всех вершин в очереди по всем направлениям, улучшая минимальные пути и записывая одни и те же вершины в очередь по несколько раз. Алгоритм Дейкстры позволяет упростить поиск в ширину, выбирая из всех возможных направлений самое подходящее. Для алгоритма Дейкстры есть одно ограничение: вес ребер не должен быть отрицательным.]]></description><content:encoded><![CDATA[
  <hr />
  <p id="snik">Сегодня мы продолжим решать задачу из предыдущего занятия. Поиск в ширину отлично работает, но его можно оптимизировать. Вспомните, мы пытались двигаться из всех вершин в очереди по всем направлениям, улучшая минимальные пути и записывая одни и те же вершины в очередь по несколько раз. Алгоритм Дейкстры позволяет упростить поиск в ширину, выбирая из всех возможных направлений самое подходящее. Для алгоритма Дейкстры есть одно ограничение: вес ребер не должен быть отрицательным.</p>
  <p id="bqiy"><strong>Алгоритм Дейкстры</strong> — алгоритм на графах, изобретённый нидерландским ученым Э. Дейкстрой в 1959 году. Находит кратчайшее расстояние от одной из вершин графа до всех остальных. </p>
  <p id="YvIa">Пусть требуется найти кратчайшие расстояния от 1-й вершины до всех остальных.</p>
  <p id="v5oL">Кружками обозначены вершины, линиями – ребра графа. В кружках обозначены номера вершин, над ребрами обозначен их вес – длина пути. Рядом с каждой вершиной красным обозначена метка – длина кратчайшего пути в эту вершину из вершины 1.</p>
  <figure id="L66E" class="m_original">
    <img src="https://img4.teletype.in/files/34/96/34962e3e-157c-4935-9e9f-ce95fbfc353f.png" width="391" />
  </figure>
  <p id="bZnz">Метка самой вершины 1 полагается равной 0, метки остальных вершин – недостижимо большое число (в идеале — бесконечность). Это отражает то, что расстояния от вершины 1 до других вершин пока неизвестны. Все вершины графа помечаются как не посещенные. </p>
  <figure id="HdEy" class="m_original">
    <img src="https://img4.teletype.in/files/75/23/7523fbaa-9218-45de-aac5-186310340d2a.png" />
  </figure>
  <p id="Obi3"><strong>Первый шаг.</strong></p>
  <p id="Cuks">Минимальную метку имеет вершина 1. Её соседями являются вершины 2, 3 и 6. Обходим соседей вершины по очереди. </p>
  <p id="EKgu">Первый сосед вершины 1 – вершина 2, потому что длина пути до неё минимальна. Длина пути в неё через вершину 1 равна сумме кратчайшего расстояния до вершины 1 (значению её метки) и длины ребра, идущего из 1-й во 2-ю, то есть 0 + 7 = 7. Это меньше текущей метки вершины 2 (10000), поэтому новая метка 2-й вершины равна 7.</p>
  <figure id="WKp7" class="m_original">
    <img src="https://img1.teletype.in/files/82/e6/82e64187-e9b7-4b61-b89d-c9b83c835503.png" width="414" />
  </figure>
  <p id="VhXL">Аналогично находим длины пути для всех других соседей (вершины 3 и 6).</p>
  <p id="kFyj">Все соседи вершины 1 проверены. Текущее минимальное расстояние до вершины 1 считается окончательным и пересмотру не подлежит. Вершина 1 отмечается как посещенная.</p>
  <p id="PF2M"><strong>Второй шаг.</strong></p>
  <p id="npWq">Шаг 1 алгоритма повторяется. Снова находим «ближайшую» из не посещённых вершин. Это вершина с минимальной красной меткой - вершина 2 с меткой 7.</p>
  <p id="maCW">Снова пытаемся уменьшить метки соседей выбранной вершины, пытаясь пройти в них через 2-ю вершину. Соседями вершины 2 являются вершины 1, 3 и 4.</p>
  <p id="ib3L">Вершина 1 уже посещена. Следующий сосед вершины 2 — вершина 3, так как имеет минимальную метку из вершин, отмеченных как не посещённые. Если идти в неё через 2, то длина такого пути будет равна 17 (7 + 10 = 17). Но текущая метка третьей вершины равна 9, а 9 &lt; 17, поэтому метка не меняется.</p>
  <figure id="udS1" class="m_original">
    <img src="https://img4.teletype.in/files/bb/77/bb773c1e-5527-4861-ac9f-cabaaab99d72.png" width="373" />
  </figure>
  <p id="5Z1t">Ещё один сосед вершины 2 — вершина 4. Если идти в неё через 2-ю, то длина такого пути будет равна 22 (7 + 15 = 22). Поскольку 22&lt;10000, устанавливаем метку вершины 4 равной 22.</p>
  <p id="52hy">Все соседи вершины 2 просмотрены, помечаем её как посещенную.</p>
  <p id="gwdc"><strong>Третий шаг.</strong></p>
  <p id="tzng">Повторяем шаг алгоритма, выбрав вершину 3. После её «обработки» получим следующие результаты.</p>
  <figure id="JbmY" class="m_original">
    <img src="https://img4.teletype.in/files/f2/4a/f24afcd2-e6a2-4e52-a8e9-8d528e923cf5.png" width="386" />
  </figure>
  <p id="hl8i"><strong>Четвертый шаг.</strong></p>
  <figure id="PxP2" class="m_original">
    <img src="https://img3.teletype.in/files/64/bc/64bcaac9-34fe-4d18-b935-80015ad34e03.png" width="383" />
  </figure>
  <p id="hKSU"><strong>Пятый шаг.</strong></p>
  <figure id="EgE8" class="m_original">
    <img src="https://img1.teletype.in/files/89/12/891286ad-af15-4905-89af-3666e2736acb.png" width="379" />
  </figure>
  <p id="mYma"><strong>Шестой шаг.</strong></p>
  <figure id="aeIu" class="m_original">
    <img src="https://img4.teletype.in/files/b4/f6/b4f6f426-0950-4598-82f2-35be5aa1dfdc.png" width="384" />
  </figure>
  <p id="ICok">Таким образом, мы получили кратчайшие расстояния от вершины 1 до всех остальных вершин.</p>
  <p id="o9Rq"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="a33a" data-lang="clike">int start = 0;
int end = 0;
Console.Write(&quot;Введите N=&quot;);
int n = Convert.ToInt32(Console.ReadLine());

int[,] graf = new int[n, n];

Console.Write(&quot;Введите M=&quot;);
int m = Convert.ToInt32(Console.ReadLine());

for (int i = 0; i &lt; m; i++)
{
    Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
    start = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    end = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    int size = Convert.ToInt32(Console.ReadLine());
    graf[start, end] = size;
    graf[end, start] = size;
}

Console.Write(&quot;Введите X=&quot;);
start = Convert.ToInt32(Console.ReadLine());

Console.Write(&quot;Введите Y=&quot;);
end = Convert.ToInt32(Console.ReadLine());

int[] path = new int[n];
for (int i = 0; i &lt; n; i++)
{
    path[i] = Int32.MaxValue;
}

Boolean[] uses = new Boolean[n];

path[start] = 0;
int usesCount = 0;
while (usesCount &lt; n)
{
    int min = Int32.MaxValue;
    int current=0;
    for (int i = 0; i &lt; n; i++)
    {
        if (!uses[i] &amp;&amp; path[i] &lt; min)
        {
            min = path[i];
            current = i;
        }
    }
    
    for (int i = 0; i &lt; n; i++)
    {
        if (graf[current, i] &gt; 0 &amp;&amp; !uses[i])
        {
            if (path[current] + graf[current, i] &lt; path[i])
            {
                path[i] = path[current] + graf[current, i];
            }
        }
    }

    uses[current] = true;
    usesCount++;
}

for (int i = 0; i &lt; n; i++)
{
    Console.WriteLine(i + &quot; - &quot; + path[i]);
}

Console.WriteLine(&quot;Ответ: &quot; + path[end]);</pre>
  <p id="3ILC">Алгоритм Дейкстры можно значительно ускорить, если оптимизировать выбор вершины с минимальной меткой из не посещенных. Это можно сделать, используя, например, двоичную кучу. Но об этом мы с вами поговорим немного позже.</p>
  <p id="c1XA"><strong><em>Задание: </em></strong>Попробуйте самостоятельно реализовать алгоритм Дейкстры для ориентированного графа (каждое ребро задает движение только в одном направлении: от первой указанной вершины до второй).</p>
  <hr />
  <h2 id="lLZU">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="xAmd">Сегодня мы улучшили поиск в ширину с помощью алгоритма Дейкстры.</p>
    <p id="lxWA">А на следующем занятии поймем, что в теории графов толпой бегать по ребрам, конечно, веселее, но часто «и один в поле, то есть в графе, воин!»</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/eiysH_3QHqG</guid><link>https://teletype.in/@gamedev_academy_feedback/eiysH_3QHqG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/eiysH_3QHqG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>14. Теория графов. Поиск в ширину (BFS).</title><pubDate>Mon, 27 Sep 2021 06:07:49 GMT</pubDate><category>Алгоритмы и структуры данных</category><description><![CDATA[Сегодня все занятие мы посвятим решению одной задачи. Точнее, мы решим вроде бы только одну задачу, а потом поймем, что это же решение можно применить (практически без изменений) для решения сотен и даже тысяч очень актуальных и важных алгоритмических задач из реальной жизни.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="lmsQ">1. Теория графов. Поиск в ширину (BFS).</h2>
  <p id="m8Ws">Сегодня все занятие мы посвятим решению одной задачи. Точнее, мы решим вроде бы только одну задачу, а потом поймем, что это же решение можно применить (практически без изменений) для решения сотен и даже тысяч очень актуальных и важных алгоритмических задач из реальной жизни.</p>
  <p id="pWh2"><strong><em>Условие задачи: </em></strong>Есть N городов. Некоторые из этих городов могут быть попарно связаны друг с другом дорогами. Всего дорог М. Каждая дорога связывает два города и имеет длину (в километрах). Необходимо ответить на вопрос, можно ли проехать по имеющимся дорогам из города X в город Y, и, если да, то какое минимальное количество километров между городами надо проехать.</p>
  <p id="Qx4p"><strong><em>Исходные данные:<br /></em></strong>Сначала вводится N – количество городов.<br />Затем М – количество дорог.<br />Далее для каждой дороги вводится номер города, из которого она идет, номер города, в который она идет, и расстояние в километрах. Города нумеруются, начиная с 0.<br />Потом вводится Х – номер города, из которого планируется начать движение.<br />И Y – номер города, в котором планируется закончить движение.</p>
  <p id="HQD5">Выводится текст «Дороги нет!», если попасть из города Х в город Y по имеющимся дорогам нельзя или число – минимальное количество километров, которые надо проехать между городами, чтобы попасть из горда Х в город Y.</p>
  <p id="y4rb">Мы будем использовать один из основных алгоритмов на графах - <strong>поиск в ширину.</strong></p>
  <p id="h6wF">В результате поиска в ширину находится путь кратчайшей длины в графе, т.е. путь, содержащий наименьший суммарный вес рёбер.</p>
  <h2 id="kvC4">2. Поиск в ширину (BFS). Описание алгоритма.</h2>
  <p id="xsoh">По сути поиск в ширину в графе очень похож на решение задачи «принц и принцесса», которую мы с вами разбирали раньше. Но, если принц мог двигаться всегда вправо, влево, вниз или вверх на свободные клетки, то в графе путь из каждой вершины задается ребрами (кстати, один из способов решения задачи «принц и принцесса» и состоит в том, чтобы представить лабиринт в виде графа и решать его поиском в ширину).</p>
  <p id="NHA6">На вход алгоритма подаётся заданный граф (невзвешенный), и номер стартовой вершины Х. Граф может быть как ориентированным, так и неориентированным, для алгоритма это не важно.</p>
  <p id="ApL7">Сам алгоритм можно понимать, как процесс &quot;поджигания&quot; графа: на нулевом шаге поджигаем только вершину Х. На каждом следующем шаге огонь с каждой уже горящей вершины перекидывается на всех её соседей; то есть за одну итерацию алгоритма происходит расширение &quot;кольца огня&quot; в ширину на единицу (отсюда и название алгоритма).</p>
  <p id="8pkJ">Более строго это можно представить следующим образом. Создадим очередь, в которую будут помещаться горящие вершины, а также заведём булевский массив used[], в котором для каждой вершины будем отмечать, горит она уже или нет (или иными словами, была ли она посещена).</p>
  <p id="MkQ3">Изначально в очередь помещается только вершина Х, и used[Х]=true, а для всех остальных вершин used[i]=false. Затем алгоритм представляет собой цикл: пока очередь не пуста, достать из её головы одну вершину, просмотреть все рёбра, исходящие из этой вершины, и если какие-то из просмотренных вершин ещё не горят, то поджечь их и поместить в конец очереди.</p>
  <p id="VliU">В итоге, когда очередь опустеет, обход в ширину обойдёт все достижимые из Х вершины, причём до каждой дойдёт кратчайшим путём. </p>
  <p id="wK2K">Для взвешенного графа алгоритм немного усложняется. Вместо булевского массива used[] заведем массив длин путей path[], элементам которого изначально присвоим бесконечность. Длине пути в исходную вершину присвоим 0: path[Х]=0. Для расчета кратчайших путей придется постоянно проверять, можем ли мы попасть в очередную вершину за более короткий путь и, если да, обновлять значение минимального пути и добавлять эту вершину в очередь еще раз, чтобы заново пересчитать минимальные пути.</p>
  <p id="BaYL">В итоге мы будем иметь длины кратчайших путей из указанной вершины Х во все остальные вершины графа (в том числе в вершину Y). Если длина пути в конкретную вершину осталась равна бесконечности, значит в эту вершину попасть нельзя.</p>
  <p id="yZgO">Приступим к реализации.</p>
  <p id="TV03">Граф будем хранить с помощью матрицы смежности (у нас достаточно много ребер).</p>
  <p id="mgRT"><strong><em>Фрагмент кода:</em></strong></p>
  <pre id="7LCB" data-lang="clike">int start = 0;
int end = 0;
Console.Write(&quot;Введите N=&quot;);
int n = Convert.ToInt32(Console.ReadLine());

int[,] graf = new int[n, n];

Console.Write(&quot;Введите M=&quot;);
int m = Convert.ToInt32(Console.ReadLine());

for (int i = 0; i &lt; m; i++)
{
    Console.Write(&quot;Номер города, из которого идет дорога: &quot;);
    start = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Номер города, в который идет дорога: &quot;);
    end = Convert.ToInt32(Console.ReadLine());
    Console.Write(&quot;Длина дороги: &quot;);
    int size = Convert.ToInt32(Console.ReadLine());
    graf[start, end] = size;
    graf[end, start] = size;
}

Console.Write(&quot;Введите X=&quot;);
start = Convert.ToInt32(Console.ReadLine());

Console.Write(&quot;Введите Y=&quot;);
end = Convert.ToInt32(Console.ReadLine());

int[] path = new int[n];
for (int i = 0; i &lt; n; i++)
{
    path[i] = Int32.MaxValue;
}

Queue&lt;int&gt; list = new Queue&lt;int&gt;();
path[start] = 0;
list.Enqueue(start);
while (list.Count &gt; 0)
{
    int u = list.Dequeue();
    for (int i = 0; i &lt; n; i++)
    {
        if (graf[u, i] &gt; 0)
        {
            if (path[u] + graf[u, i] &lt; path[i])
            {
                path[i] = path[u] + graf[u, i];
                list.Enqueue(i);
            }
        }
    }
}

if (path[end] != Int32.MaxValue)
{
    Console.WriteLine(&quot;Ответ: &quot;+path[end]); 
}
else
{
    Console.WriteLine(&quot;Дороги нет!&quot;); 
}</pre>
  <p id="27wZ"><strong><em>Задание*:</em></strong> Попробуйте самостоятельно вывести в консоль кратчайший маршрут движения от города Х в город Y (список номеров городов, через которые надо проехать).</p>
  <p id="b28F">Этот код можно применять при решении огромного количества задач: поиск пути, расчет минимального времени, логистика перевозок, оптимизация работы автоматических линий в цехах предприятий и многое другое.</p>
  <hr />
  <h2 id="r0vq">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="4eId">Сегодня мы познакомились с самым базовым из алгоритмов теории графов – поиском в ширину, который может применяться при решении огромного количества задач на графы.</p>
    <p id="5GyX">А на следующем занятии поймем, что ни один поиск в ширину не может оправдать походов «налево»! Только вперед! И только в правильном направлении!</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/waq9yvgPpw_</guid><link>https://teletype.in/@gamedev_academy_feedback/waq9yvgPpw_?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/waq9yvgPpw_?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>13. Теория графов. Основные понятия теории графов. Способы представления графа в памяти компьютера.</title><pubDate>Sun, 12 Sep 2021 18:55:26 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0d/ac/0dacbcef-494c-4d28-90d2-c5493f4bf217.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[<img src="https://img3.teletype.in/files/e9/3b/e93b991a-2c16-4485-9a33-16a45966707e.png"></img>Сегодня мы начинаем говорить о теории графов. Это не структура данных и не метод решения задача. Графы – это способ представления и обработки данных в виде не просто множества объектов заданного класса, но и с учетом связей между ними.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="vLO6">1. Теория графов. </h2>
  <p id="pmzJ">Сегодня мы начинаем говорить о теории графов. Это не структура данных и не метод решения задача. Графы – это способ представления и обработки данных в виде не просто множества объектов заданного класса, но и с учетом связей между ними.</p>
  <p id="8nhM"><strong>Теория графов </strong>- один из обширнейших разделов дискретной математики, широко применяется в решении экономических и управленческих задач, в программировании, химии, конструировании и изучении электрических цепей, коммуникации, психологии, социологии, лингвистике, других областях знаний. <strong>Теория графов</strong> систематически и последовательно изучает свойства графов, о которых можно сказать, что они состоят из множеств точек и множеств линий, отображающих связи между этими точками. Основателем теории графов считается Леонард Эйлер (1707-1882), решивший в 1736 году известную в то время задачу о кёнигсбергских мостах.</p>
  <p id="Ohb0"><strong>Графы строят</strong> для того, чтобы отобразить отношения на <a href="https://function-x.ru/sets1.html" target="_blank">множествах</a>. Пусть, например, множество A = {a1, a2, ... an} - множество людей, а каждый элемент будет отображён в виде точки. Множество B = {b1, b2, ... bm} - множество связок (прямых, дуг, отрезков - пока не важно). На множестве A задано отношение знакомства между людьми из этого множества. <strong>Строим граф</strong> из точек и связок. Связки будут связывать пары людей, знакомых между собой. Естественно, число знакомых у одних людей может отличаться от числа знакомых у других людей, а некоторые вполне могут и не быть ни с кем знакомы (такие элементы будут точками, не соединёнными ни с одной другой). Вот и получился граф!</p>
  <p id="b7An">То, что мы сначала назвали &quot;точками&quot;, следует называть <strong>вершинами графа</strong>, а то, что называли &quot;связками&quot; - <strong>рёбрами графа.</strong></p>
  <figure id="jYLf" class="m_column">
    <img src="https://img3.teletype.in/files/e9/3b/e93b991a-2c16-4485-9a33-16a45966707e.png" width="469" />
  </figure>
  <p id="zTWB">Теория графов не учитывает конкретную природу множеств A и B. Существует большое количество самых разных конкретных задач, при решении которых можно временно забыть о специфическом содержании множеств и их элементов. Эта специфика никак не сказывается на ходе решения задачи, независимо от её трудности! Например, при решении вопроса о том, можно ли из точки a добраться до точки b, двигаясь только по соединяющим точки линиям, неважно, имеем ли мы дело с людьми, городами, числами и т.д. Но, когда задача решена, мы получаем решение, верное для любого содержания, которое было смоделировано в виде графа. Не удивительно поэтому, что теория графов - один из самых востребованных инструментов при создании искусственного интеллекта: ведь искусственный интеллект может обсудить с собеседником и вопросы любви, и вопросы музыки или спорта, и вопросы решения различных задач, причем делает это без всякого перехода (переключения), без которого в подобных случаях не обойтись человеку.</p>
  <p id="ftXO">А теперь строгие математические определения графа.</p>
  <h2 id="zAB4">2. Основные понятия теории графов.</h2>
  <p id="83tc"><strong>Графом называется </strong>система объектов произвольной природы (вершин) и связок (рёбер), соединяющих некоторые пары этих объектов.</p>
  <p id="Irfu">Пусть V – (непустое) множество вершин, элементы v∈V – вершины. Граф G = G(V) с множеством вершин V есть некоторое семейство пар вида: e = (a, b), где a,b∈V, указывающих, какие вершины остаются соединёнными. Каждая пара e = (a, b) - ребро графа. Множество U - множество рёбер e графа. Вершины a и b – концевые точки ребра e.</p>
  <p id="cRbs"><strong>Графы как структура данных.</strong> Широким применением теории графов в компьютерных науках и информационных технологиях обусловлено добавлением к вышеизложенным определениям понятия графа как структуры данных. В компьютерных науках и информационных технологиях граф определяется как нелинейная структура данных. Что же тогда - линейная структура данных и чем от них отличаются графы? Линейные структуры данных характеризуются тем, что связывают элементы отношениями типа &quot;простого соседства&quot;. Линейными структурами данных являются, например, массивы, таблицы, списки, очереди, стеки, строки. В противоположность им нелинейные структуры данных - такие, в которых элементы располагаются на различных уровнях иерархии и подразделяются на три вида: исходные, порождённые и подобные. Итак, граф - нелинейная структура данных.</p>
  <p id="Dmzw">Слово граф греческого происхождения, от слов &quot;пишу&quot;, &quot;описываю&quot;. То есть, любой граф описывает отношения. И наоборот: любое отношение можно описать в виде графа.</p>
  <h3 id="Lmij">Ориентированные и неориентированные графы.</h3>
  <p id="xGru">Графы, в которых все рёбра являются <a href="https://function-x.ru/graphs5.html" target="_blank">звеньями</a> (порядок двух концов ребра графа не существенен), называются <strong>неориентированными.</strong></p>
  <p id="i4AP">Графы, в которых все рёбра являются <a href="https://function-x.ru/graphs5.html" target="_blank">дугами</a> (порядок двух концов ребра графа существенен), называются <strong>ориентированными графами.</strong></p>
  <p id="1ylU"><strong>Неориентированный граф</strong> может быть представлен в виде ориентированного графа, если каждое его звено заменить на две дуги, имеющие противоположные направления.</p>
  <p id="ewfO"><strong>Связный граф</strong> — <a href="https://ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D1%84_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)" target="_blank">граф</a>, содержащий ровно одну <a href="https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82%D0%B0_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8_%D0%B3%D1%80%D0%B0%D1%84%D0%B0" target="_blank">компоненту связности</a>. Это означает, что между любой парой <a href="https://ru.wikipedia.org/wiki/%D0%93%D0%BB%D0%BE%D1%81%D1%81%D0%B0%D1%80%D0%B8%D0%B9_%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D0%B8_%D0%B3%D1%80%D0%B0%D1%84%D0%BE%D0%B2" target="_blank">вершин</a> этого графа существует как минимум один <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%83%D1%82%D1%8C_(%D1%82%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B3%D1%80%D0%B0%D1%84%D0%BE%D0%B2)" target="_blank">путь</a>.</p>
  <p id="6Trq">Граф заданного типа называют полным, если он содержит все возможные для этого типа рёбра (при неизменном множестве вершин). Так, в полном обыкновенном графе каждая пара различных вершин соединена ровно одним звеном.</p>
  <p id="JlB4"><strong>Деревом </strong>называется связный граф без циклов. Любые две вершины дерева соединены лишь одним маршрутом:</p>
  <figure id="lQ12" class="m_custom">
    <img src="https://img1.teletype.in/files/48/9b/489b5e43-99ba-46b1-9242-6ec11bdb0eda.png" width="365" />
  </figure>
  <p id="y0Fl"><strong>Смежность вершин графа</strong> - это когда две вершины графа соединены ребром.</p>
  <p id="chaB"><strong><em>Зададимся вопросом:</em></strong> можно ли поместить слона в компьютер? Ответ: можно, если слона смоделировать в виде графа, в котором вершинами являются части его тела, а рёбра соединяют те части тела, которые соединены в слоне как биологическом объекте. При этом получившийся граф должен быть представлен в памяти компьютера в понятном компьютеру виде.</p>
  <h2 id="dKgs">3. Способы представления графа в памяти компьютера.</h2>
  <p id="zBcO">В связи с широким применением <a href="https://function-x.ru/graphs1_relations.html" target="_blank">графов</a> в программировании и информационных технологиях вообще возникает вопрос о представлении графа в виде структуры данных. Различные способы представления графов в памяти компьютера отличаются объёмом занимаемой памяти и скоростью выполнения операций над графами.</p>
  <p id="a975">Наиболее часто используются две такие структуры данных - <strong>матрица смежности </strong>и<strong> список инцидентности.</strong></p>
  <h3 id="nv3z">Матрицы смежности</h3>
  <p id="WObb">Матрица смежности позволяет установить множество вершин, соседних с заданной (то есть рассматриваемой в конкретной задаче), не прибегая к полному просмотру всей матрицы. Матрицы смежности обычно представляются двумерным массивом размера n x n, где n - число вершин графа.</p>
  <p id="TKTA"><strong>Матрица смежности S</strong> - это квадратная матрица, в которой и число строк, и число столбцов равно n - числу вершин графа. В ячейки матрицы смежности записываются некоторые числа в зависимости от того, соединены соответствующие вершины рёбрами или нет, и от типа графа.</p>
  <h3 id="zFOb">Списки инцидентности</h3>
  <p id="i1LO">Графы значительного объёма целесообразно хранить в памяти компьютера в форме списков инцидентности.</p>
  <p id="hgzJ">Список инцидентности одной вершины графа включает номера вершин, смежных с ней.</p>
  <p id="s1co">Ссылки на начало этих списков образуют одномерный массив, индексами которого служат номера вершин графа.</p>
  <h3 id="fdd9">Преимущества и недостатки каждого способа.</h3>
  <p id="zpSR"><strong>Матрицы смежности целесообразнее использовать, когда:</strong></p>
  <ul id="jIYL">
    <li id="1AVF">число вершин графа невелико;</li>
    <li id="vYCv">число рёбер графа относительно большое;</li>
    <li id="WfPw">в алгоритме часто требуется проверять, соединены ли между собой две вершины;</li>
    <li id="n0bx">в алгоритме используются фундаментальные понятия теории графов, например, связность графа.</li>
  </ul>
  <p id="P3SP">Из-за последнего обстоятельства матрицы чаще используются в теоретических исследованиях графов.</p>
  <p id="Ue0m"><strong>Списки инцидентности целесообразнее использовать, когда:</strong></p>
  <ul id="gN6l">
    <li id="kAeu">число вершин графа велико;</li>
    <li id="95VQ">число рёбер графа относительно невелико;</li>
    <li id="VaQL">граф формируется по какой-либо модели;</li>
    <li id="mvKb">во время действия алгоритма часто требуется модифицировать граф;</li>
    <li id="rUPO">в алгоритме часто используются локальные свойства вершин, например, окрестности вершин.</li>
  </ul>
  <hr />
  <h2 id="pVpr">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="kGuL">Сегодня мы начали знакомиться с целым разделом математики и программирования – теорией графов. Разобрались, что такое граф, из чего он состоит, поговорили, какие бывают графы, и, самое главное, научились сохранять граф в памяти компьютера.</p>
    <p id="NPNZ">А на следующем занятии попробуем разобрать на логические болтики и винтики очень нужную в хозяйстве вещь - автомобильный навигатор!</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@gamedev_academy_feedback/KKCvRif726r</guid><link>https://teletype.in/@gamedev_academy_feedback/KKCvRif726r?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback</link><comments>https://teletype.in/@gamedev_academy_feedback/KKCvRif726r?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=gamedev_academy_feedback#comments</comments><dc:creator>gamedev_academy_feedback</dc:creator><title>12. Хэш-таблицы. Коллизии. Класс Dictionary.</title><pubDate>Sun, 12 Sep 2021 15:39:07 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f8/c7/f8c7eb45-6d95-4cc8-af91-e67787f86192.png"></media:content><category>Алгоритмы и структуры данных</category><description><![CDATA[Продолжаем говорить о структурах данных.]]></description><content:encoded><![CDATA[
  <hr />
  <h2 id="OoSC">1. Хэш-таблицы.</h2>
  <p id="K6bN">Продолжаем говорить о структурах данных.</p>
  <p id="ihRS">Сегодня мы поговорим о структуре, которая позволяет найти элемент за О(1). Напоминаем, что и в обычном массиве, и в динамическом списке эта операция осуществляется за О(n).</p>
  <p id="q3qP">В вычислениях хэш-таблица - это структура данных, в которой хранится набор значений, каждое из которых может быть идентифицировано уникальным ключом. Этот ключ можно использовать в качестве подстановки для поиска любого элемента в наборе. Однако вместо того, чтобы хранить значение уникального ключа непосредственно, он хэшируется. Хэширование преобразует ключ в номер индекса, и данные значения, связанные с ключом, помещаются в место хранения, определённое этим индексом.</p>
  <p id="lToh">Главным преимуществом структуры хэш-таблиц является возможность быстрого поиска элементов, даже в огромных наборах данных. Если ключ для желаемого значения данных известен, ключ может быть хэширован и местоположение значения найдено непосредственно. Нет необходимости сканировать весь набор данных, чтобы найти элемент или отсортировать набор для выполнения двоичного поиска.</p>
  <p id="bWbz">При добавлении элементов в хэш-таблицу автоматически создается хэш-код. Этот код скрыт от разработчика. Весь доступ к значениям таблицы достигается с помощью ключевого объекта для идентификации.</p>
  <h2 id="EgLI">2. Коллизии.</h2>
  <p id="dcKw">Естественно, возникает вопрос, почему невозможно такое, что мы попадем дважды в одну ячейку массива, ведь представить функцию, которая ставит в сравнение каждому элементу совершенно различные натуральные числа просто невозможно. Именно так возникает проблема коллизии.</p>
  <p id="mJhs">Коллизия хэш-функции возникает тогда, когда двум значениям соответствует один ключ-значение. Коллизии существуют для большинства хэш-функций, но для «хороших» хэш-функций частота их возникновения близка к минимуму. </p>
  <p id="yOve">В некоторых частных случаях, когда множество различных входных данных конечно, можно задать хэш-функцию, по определению не имеющую коллизий. Однако для хэш-функций, принимающих вход переменной длины и возвращающих хэш постоянной длины, коллизии обязаны существовать, поскольку хотя бы для одного значения хэш-функции соответствующее ему множество входных данных будет бесконечно — и любые два набора данных из этого множества образуют коллизию. </p>
  <p id="t8XL">Если бы все данные были случайными, то хэш-функции были бы очень простые (например, несколько битов ключа). Однако на практике случайные данные встречаются достаточно редко, и приходится создавать функцию, которая зависела бы от всего ключа. Наихудший случай – когда все ключи хэшируются в один индекс. </p>
  <p id="lqN9">При возникновении коллизий необходимо найти новое место для хранения ключей, претендующих на одну и ту же ячейку хэш-таблицы. Причем, если коллизии допускаются, то их количество необходимо минимизировать. В некоторых специальных случаях удается избежать коллизий вообще. Например, если все ключи элементов известны заранее (или количество или длина значений ограничены), то для них можно найти некоторую инъективную хэш-функцию, которая распределит их по ячейкам хэш-таблицы без коллизий. </p>
  <p id="HM3D">Существует несколько решений проблемы коллизий: метод цепочек и метод двойного хэширования.</p>
  <p id="WpJo">Технология сцепления элементов (метод цепочек) состоит в том, что элементы множества, которым соответствует одно и то же хэш-значение, связываются в цепочку-список. В позиции номер i хранится указатель на голову списка тех элементов, у которых хэш-значение ключа равно i; если таких элементов в множестве нет, в позиции i записан NULL.</p>
  <p id="L0yT">В отличие от хэширования с цепочками, при открытой адресации никаких списков нет, а все записи хранятся в самой хэш-таблице. Каждая ячейка таблицы содержит либо элемент динамического множества, либо NULL. В этом случае, если ячейка с вычисленным индексом занята, то можно просто просматривать следующие записи таблицы по порядку до тех пор, пока не будет найден ключ K или пустая позиция в таблице. Для вычисления шага можно также применить формулу, которая и определит способ изменения шага.</p>
  <h2 id="tVay">3. Класс Dictionary.</h2>
  <p id="WH4F">В C# есть несколько классов для реализации хэш-таблиц. Рассмотрим один из самых популярных.</p>
  <p id="JM3O"><strong>Словарь (dictionary) </strong>— это обобщенная версия Hashtable. Главное свойство dictionary— быстрый поиск с помощью ключей. Можно также добавлять и удалять элементы, наподобие того как это делается в структуре List, но без расходов производительности, связанных с необходимостью смещения последующих элементов в памяти.</p>
  <p id="EuCh"><strong>В классе Dictionary&lt;TKey, TValue&gt; опреден ряд методов:</strong></p>
  <ul id="YpMF">
    <li id="Y3qu"><strong>Add()</strong> - добавляет в словарь пару &quot;ключ-значение&quot;, определяемую параметрами key и value. Если ключ key уже находится в словаре, то его значение не изменяется, и генерируется исключение ArgumentException.</li>
    <li id="z732"><strong>ContainsKey() </strong>- возвращает логическое значение true, если вызывающий словарь содержит объект key в качестве ключа; а иначе — логическое значение false.</li>
    <li id="VUZU"><strong>ContainsValue()</strong> - возвращает логическое значение true, если вызывающий словарь содержит значение value; в противном случае — логическое значение false.</li>
    <li id="Mvk5"><strong>Remove()</strong> - удаляет ключ key из словаря. При удачном исходе операции возвращается логическое значение true, а если ключ key отсутствует в словаре — логическое значение false.</li>
  </ul>
  <p id="eemO">Кроме того, класс Dictionary&lt;TKey, TValue&gt; определяет собственные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Эти свойства приведены ниже:</p>
  <ul id="1iNq">
    <li id="8GmE"><strong>Keys</strong> - получает коллекцию ключей.</li>
    <li id="JIdi"><strong>Values</strong> - получает коллекцию значений.</li>
  </ul>
  <h2 id="1uJi">4. Рассмотрим на примере использование словарей.</h2>
  <pre id="Ze8F" data-lang="clike">Dictionary&lt;int, string&gt; countries = new Dictionary&lt;int, string&gt;(5);
countries.Add(1, &quot;Russia&quot;);
countries.Add(3, &quot;Great Britain&quot;);
countries.Add(2, &quot;USA&quot;);
countries.Add(4, &quot;France&quot;);
countries.Add(5, &quot;China&quot;);

foreach (KeyValuePair&lt;int, string&gt; keyValue in countries)
{
    Console.WriteLine(keyValue.Key + &quot; - &quot; + keyValue.Value);
}

// получение элемента по ключу
string country = countries[4];
Console.WriteLine(country);

// изменение объекта
countries[4] = &quot;Spain&quot;;

// удаление по ключу
countries.Remove(2);

Console.WriteLine();
foreach (KeyValuePair&lt;int, string&gt; keyValue in countries)
{
    Console.WriteLine(keyValue.Key + &quot; - &quot; + keyValue.Value);
}</pre>
  <p id="5hdd">Класс словарей также, как и другие коллекции, предоставляет методы Add и Remove для добавления и удаления элементов. Только в случае словарей в метод Add передаются два параметра: ключ и значение. А метод Remove удаляет не по индексу, а по ключу.</p>
  <p id="RylD">Так как в нашем примере ключами является объекты типа int, а значениями - объекты типа string, то словарь в нашем случае будет хранить объекты KeyValuePair&lt;int, string&gt;. В цикле foreach мы их можем получить и извлечь из них ключ и значение.</p>
  <p id="GKmU">Кроме того, мы можем получить отдельно коллекции ключей и значений словаря:</p>
  <pre id="puQM" data-lang="clike">class Localization
{
    public Localization(string russian, string german)
    {
        Russian = russian;
        German = german;
    }
    
    public string Russian { get; set; }
    public string German { get; set; }
}

public static void Main(string[] args)
{
    Dictionary&lt;string, Localization&gt; words = new Dictionary&lt;string, Localization&gt;();
    words.Add(&quot;book&quot;, new Localization(&quot;Книга&quot;, &quot;Buch&quot;));
    words.Add(&quot;pen&quot;, new Localization(&quot;Ручка&quot;, &quot;Griff&quot;));
    words.Add(&quot;paper&quot;, new Localization(&quot;Бумага&quot;, &quot;Papier&quot;));

    foreach (KeyValuePair&lt;string, Localization&gt; keyValue in words)
    {
        // keyValue.Value представляет класс Localization
        Console.WriteLine(keyValue.Key + &quot; - &quot; + keyValue.Value.Russian);
    }

    // перебор ключей
    foreach (string key in words.Keys)
    {
        Console.WriteLine(key);
    }

    // перебор по значениям
    foreach (Localization word in words.Values)
    {
        Console.WriteLine(word.Russian);
    }
}</pre>
  <p id="w8b3">Здесь в качестве ключей выступают объекты типа string, а значениями – объекты Localization. Используя свойство Keys, мы можем получить ключи словаря, а свойство Values соответственно хранит все значения в словаре.</p>
  <p id="wnWk">Для добавления необязательно применять метод Add(), можно использовать сокращенный вариант:</p>
  <pre id="MNcc" data-lang="clike">Dictionary&lt;string, Localization &gt; words = new Dictionary&lt;string, Localization&gt;();
Dictionary.Add(&quot;eraser&quot;, new Localization (&quot;Стерка&quot;, &quot;Radiergummi&quot;));
Dictionary[&quot;eraser&quot;] = new Localization (&quot;Ластик&quot;, &quot;Radiergummi&quot;);</pre>
  <p id="i9fY">Несмотря на то, что изначально в словаре нет ключа &quot;eraser&quot; и соответствующего ему элемента, то он все равно будет установлен. Если же он есть, то элемент по ключу &quot;eraser&quot; будет заменен на новый объект new Localization (&quot;Ластик&quot;, &quot;Radiergummi&quot;).</p>
  <h3 id="F0zn">Инициализация словарей.</h3>
  <p id="O3Yw">Инициализировать словари можно следующим образом:</p>
  <p id="B16H"><strong><em>Первый вариант:</em></strong></p>
  <pre id="Iw6N" data-lang="clike">Dictionary&lt;string, string&gt; countries = new Dictionary&lt;string, string&gt;
{
     {&quot;Франция&quot;, &quot;Париж&quot;},
     {&quot;Германия&quot;, &quot;Берлин&quot;},
     {&quot;Великобритания&quot;, &quot;Лондон&quot;}
};

foreach(var pair in countries)
     Console.WriteLine(&quot;{0} - {1}&quot;, pair.Key, pair.Value);</pre>
  <p id="xtHY"><strong><em>Второй вариант:</em></strong></p>
  <pre id="95MB" data-lang="clike">Dictionary&lt;string, string&gt; countries = new Dictionary&lt;string, string&gt;
{
     [&quot;Франция&quot;]= &quot;Париж&quot;,
     [&quot;Германия&quot;]= &quot;Берлин&quot;,
     [&quot;Великобритания&quot;]= &quot;Лондон&quot;
};</pre>
  <hr />
  <h2 id="lgps">Итоги занятия.</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="3Cs4">Хэш-таблицы и, в частности, словари удобно применять, когда надо очень быстро получать ответ о наличии элемента в структуре по ключу. Один из самых часто используемых вариантов – как раз проверка правописания или программы-переводчики. При этом надо помнить, что такая структура может задействовать достаточно много памяти, поэтому не стоит ее применять без достаточных оснований.</p>
    <p id="YOWg">Итак, мы заканчиваем первую часть темы «Алгоритмизация и структуры данных». Мы уже успели поговорить о сложности алгоритмов, познакомиться со структурами данных динамический массив, стек, списки, очередь, хэш-таблицы и словари, разобрать несколько вариантов сортировки массивов, реализовать бинарный поиск и еще несколько очень полезных методов классической алгоритмизации (решето Эратосфена, алгоритм Евклида, префикс суммы), прикоснулись к рекурсии.</p>
    <p id="YMXq">Сделано очень много, но впереди самое сложное… И, наверное, самое полезное и интересное…</p>
    <p id="sP97">Сейчас ваша задача разобраться с первой частью, ответить на вопросы по теме, попрактиковаться, порешать задачи, накопить вопросы и проблемы, с которыми мы обязательно постараемся справиться.</p>
    <p id="8y0k">И только после этого нас ждут ГРАФЫ и ДЕРЕВЬЯ!</p>
  </section>

]]></content:encoded></item></channel></rss>