Древовидные структуры достаточно гибкие по своей задумке. Один и тот же набор значений можно расположить в виде дерева, если использовать разные топологические решения. В итоге два дерева с одинаковым содержимым будут обладать противоположными характеристиками, например, по скорости поиска узлов.
Ученые еще в середине прошлого века заинтересовались вопросом оптимизации хранения данных в деревьях. Они хотели обеспечить максимальную скорость поиска в них. Так появились сбалансированные деревья.
В этом уроке мы детально познакомимся с балансировкой деревьев, способами ребалансировки при добавлении новых узлов, а также рассмотрим новые виды древовидных структур.
Сбалансированные деревья
Идеальная сбалансированность — это свойство дерева, при котором все его уровни, иногда кроме последнего, полностью заполнены.
Для сравнения рассмотрим два бинарных дерева поиска, которые будут представлять собой одну последовательность элементов — 1,2,3,4,5,6. Из этой последовательности можно построить несколько деревьев, например:
В дереве (б) каждый из уровней, кроме левой ветки узла 1, заполнены. Значит, дерево (б) — идеально сбалансированное дерево. Что нельзя сказать о дереве (а).
Дерево (а) и дерево (б) можно отнести к бинарным деревьям поиска. Но для худшего случая — поиска в дереве элемента №6 в дереве (а) — требуется выполнить шесть операций перехода между вершинами, а в случае (б) — только три.
Дерево с максимально возможной высотой (а) называется разбалансированным или вырожденным деревом. Оно не отличается от двусвязного списка, значит, теряет свое основное преимущество — скорость поиска.
Для разбалансированных деревьев скорость составляет O(N). А для идеально сбалансированного дерева (б) поиск будет завершен за число шагов, которые не превышают высоту дерева или за O(logN).
Состояние идеальной сбалансированности в дереве трудно поддерживать. Любое добавление или удаление узла в сбалансированном дереве может привести к ситуации, когда дерево выйдет из идеально сбалансированного состояния. В таком случае скорость поиска будет значительно деградировать после каждой вставки или удаления узла.
Чтобы вернуть дерево в сбалансированный вид, его перестраивают после каждой манипуляции с составом узлов. Рассмотрим пример с добавлением элемента №7 в наше дерево. Это можно сделать несколькими способами:
В случае (б) дерево вышло из состояния идеальной сбалансированности, так как оба нижних уровня не заполнены полностью. В таком случае поиск элемента №7 будет выполнен за четыре операции.
В случае (а) элементы были перераспределены, чтобы сохранить сбалансированное состояние. В таком случае поиск элемента №7 будет выполнен всего за три операции.
На таких маленьких примерах выигрыш в одну операцию во время поиска может показаться слишком незначительным, чтобы постоянно перестраивать дерево. Но при работе с деревьями из миллионов узлов и повышенных требованиях к скорости выборки такое перестроение может себя оправдать. Пользователям очень нравится, когда приложение работает со скоростью человеческой мысли.
Ресурсы вычислительных машин не безграничны. Чтобы снизить нагрузку на них и одновременно с этим максимально сохранить преимущества идеально сбалансированного дерева, придумали новые виды деревьев. Среди них особенно выделяются АВЛ-деревья и красно-черные деревья, с которыми мы познакомимся подробнее.
АВЛ-деревья
Этот вид деревьев представили в 1962 году двое советских ученых: Адельсон-Вельский Г.М. и Ландис Е.М. Именно по сокращению от их фамилий АВЛ-деревья и получили такое название.
АВЛ-деревья отличаются от идеально сбалансированных. АВЛ-дерево считается сбалансированным, если для каждого узла дерева высота его правого и левого поддеревьев отличаются не более чем на единицу. Если модификация структуры узлов приводит к нарушению сбалансированности дерева, то необходимо выполнить его балансировку.
Поиск в АВЛ-дереве выполняется аналогично работе с бинарным деревом поиска. При этом благодаря поддержке возможности ребалансировки при вставки и удалении узлов в АВЛ-деревьях есть особенности реализации.
В качестве индикатора наличия разбалансированности в поддереве на узел выносят показатель баланса, который принимает значения от -1 до +1. Их значения:
- –1 — в правом поддереве больше высота
- 0 — поддеревья равной высоты
- +1 — высота больше в левом поддереве
В итоге код нашего узла принимает следующий вид:
Этот фрагмент кода определяет базовый скелет узла нашего будущего дерева. Чтобы превратить его в полноценное АВЛ-дерево, нужно добавить возможность искать и модифицировать узлы. Операцию поиска можно переиспользовать из бинарных деревьев поиска, а изменение в структуре дерева потребует дополнительной частичной ребалансировки.
Далее мы подробно поговорим об операциях добавления и удаления узлов в АВЛ-деревьях.
Модификация структуры узлов
Добавить новую вершину можно с помощью рекурсивного спуска к месту вставки нового узла. Если возвращаться к корневой вершине через каждый промежуточный узел, то добавивший новую вершину метод модифицирует значение balanceFactor. Если новое значение выходит из допустимого диапазона, то выполняется ребалансировка данного поддерева.
Ребалансировка деревьев осуществляется при помощи специальных механизмов — методов вращения. Вращения бывают двух видов: левое и правое.
Вращение вправо выполняется за три шага:
- Текущий корень поддерева (D) заменяется на левый дочерний узел (B)
- Предыдущий корень (D) становится правым дочерним узлом для (B)
- Предыдущее правое поддерево узла (B) становится левым поддеревом для (D)
Вращение влево выполняется аналогично:
- Текущий корень поддерева (D) заменяется на правый дочерний узел (C)
- Предыдущий корень (D) становится левым дочерним узлом для (C)
- Предыдущее левое поддерево узла (C) становится правым поддеревом для (D)
В зависимости от того, куда добавили новую вершину, возможны различные комбинации выполнения вращений. Они помогут вернуть дерево в сбалансированное состояние.
Всего выделяется четыре варианта развития событий:
- Левое поддерево левой дочерней вершины
- Левое поддерево правой дочерней вершины
- Правое поддерево левой дочерней вершины
- Правое поддерево правой дочерней вершины
Рассмотрим пример, который описывает первый случай — вставку в левое поддерево левой дочерней вершины. Изображенные треугольники представляют собой сбалансированные АВЛ-поддеревья. Они могут содержать большое количество вершин. У вершины В дерево не сбалансировано, поскольку поддерево А1 в вершине А на два уровня выше, чем поддерево В2:
Чтобы сбалансировать дерево, необходимо совершить правое вращение — заменить вершину В вершиной А и сделать поддерево А2 левым поддеревом вершины В. После такого преобразования наше поддерево примет следующий вид:
Четвертый сценарий будет выглядеть аналогично кроме замены способа вращения на левое.
Для второго и третьего сценариев необходимо выполнить вращение дважды:
Удаление узлов также осуществляется при помощи механизмов вращения. При возврате во время рекурсивного спуска осуществляется вычисление balanceFactor. Если он отклоняется от допустимых значений, то выполняется ребалансировка аналогично добавлению узла.
Другим видом автоматически балансирующихся деревьев являются красно-черные деревья, с которыми мы познакомимся дальше.
Красно-черные деревья
Красно-черные деревья — одни из наиболее активно используемых на практике самобалансирующихся деревьев. Они так называются из-за наличия на узле дополнительного поля, в котором размещается цвет узла. В качестве стандартных цветов используют обычно красные и черные узлы, а сам цвет узла используется во время балансировки.
Так как красно-черные деревья самобалансирующиеся, то среднее и худшее время поиска тоже составляют O(logN). А операции вставки и удаления узла могут потребовать поворот поддерева.
Для красно-черного дерева наш код узла примет следующий вид:
В отличие от других видов деревьев в листовых узлах красно-черных деревьев не хранят полезную нагрузку. А цвет листовых узлов без данных всегда считается черным. Такая особенность позволяет считать ссылку на null валидным узлом. Эта особенность позволит сэкономить память. А само дерево принимает следующий вид:
Помимо особенностей работы с листовыми узлами к свойствам красно-черного так же относят:
- Корень красно-черного дерева черный
- Две красные вершины не могут идти подряд ни на одном пути. Оба потомка каждого красного узла — черные
- Для каждой вершины, в каждом исходящем из нее пути, одинаковое число черных вершин
Иногда при работе с узлами красно-черного дерева используют черную высоту — количество черных вершин на исходящих из нее путях, не включая саму исходную вершину.
Чтобы вставить узел, мы сначала ищем в дереве место, куда его следует добавить. Новый узел всегда добавляется как лист, поэтому оба его потомка являются пустыми узлами и предполагаются черными. После вставки красим узел в красный цвет. Далее смотрим на предка и проверяем, не нарушается ли свойства дерева, которые описали выше. Если необходимо, мы перекрашиваем узел и производим поворот, чтобы сбалансировать дерево.
Сбалансированность этих деревьев хуже, чем у АВЛ-деревьев. При этом затраты на поддержание состояния сбалансированности и потребление памяти меньше, а операцию поиска можно выполнять одновременно с выполнением перестроения дерева.
Благодаря этим преимуществам сфера применения красно-черных деревьев существенно шире. Так, например, в стандартной библиотеке шаблонов языка C++ STL и TreeMap в Java применяются именно красно-черные деревья.
Выводы
Хранение данных в идеально сбалансированных деревьях позволяет достичь скорости поиска O(logN). Но чтобы поддерживать идеальную сбалансированность, требуются существенные ресурсные затраты на полное перестроение дерева при добавлении нового элемента.
Чтобы решить эту проблему, были придуманы новые виды деревьев: АВЛ-деревья и красно-черные деревья. Использование таких подходов поможет сохранить преимущества бинарных деревьев поиска и не тратить большое количество ресурсов. Так можно поддерживать идеальную согласованность.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 16:30:36 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="pdTZ6dNhdwi8PB8lTlzdwVFvjkJ9DRMAX3NCzVeulPxKBRLeIR_aaAp_O71CUy22kWaj6HU67aLik9iZBalzkg";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Балансировка | Алгоритмы на деревьях</title>
<meta name="description" content="Балансировка / Алгоритмы на деревьях: Знакомимся со сбалансированными деревьями и новыми видами древовидных структур">
<link rel="canonical" href="https://ru.hexlet.io/courses/algorithms-trees/lessons/balancing/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Балансировка">
<meta property="og:title" content="Алгоритмы на деревьях">
<meta property="og:description" content="Балансировка / Алгоритмы на деревьях: Знакомимся со сбалансированными деревьями и новыми видами древовидных структур">
<meta property="og:url" content="https://ru.hexlet.io/courses/algorithms-trees/lessons/balancing/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="3YB9FLJf2aPs1qQTFBb2UW_Bl3AOVm70Ryh1Y3WpVp4yUbYjQCF0w1qVgIsYGQYmr8i62gZhkFb6yO83J66x8A" />
<script src="/vite/assets/inertia-INZxX8jp.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-6pOtQ3OW.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T16:30:36.377Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"DcYvpjbx-BlvL3pOEPrWObOWD02vp9Ov61l7qLeHHf3iF-SRxI9VedlsXtYc9SZOc58i56eQLQ1WueH85YD6kw","topics":[{"id":92077,"title":"Добрый день, поправьте пожалуйста описание сложности в разборе красно-черных деревьев.","plain_title":"Добрый день, поправьте пожалуйста описание сложности в разборе красно-черных деревьев. ","creator":{"public_name":"Anton Ternovski","id":115332,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":181748,"body":"Антон, добрый день.\n\nСпасибо, что сообщили. Поправим.","topic_id":92077}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100160,"title":"Наверное балансировка деревьев тема обширная. Я понял зачем это, понял, что балансировка достигается за счет вращения, но детали этих вращений теоретическая часть мне не объяснила. Думаю нужно либо даже не пытаться углубляться, а уж если углубляться, то доносить материал более детально","plain_title":"Наверное балансировка деревьев тема обширная. Я понял зачем это, понял, что балансировка достигается за счет вращения, но детали этих вращений теоретическая часть мне не объяснила. Думаю нужно либо даже не пытаться углубляться, а уж если углубляться, то доносить материал более детально ","creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"comments":[{"creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"id":191691,"body":"Начну даже не с вращений, начну наверное с базы. Например, вы активно используете термин **высота**, но определения ему дано не было. В задании нужно найти **черную высоту**. Но что это такое, я так и не понял","topic_id":100160},{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":191747,"body":"**павел андреев**, \n\nспасибо, что не остаетесь равнодушным, и делитесь впечатлениями и обратной связь по материалам. Передаю ваш фидбек нашим методистам, будем дорабатывать!","topic_id":100160}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100161,"title":"> Добавить новую вершину можно с помощью рекурсивного спуска к месту вставки нового узла. Если возвращаться к корневой вершине через каждый промежуточный узел, то добавивший новую вершину метод модифицирует значение balanceFactor. Если новое значение выходит из допустимого диапазона, то выполняется ребалансировка данного поддерева.\n\nНачиная со второго предложения непонятно все, ну например:\n\n1. Что значит возвращаться к корневой вершине через каждый промежуточный узел?\n2. Что за добавивший новую вершину метод?\n3. Где именно он должен смодифицировать значение balanceFactor и как именно это происходит\n4. Что за допустимый диапазон? \n\nДумаю даже псевдо код без логики самого вращения тут бы сильно помог","plain_title":"Добавить новую вершину можно с помощью рекурсивного спуска к месту вставки нового узла. Если возвращаться к корневой вершине через каждый промежуточный узел, то добавивший новую вершину метод модифицирует значение balanceFactor. Если новое значение выходит из допустимого диапазона, то выполняется ребалансировка данного поддерева. Начиная со второго предложения непонятно все, ну например: Что значит возвращаться к корневой вершине через каждый промежуточный узел? Что за добавивший новую вершину метод? Где именно он должен смодифицировать значение balanceFactor и как именно это происходит Что за допустимый диапазон? Думаю даже псевдо код без логики самого вращения тут бы сильно помог ","creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":191758,"body":"**павел андреев**, \n\nспасибо, забрала вашу обратную связь в работу.","topic_id":100161}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100163,"title":"> Рассмотрим пример, который описывает первый случай — вставку в левое поддерево левой дочерней вершины. Изображенные треугольники представляют собой сбалансированные АВЛ-поддеревья. Они могут содержать большое количество вершин.\n\nТут вы впервые вводите эти треугольники. Не совсем понятно что они и горизонтальные пунктирные линии обозначают","plain_title":"Рассмотрим пример, который описывает первый случай — вставку в левое поддерево левой дочерней вершины. Изображенные треугольники представляют собой сбалансированные АВЛ-поддеревья. Они могут содержать большое количество вершин. Тут вы впервые вводите эти треугольники. Не совсем понятно что они и горизонтальные пунктирные линии обозначают ","creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":191760,"body":"**павел андреев**, \n\nспасибо за вопросы, также забрала в работу.","topic_id":100163},{"creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"id":191692,"body":"> У вершины В дерево не сбалансировано, поскольку поддерево А1 в вершине А на два уровня выше, чем поддерево В2\n\nа у вершины А дерево сбалансировано? Если мы сбалансируем поддерево у вершины А, все дерево разве не станет сбалансированно?","topic_id":100163}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100162,"title":"В зависимости от того, куда добавили новую вершину, возможны различные комбинации выполнения вращений. Они помогут вернуть дерево в сбалансированное состояние.\n\nВсего выделяется четыре варианта развития событий:\n\n- Левое поддерево левой дочерней вершины\n- Левое поддерево правой дочерней вершины\n- Правое поддерево левой дочерней вершины\n- Правое поддерево правой дочерней вершины\n\nПочему именно четыре варианта, почему не два (дерево же бинарное) или бесконечно много?","plain_title":"В зависимости от того, куда добавили новую вершину, возможны различные комбинации выполнения вращений. Они помогут вернуть дерево в сбалансированное состояние. Всего выделяется четыре варианта развития событий: Левое поддерево левой дочерней вершины Левое поддерево правой дочерней вершины Правое поддерево левой дочерней вершины Правое поддерево правой дочерней вершины Почему именно четыре варианта, почему не два (дерево же бинарное) или бесконечно много? ","creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":191759,"body":"**павел андреев**, \n\nспасибо, передала нашим методистам, посмотрим - дополним материал таким образом, чтобы он давал ответ на этот вопрос.","topic_id":100162}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100166,"title":"Про красно черные деревья понимания не возникло вообще никакого. Описано несколько их свойств и характеристик, но как эта самобалансируемость работает, для меня осталось магией. Как по мне, лучше бы вообще их не упоминали, так как тема совсем не раскрыта","plain_title":"Про красно черные деревья понимания не возникло вообще никакого. Описано несколько их свойств и характеристик, но как эта самобалансируемость работает, для меня осталось магией. Как по мне, лучше бы вообще их не упоминали, так как тема совсем не раскрыта ","creator":{"public_name":"павел андреев","id":179364,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":191763,"body":"**павел андреев**, \n\nспасибо, забираю также в работу.","topic_id":100166}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":102149,"title":"**Очепятка**\n> Реализуйте и экспортируйте по умолчанию функцию, которая ~~**которая**~~ возвращает черную высоту дерева. Функция принимает объект, преобразует его в дерево и возвращает число – черную высоту дерева.\n","plain_title":"Очепятка Реализуйте и экспортируйте по умолчанию функцию, которая ~~которая~~ возвращает черную высоту дерева. Функция принимает объект, преобразует его в дерево и возвращает число – черную высоту дерева. ","creator":{"public_name":"Алексей Олещенко","id":582226,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":194303,"body":"Поправил, спасибо!","topic_id":102149},{"creator":{"public_name":"Игорь Пустошило","id":744978,"is_tutor":false},"id":194283,"body":"Проще не в обсуждения писать. Выделяете текст и Ctrl+Enter. Пишете коммент - что не так (если нужно), и оно сразу идет Верховному :) Это везде работает - и в статьях, и в уроках.","topic_id":102149}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":102151,"title":"Почему бы сразу не прописать это в ридми? Отвлекает )\n```\nimport os\nimport sys\n\nsys.path.append(os.path.abspath('/usr/src/app/'))\n\nfrom helpers.helpers import build_RBTree\n\n\ndef solution(data):\n tree = build_RBTree(data)\n return count_black_nodes(tree)\n```","plain_title":"Почему бы сразу не прописать это в ридми? Отвлекает ) ``` import os import sys sys.path.append(os.path.abspath('/usr/src/app/')) from helpers.helpers import build_RBTree def solution(data): tree = buildRBTree(data) return countblack_nodes(tree) ``` ","creator":{"public_name":"Алексей Олещенко","id":582226,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":194320,"body":"Что значит сразу прописать в редми?","topic_id":102151},{"creator":{"public_name":"Алексей Олещенко","id":582226,"is_tutor":false},"id":194325,"body":"Импорты прописать сразу...)","topic_id":102151},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":194336,"body":"Ага, понял, поправил практику","topic_id":102151}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":100422,"title":"Каким образом найдено значение черной высоты если вы прошлись только по одной левой стороне?\nИли у вас в тесте некорректна картинка узлов, не везде есть черные левые узлы, или решение не может быть таким как есть.\nВ чем причина? ","plain_title":"Каким образом найдено значение черной высоты если вы прошлись только по одной левой стороне? Или у вас в тесте некорректна картинка узлов, не везде есть черные левые узлы, или решение не может быть таким как есть. В чем причина? ","creator":{"public_name":"Сергей","id":458071,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":192194,"body":"**Сергей**, \n\nспасибо, что обратили внимание. Создала тикет, проверим упражнение и при необходимости внесем корректировки.","topic_id":100422}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}},{"id":105078,"title":"Приветствую!\nПочему-то при сдаче задания на js запускались тесты на питоне, и, падая, не пропускали к учительскому решению. Змеиный скрипт, как и во всех предыдущих практиках, вообще не трогал.\n\nПробовал сбрасывать практику несколько раз, но тщетно – пришлось переводить на парселтанг :)\n\nИ ещё один момент – заметил очепятку в тексте, в абзаце про АВЛ деревья:\n\n> Поиск в АВЛ-дереве выполняется аналогично работе с бинарным деревом поиска. При этом благодаря поддержке возможности ребалансировки **при вставки** и удалении узлов в АВЛ-деревьях есть особенности реализации.\n\nТут, конечно, правильно будет \"при вставкЕ и удалении\"","plain_title":"Приветствую! Почему-то при сдаче задания на js запускались тесты на питоне, и, падая, не пропускали к учительскому решению. Змеиный скрипт, как и во всех предыдущих практиках, вообще не трогал. task-tests https://res.cloudinary.com/dzq4iaveg/image/upload/v1735404205/samples/Screenshot_2024-12-28_at_18.42.18_boqlbx.png Пробовал сбрасывать практику несколько раз, но тщетно – пришлось переводить на парселтанг :) И ещё один момент – заметил очепятку в тексте, в абзаце про АВЛ деревья: Поиск в АВЛ-дереве выполняется аналогично работе с бинарным деревом поиска. При этом благодаря поддержке возможности ребалансировки при вставки и удалении узлов в АВЛ-деревьях есть особенности реализации. Тут, конечно, правильно будет \"при вставкЕ и удалении\" ","creator":{"public_name":"Егор Булгаков","id":392445,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":197817,"body":"Ага, поправил практику.","topic_id":105078}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Балансировка","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2500,"slug":"algorithms_trees_balancing_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Напишите функцию, которая возвращает черную высоту дерева. Предполагаем, что дерево сбалансированно.\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n\nsolution(tree) # 1\n```\n\n## solutions/solution.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая возвращает черную высоту дерева. Функция принимает объект, преобразует его в дерево и возвращает число – черную высоту дерева.\n\nДля преобразования исходного объекта в дерево используйте функцию `buildRBTree()`.\n\nПредположим, что есть такое дерево\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n```\n\n```javascript\nimport buildRBTree from `./helpers.js`\n\nconst solution = (data) => {\n const tree = buildRBTree(data);\n return countBlackNodes(tree); // функция, которую вам нужно реализовать\n};\n\nsolution(data); // 1\n```\n\n## solutions/solution.php\n\nУсловия такие же как для JavaScript. Для преобразования исходного массива в дерево используйте функцию `buildRBTree()`\n\n```php\n<?php\n\nfunction solution($data) \n{\n $tree = buildRBTree($data);\n return countBlackNodes($tree); // функция, которую вам нужно реализовать\n}\n\nsolution($data); // 1 \n```\n\n## solutions/solution.py\n\nУсловия такие же как для JavaScript. Для преобразования исходного массива в дерево используйте функцию `build_RBTree()`\n\n```python\ndef solution(data):\n tree = build_RBTree(data)\n return count_black_nodes(tree) ## функция, которую вам нужно реализовать\n\nsolution(data) ## 1 \n```\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `countBlackNodes()`, который принимает в качестве параметра красно-черное дерево `RBTreeNode`. Метод должен вернуть целое число – черную высоту дерева.\n\nПредположим, есть такое дерево `RBTreeNode rbt`:\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n```\n\n```java\nvar h = Solution.countBlackNodes(); // 1\n```\n\nДобавьте в класс `Solution` метод `run()` со следующим кодом:\n\n```java\npublic static int run(java.util.Map<String, Object> data) {\n return helpers.Helper.run(data);\n}\n```\n\nЭтот метод необходим для проверки вашего решения автоматическими тестами\n\n### Подсказки\n\n* Структуру исходных деревьев можно подсмотреть в директории *\\_\\_fixtures\\_\\_/*\n","prepared_readme":"Напишите функцию, которая возвращает черную высоту дерева. Предполагаем, что дерево сбалансированно.\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n\nsolution(tree) # 1\n```\n\n## solutions/solution.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая возвращает черную высоту дерева. Функция принимает объект, преобразует его в дерево и возвращает число – черную высоту дерева.\n\nДля преобразования исходного объекта в дерево используйте функцию `buildRBTree()`.\n\nПредположим, что есть такое дерево\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n```\n\n```javascript\nimport buildRBTree from `./helpers.js`\n\nconst solution = (data) => {\n const tree = buildRBTree(data);\n return countBlackNodes(tree); // функция, которую вам нужно реализовать\n};\n\nsolution(data); // 1\n```\n\n## solutions/solution.php\n\nУсловия такие же как для JavaScript. Для преобразования исходного массива в дерево используйте функцию `buildRBTree()`\n\n```php\n<?php\n\nfunction solution($data) \n{\n $tree = buildRBTree($data);\n return countBlackNodes($tree); // функция, которую вам нужно реализовать\n}\n\nsolution($data); // 1 \n```\n\n## solutions/solution.py\n\nУсловия такие же как для JavaScript. Для преобразования исходного массива в дерево используйте функцию `build_RBTree()`\n\n```python\ndef solution(data):\n tree = build_RBTree(data)\n return count_black_nodes(tree) ## функция, которую вам нужно реализовать\n\nsolution(data) ## 1 \n```\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `countBlackNodes()`, который принимает в качестве параметра красно-черное дерево `RBTreeNode`. Метод должен вернуть целое число – черную высоту дерева.\n\nПредположим, есть такое дерево `RBTreeNode rbt`:\n\n```text\n 5b\n / \\\n / \\\n 3r 8r\n / \\ / \\\n 2b 4b 7b 9b\n / /\n 1r 6r\n```\n\n```java\nvar h = Solution.countBlackNodes(); // 1\n```\n\nДобавьте в класс `Solution` метод `run()` со следующим кодом:\n\n```java\npublic static int run(java.util.Map<String, Object> data) {\n return helpers.Helper.run(data);\n}\n```\n\nЭтот метод необходим для проверки вашего решения автоматическими тестами\n\n### Подсказки\n\n* Структуру исходных деревьев можно подсмотреть в директории *\\_\\_fixtures\\_\\_/*\n","has_solution":true,"entity_name":"Балансировка"},"units":[{"id":7228,"name":"theory","url":"/courses/algorithms-trees/lessons/balancing/theory_unit"},{"id":8502,"name":"quiz","url":"/courses/algorithms-trees/lessons/balancing/quiz_unit"},{"id":8527,"name":"exercise","url":"/courses/algorithms-trees/lessons/balancing/exercise_unit"}],"links":[],"ordered_units":[{"id":7228,"name":"theory","url":"/courses/algorithms-trees/lessons/balancing/theory_unit"},{"id":8502,"name":"quiz","url":"/courses/algorithms-trees/lessons/balancing/quiz_unit"},{"id":8527,"name":"exercise","url":"/courses/algorithms-trees/lessons/balancing/exercise_unit"}],"id":3239,"slug":"balancing","state":"approved","name":"Балансировка","course_order":130,"goal":"Знакомимся со сбалансированными деревьями и новыми видами древовидных структур","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Древовидные структуры достаточно гибкие по своей задумке. Один и тот же набор значений можно расположить в виде дерева, если использовать разные топологические решения. В итоге два дерева с одинаковым содержимым будут обладать противоположными характеристиками, например, по скорости поиска узлов.\n\nУченые еще в середине прошлого века заинтересовались вопросом оптимизации хранения данных в деревьях. Они хотели обеспечить максимальную скорость поиска в них. Так появились сбалансированные деревья.\n\nВ этом уроке мы детально познакомимся с балансировкой деревьев, способами ребалансировки при добавлении новых узлов, а также рассмотрим новые виды древовидных структур.\n\n## Сбалансированные деревья\n\n**Идеальная сбалансированность** — это свойство дерева, при котором все его уровни, иногда кроме последнего, полностью заполнены.\n\nДля сравнения рассмотрим два бинарных дерева поиска, которые будут представлять собой одну последовательность элементов — 1,2,3,4,5,6. Из этой последовательности можно построить несколько деревьев, например:\n\n\n\nВ дереве (б) каждый из уровней, кроме левой ветки узла 1, заполнены. Значит, дерево (б) — идеально сбалансированное дерево. Что нельзя сказать о дереве (а).\n\nДерево (а) и дерево (б) можно отнести к бинарным деревьям поиска. Но для худшего случая — поиска в дереве элемента №6 в дереве (а) — требуется выполнить шесть операций перехода между вершинами, а в случае (б) — только три.\n\nДерево с максимально возможной высотой (а) называется **разбалансированным** или **вырожденным деревом**. Оно не отличается от двусвязного списка, значит, теряет свое основное преимущество — скорость поиска.\n\nДля разбалансированных деревьев скорость составляет `O(N)`. А для идеально сбалансированного дерева (б) поиск будет завершен за число шагов, которые не превышают высоту дерева или за `O(logN)`.\n\nСостояние идеальной сбалансированности в дереве трудно поддерживать. Любое добавление или удаление узла в сбалансированном дереве может привести к ситуации, когда дерево выйдет из идеально сбалансированного состояния. В таком случае скорость поиска будет значительно деградировать после каждой вставки или удаления узла.\n\nЧтобы вернуть дерево в сбалансированный вид, его перестраивают после каждой манипуляции с составом узлов. Рассмотрим пример с добавлением элемента №7 в наше дерево. Это можно сделать несколькими способами:\n\n\n\nВ случае (б) дерево вышло из состояния идеальной сбалансированности, так как оба нижних уровня не заполнены полностью. В таком случае поиск элемента №7 будет выполнен за четыре операции.\n\nВ случае (а) элементы были перераспределены, чтобы сохранить сбалансированное состояние. В таком случае поиск элемента №7 будет выполнен всего за три операции.\n\nНа таких маленьких примерах выигрыш в одну операцию во время поиска может показаться слишком незначительным, чтобы постоянно перестраивать дерево. Но при работе с деревьями из миллионов узлов и повышенных требованиях к скорости выборки такое перестроение может себя оправдать. Пользователям очень нравится, когда приложение работает со скоростью человеческой мысли.\n\nРесурсы вычислительных машин не безграничны. Чтобы снизить нагрузку на них и одновременно с этим максимально сохранить преимущества идеально сбалансированного дерева, придумали новые виды деревьев. Среди них особенно выделяются АВЛ-деревья и красно-черные деревья, с которыми мы познакомимся подробнее.\n\n### АВЛ-деревья\n\nЭтот вид деревьев представили в 1962 году двое советских ученых: Адельсон-Вельский Г.М. и Ландис Е.М. Именно по сокращению от их фамилий **АВЛ-деревья** и получили такое название.\n\nАВЛ-деревья отличаются от идеально сбалансированных. АВЛ-дерево считается сбалансированным, если для каждого узла дерева высота его правого и левого поддеревьев отличаются не более чем на единицу. Если модификация структуры узлов приводит к нарушению сбалансированности дерева, то необходимо выполнить его балансировку.\n\nПоиск в АВЛ-дереве выполняется аналогично работе с бинарным деревом поиска. При этом благодаря поддержке возможности ребалансировки при вставки и удалении узлов в АВЛ-деревьях есть особенности реализации.\n\nВ качестве индикатора наличия разбалансированности в поддереве на узел выносят показатель баланса, который принимает значения от -1 до +1. Их значения:\n\n* –1 — в правом поддереве больше высота\n* 0 — поддеревья равной высоты\n* +1 — высота больше в левом поддереве\n\nВ итоге код нашего узла принимает следующий вид:\n\n```javascript\nclass AvlTreeNode {\n constructor(value, parent) {\n this.left = null // ссылка на левый дочерний узел\n this.right = null // ссылка на правый дочерний\n this.balanceFactor = 0 // показатель сбалансированности\n this.parent = parent // ссылка на родителя\n this.value = value // полезная нагрузка\n }\n}\n```\n\n```java\nclass AvlTreeNode {\n public AvlTreeNode left = null; // ссылка на левый дочерний узел\n public AvlTreeNode right = null; // ссылка на правый дочерний\n public int balanceFactor = 0; // показатель сбалансированности\n public AvlTreeNode parent; // ссылка на родителя\n public Object value; // полезная нагрузка\n\n AvlTreeNode(Object value, AvlTreeNode parent) {\n this.parent = parent;\n this.value = value;\n }\n}\n```\n\n```python\nclass AvlTreeNode:\n def __init__(self, value, parent=None):\n self.left = None # ссылка на левый дочерний узел\n self.right = None # ссылка на правый дочерний узел\n self.balance_factor = 0 # показатель сбалансированности\n self.parent = parent # ссылка на родителя\n self.value = value # полезная нагрузка\n```\n\n```php\n<?php\n\nclass AvlTreeNode\n{\n public $left = null; // ссылка на левый дочерний узел\n public $right = null; // ссылка на правый дочерний\n public $balanceFactor = 0; // показатель сбалансированности\n public $parent; // ссылка на родителя\n public $value; // полезная нагрузка\n\n public function __construct($value, $parent)\n {\n $this->parent = $parent;\n $this->value = $value;\n }\n}\n```\n\nЭтот фрагмент кода определяет базовый скелет узла нашего будущего дерева. Чтобы превратить его в полноценное АВЛ-дерево, нужно добавить возможность искать и модифицировать узлы. Операцию поиска можно переиспользовать из бинарных деревьев поиска, а изменение в структуре дерева потребует дополнительной частичной ребалансировки.\n\nДалее мы подробно поговорим об операциях добавления и удаления узлов в АВЛ-деревьях.\n\n### Модификация структуры узлов\n\nДобавить новую вершину можно с помощью рекурсивного спуска к месту вставки нового узла. Если возвращаться к корневой вершине через каждый промежуточный узел, то добавивший новую вершину метод модифицирует значение balanceFactor. Если новое значение выходит из допустимого диапазона, то выполняется ребалансировка данного поддерева.\n\nРебалансировка деревьев осуществляется при помощи специальных механизмов — **методов вращения**. Вращения бывают двух видов: левое и правое.\n\nВращение вправо выполняется за три шага:\n\n1. Текущий корень поддерева (D) заменяется на левый дочерний узел (B)\n2. Предыдущий корень (D) становится правым дочерним узлом для (B)\n3. Предыдущее правое поддерево узла (B) становится левым поддеревом для (D)\n\n\n\nВращение влево выполняется аналогично:\n\n1. Текущий корень поддерева (D) заменяется на правый дочерний узел (C)\n2. Предыдущий корень (D) становится левым дочерним узлом для (C)\n3. Предыдущее левое поддерево узла (C) становится правым поддеревом для (D)\n\n\n\nВ зависимости от того, куда добавили новую вершину, возможны различные комбинации выполнения вращений. Они помогут вернуть дерево в сбалансированное состояние.\n\nВсего выделяется четыре варианта развития событий:\n\n* Левое поддерево левой дочерней вершины\n* Левое поддерево правой дочерней вершины\n* Правое поддерево левой дочерней вершины\n* Правое поддерево правой дочерней вершины\n\nРассмотрим пример, который описывает первый случай — вставку в левое поддерево левой дочерней вершины. Изображенные треугольники представляют собой сбалансированные АВЛ-поддеревья. Они могут содержать большое количество вершин. У вершины В дерево не сбалансировано, поскольку поддерево А1 в вершине А на два уровня выше, чем поддерево В2:\n\n\n\nЧтобы сбалансировать дерево, необходимо совершить правое вращение — заменить вершину В вершиной А и сделать поддерево А2 левым поддеревом вершины В. После такого преобразования наше поддерево примет следующий вид:\n\n\n\nЧетвертый сценарий будет выглядеть аналогично кроме замены способа вращения на левое.\n\nДля второго и третьего сценариев необходимо выполнить вращение дважды:\n\n\n\nУдаление узлов также осуществляется при помощи механизмов вращения. При возврате во время рекурсивного спуска осуществляется вычисление balanceFactor. Если он отклоняется от допустимых значений, то выполняется ребалансировка аналогично добавлению узла.\n\n\n\nДругим видом автоматически балансирующихся деревьев являются красно-черные деревья, с которыми мы познакомимся дальше.\n\n### Красно-черные деревья\n\n**Красно-черные деревья** — одни из наиболее активно используемых на практике самобалансирующихся деревьев. Они так называются из-за наличия на узле дополнительного поля, в котором размещается цвет узла. В качестве стандартных цветов используют обычно красные и черные узлы, а сам цвет узла используется во время балансировки.\n\nТак как красно-черные деревья самобалансирующиеся, то среднее и худшее время поиска тоже составляют `O(logN)`. А операции вставки и удаления узла могут потребовать поворот поддерева.\n\nДля красно-черного дерева наш код узла примет следующий вид:\n\n```javascript\nclass RBTreeNode {\n constructor(value, parent) {\n this.left = null // ссылка на левый дочерний узел\n this.right = null // ссылка на правый дочерний\n this.isRed = false // цвет узла. Если не красный, то считаем что узел черный\n this.parent = parent // ссылка на родителя\n this.value = value // полезная нагрузка\n }\n}\n```\n\n```java\nclass RBTreeNode {\n public RBTreeNode left = null; // ссылка на левый дочерний узел\n public RBTreeNode right = null; // ссылка на правый дочерний\n public boolean isRed = false; // Цвет узла\n // Если узел не красный, то считаем что он черный\n public RBTreeNode parent; // ссылка на родителя\n public Object value; // полезная нагрузка\n\n RBTreeNode(Object value, RBTreeNode parent) {\n this.parent = parent;\n this.value = value;\n }\n}\n```\n\n```python\nclass RBTreeNode:\n def __init__(self, value, parent=None):\n self.left = None # ссылка на левый дочерний узел\n self.right = None # ссылка на правый дочерний узел\n self.is_red = False # цвет узла. Если не красный, то считаем что узел черный\n self.parent = parent # ссылка на родителя\n self.value = value # полезная нагрузка\n```\n\n```php\n<?php\n\nclass RBTreeNode\n{\n public $left = null; // ссылка на левый дочерний узел\n public $right = null; // ссылка на правый дочерний\n public $isRed = false; // Цвет узла\n // Если узел не красный, то считаем что он черный\n public $parent; // ссылка на родителя\n public $value; // полезная нагрузка\n\n public function __construct($value, $parent)\n {\n $this->parent = $parent;\n $this->value = $value;\n }\n}\n\n```\n\nВ отличие от других видов деревьев в листовых узлах красно-черных деревьев не хранят полезную нагрузку. А цвет листовых узлов без данных всегда считается черным. Такая особенность позволяет считать ссылку на null валидным узлом. Эта особенность позволит сэкономить память. А само дерево принимает следующий вид:\n\n\n\nПомимо особенностей работы с листовыми узлами к свойствам красно-черного так же относят:\n\n1. Корень красно-черного дерева черный\n2. Две красные вершины не могут идти подряд ни на одном пути. Оба потомка каждого красного узла — черные\n3. Для каждой вершины, в каждом исходящем из нее пути, одинаковое число черных вершин\n\nИногда при работе с узлами красно-черного дерева используют **черную высоту** — количество черных вершин на исходящих из нее путях, не включая саму исходную вершину.\n\nЧтобы вставить узел, мы сначала ищем в дереве место, куда его следует добавить. Новый узел всегда добавляется как лист, поэтому оба его потомка являются пустыми узлами и предполагаются черными. После вставки красим узел в красный цвет. Далее смотрим на предка и проверяем, не нарушается ли свойства дерева, которые описали выше. Если необходимо, мы перекрашиваем узел и производим поворот, чтобы сбалансировать дерево.\n\nСбалансированность этих деревьев хуже, чем у АВЛ-деревьев. При этом затраты на поддержание состояния сбалансированности и потребление памяти меньше, а операцию поиска можно выполнять одновременно с выполнением перестроения дерева.\n\nБлагодаря этим преимуществам сфера применения красно-черных деревьев существенно шире. Так, например, в стандартной библиотеке шаблонов языка C++ STL и TreeMap в Java применяются именно красно-черные деревья.\n\n## Выводы\n\nХранение данных в идеально сбалансированных деревьях позволяет достичь скорости поиска `O(logN)`. Но чтобы поддерживать идеальную сбалансированность, требуются существенные ресурсные затраты на полное перестроение дерева при добавлении нового элемента.\n\nЧтобы решить эту проблему, были придуманы новые виды деревьев: АВЛ-деревья и красно-черные деревья. Использование таких подходов поможет сохранить преимущества бинарных деревьев поиска и не тратить большое количество ресурсов. Так можно поддерживать идеальную согласованность.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":{"id":2482,"slug":"algorithms_trees_concept_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Напишите функцию, которая принимает содержание книги и возвращает маркированный список-оглавление.\n\n## solutions/solution.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде массива объектов и возвращает строку – маркированный список-оглавление следующего вида:\n\n```text\nChapter 1: Sorting Spells\n* 1.1 Bubble Sort\n* 1.2 Insertion Sort\n* 1.3 Merge Sort\n* 1.4 Quick Sort\nChapter 2: Graphical Charms\n* 2.1 Graph Traversal\n* * 2.1.1 Breadth-First Search\n* * 2.1.2 Depth-First Search\n...\n```\n\nСтруктуру объекта с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```javascript\nimport solution from './solution.js';\n\nsolution(book);\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n\n## solutions/solution.php\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде ассоциативного массива и возвращает строку – маркированный список-оглавление. Структуру ассоциативного массива с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```php\n<?php\n\nsolution(book);\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n\n## solutions/solution.py\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде списка словарей и возвращает строку – маркированный список-оглавление. Структуру списка с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```python\nimport solution from solution\n\nsolution(book)\n# Chapter 1: Sorting Spells\n# * 1.1 Bubble Sort\n# * 1.2 Insertion Sort\n# * 1.3 Merge Sort\n# * 1.4 Quick Sort\n# Chapter 2: Graphical Charms\n# * 2.1 Graph Traversal\n# * * 2.1.1 Breadth-First Search\n# * * 2.1.2 Depth-First Search\n# ...\n```\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `run()`, который принимает содержание книги в виде списка мап `List<Map<String, Object>>`. Метод должен вернуть строку – маркированный список-оглавление\n\nСтруктуру списка с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```java\nimport solutions.Solution;\n\nSolution.run(book);\n\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n","prepared_readme":"Напишите функцию, которая принимает содержание книги и возвращает маркированный список-оглавление.\n\n## solutions/solution.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде массива объектов и возвращает строку – маркированный список-оглавление следующего вида:\n\n```text\nChapter 1: Sorting Spells\n* 1.1 Bubble Sort\n* 1.2 Insertion Sort\n* 1.3 Merge Sort\n* 1.4 Quick Sort\nChapter 2: Graphical Charms\n* 2.1 Graph Traversal\n* * 2.1.1 Breadth-First Search\n* * 2.1.2 Depth-First Search\n...\n```\n\nСтруктуру объекта с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```javascript\nimport solution from './solution.js';\n\nsolution(book);\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n\n## solutions/solution.php\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде ассоциативного массива и возвращает строку – маркированный список-оглавление. Структуру ассоциативного массива с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```php\n<?php\n\nsolution(book);\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n\n## solutions/solution.py\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает содержание книги в виде списка словарей и возвращает строку – маркированный список-оглавление. Структуру списка с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```python\nimport solution from solution\n\nsolution(book)\n# Chapter 1: Sorting Spells\n# * 1.1 Bubble Sort\n# * 1.2 Insertion Sort\n# * 1.3 Merge Sort\n# * 1.4 Quick Sort\n# Chapter 2: Graphical Charms\n# * 2.1 Graph Traversal\n# * * 2.1.1 Breadth-First Search\n# * * 2.1.2 Depth-First Search\n# ...\n```\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `run()`, который принимает содержание книги в виде списка мап `List<Map<String, Object>>`. Метод должен вернуть строку – маркированный список-оглавление\n\nСтруктуру списка с содержанием книги можно посмотреть в файле *\\_\\_fixtures\\_\\_/table_of_contents.json*\n\n```java\nimport solutions.Solution;\n\nSolution.run(book);\n\n// Chapter 1: Sorting Spells\n// * 1.1 Bubble Sort\n// * 1.2 Insertion Sort\n// * 1.3 Merge Sort\n// * 1.4 Quick Sort\n// Chapter 2: Graphical Charms\n// * 2.1 Graph Traversal\n// * * 2.1.1 Breadth-First Search\n// * * 2.1.2 Depth-First Search\n// ...\n```\n","has_solution":true,"entity_name":"Деревья как концепция"},"units":[{"id":7043,"name":"theory","url":"/courses/algorithms-trees/lessons/concept/theory_unit"},{"id":8500,"name":"quiz","url":"/courses/algorithms-trees/lessons/concept/quiz_unit"},{"id":8488,"name":"exercise","url":"/courses/algorithms-trees/lessons/concept/exercise_unit"}],"links":[],"ordered_units":[{"id":7043,"name":"theory","url":"/courses/algorithms-trees/lessons/concept/theory_unit"},{"id":8500,"name":"quiz","url":"/courses/algorithms-trees/lessons/concept/quiz_unit"},{"id":8488,"name":"exercise","url":"/courses/algorithms-trees/lessons/concept/exercise_unit"}],"id":3111,"slug":"concept","state":"approved","name":"Деревья как концепция","course_order":110,"goal":"Разбираемся, что такое деревья, для чего они нужны, какие формы деревьев бывают и как их представляют","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Иерархические структуры окружают нас везде — например, структура управления предприятием, адреса, содержания в книгах. Иерархия встречается и в программировании, например, в работе со стандартными типами данных, такими как массивы. Так как такие данные нельзя разместить линейно, программисты используют деревья. Они считаются одной из важнейших нелинейных структур, которые встречаются при работе с алгоритмами.\n\nВ этом уроке мы поговорим о деревьях, узнаем, какие проблемы они решают, и какие характеристики и способы представления деревьев существуют.\n\n## Что такое деревья и для чего они нужны\n\nДеревья используют, чтобы отразить в памяти компьютера иерархические взаимосвязи. Их применяют для реестра Windows, XML-документов, DOM-структур HTML-страниц, родословных, каталогов запчастей или файловых систем. Например, так файловую структуру видят конечные пользователи:\n\n\n\nПри этом для программистов она выглядит иначе:\n\n\n\nЭто древовидное представление структуры. Из этой схемы можно сделать вывод, что **дерево** — это конечное множество, которое состоит из **вершин** или **узлов**, а еще есть выделенный узел — **корень дерева**. В примере выше вершины — это все папки, а корень дерева — «Новая папка».\n\nКаждый узел содержит данные и ссылки на другие непересекающиеся между собой деревья. В этом случае каждая папка в дереве, от которой исходят другие данные, является для них **корневым узлом**. При этом эти данные образуют поддерево основного дерева.\n\nПомимо представления иерархических взаимосвязей, деревья применяют в следующих случаях:\n\n* Организация быстрого поиска в отсортированных данных, например, в индексах баз данных\n* Кластеризация данных. Возможность разбивать данные на кластеры применяется в базах данных и машинном обучении\n* Решение сложных арифметических выражений. Дерево используется, чтобы хранить порядок выполнения операций, значений аргументов и промежуточных результатов\n* Алгоритмы принятия решений. Дерево решений — инструмент интеллектуального анализа данных и проведения предсказаний. Он считается более простым в работе инструментом, чем нейросети, так как формулирует правила на естественном языке\n* Сетевое взаимодействие. Деревья используют для маршрутизации и работы механизмов определения IP-адресов по URL сайта, например, DNS-сервера\n\nДеревья часто используют в проектах по разработке программ. Как результат — разработчики выделили терминологию описания узлов и формы деревьев.\n\n## Какие узлы есть в деревьях\n\nТак как внешний вид дерева схож с генеалогическим деревом, термины генеалогии применяются и в программировании. Например, в отношении алгоритмических деревьев можно услышать такие термины, как «основатель династии», «мама», «ребенок», «брат», «двоюродный дядя». При этом существуют стандартизированные именования для отношений узлов:\n\n* **Родительский узел** или **предок** — узел, который находится на первом уровне иерархии\n* **Дочерний узел** или **потомок** — узел, на который есть ссылки из рассматриваемого узла\n* **Корневой узел** — узел, на который нет ссылок из других узлов\n* **Сестринские узлы** — два узла, у которых общие родители\n* **Листовой узел**, **лист дерева** или **терминальный узел** — узел, у которого количество поддеревьев равно нулю\n* **Узел ветвления** или **внутренняя вершина** — узел, у которого есть дочерние структуры\n\nКоличество поддеревьев у узла называется его **степенью***. Максимальное значение степени узла — **степень дерева**. Если степень дерева равна двум, значит, у каждого узла может быть не более двух потомков:\n\n\n\n## Какие формы деревьев бывают\n\nИз-за определенных особенностей задач и алгоритмов, разработчики применяют разные формы деревьев со своими особенностями организации вершин:\n\n* **Упорядоченное дерево** — дерево, в котором все вершины отсортированы. Такие деревья еще называются **плоскими**, так как при последовательном обходе вершин получится отсортированный массив:\n\n\n\n Далее в курсе мы будем считать все деревья упорядоченными, если в условии не сказано обратное.\n\n* **Полное дерево** — дерево, в котором количество дочерних узлов у каждой внутренней вершины равно степени дерева:\n\n\n\n* **Завершенное дерево** – дерево, у которого каждый уровень, кроме последнего, является полным:\n\n\n\n* **Идеальное дерево** — полное дерево, у которого все терминальные узлы располагаются на одном уровне:\n\n\n\nМы познакомились с основной терминологией, которая используется при работе с деревьями. Теперь рассмотрим, какие есть способы представления деревьев в графическом виде и в коде.\n\n## Как могут выглядеть деревья\n\nЧтобы изображать иерархические структуры, используют следующие варианты:\n\n* Древовидное схематическое представление\n* Круги Эйлера\n* Списки с отступами\n* Код\n\nРазберем каждый способ изображения дерева.\n\n### Древовидное схематическое представление\n\nВ качестве основного способа представления деревьев используют имена, номера вершин или содержимое полезных данных узла. Они соединяются линиями, которые обозначают связи между вершинами:\n\n\n\nЭтот способ похож на природные деревья, только в информатике корень дерева обычно рисуется вверху схемы.\n\n### Круги Эйлера\n\nМы можем изобразить алгоритмическое дерево по правилам теории множеств с помощью кругов Эйлера:\n\n\n\nДанный способ на практике встречается достаточно редко, так как поддеревья обычно не пересекаются между собой. В такой ситуации использование схематичного представления более наглядно для человека.\n\n### Списки с отступами\n\nИерархическую связь можно изображать через пронумерованный список с отступами, где отступ или номер строки будут означать ее уровень:\n\n\n\nТакой формат используют при работе с книгой, так как оглавление — это дерево.\n\n### Код\n\nЧтобы работать с деревьями, их нужно уметь не только рисовать, но и хранить в памяти компьютера.\n\nВ виде кода мы получаем следующий класс узла:\n\n```javascript\nclass BinaryTreeNode {\n constructor(value, parent) {\n this.child1 = null\n this.child2 = null\n this.parent = parent\n this.value = value\n }\n}\n ```\n\n```java\nclass BinaryTreeNode {\n public BinaryTreeNode child1 = null;\n public BinaryTreeNode child2 = null;\n public BinaryTreeNode parent;\n public Object value;\n\n BinaryTreeNode(Object value, BinaryTreeNode parent) {\n this.parent = parent;\n this.value = value;\n }\n}\n```\n\n```python\nclass BinaryTreeNode:\n def __init__(self, value, parent=None):\n self.child1 = None\n self.child2 = None\n self.parent = parent\n self.value = value\n```\n\n```php\n<?php\nclass BinaryTreeNode\n{\n public ?BinaryTreeNode $child1 = null;\n public ?BinaryTreeNode $child2 = null;\n public ?BinaryTreeNode $parent = null;\n public $value;\n\n public function __construct($value, BinaryTreeNode $parent)\n {\n $this->parent = $parent;\n $this->value = $value;\n }\n}\n```\n\nЧтобы организовать из узла дерево, нужно добавить методы, которые заполняют ссылки на поддеревья `child1` и `child2`. Как реализуются деревья в коде, разберем подробно в следующих уроках.\n\nПрежде чем писать код дерева, разработчики часто изображают иерархию графически, чтобы видеть полную картину взаимосвязей.\n\n## Выводы\n\nМы познакомились с такой структурой данных как деревья. Они используются, чтобы хранить информацию в иерархическом виде, индексировать и кластеризировать большие последовательности данных.\n\nВ этом уроке мы также познакомились с основными обозначениями узлов, а также различными формами деревьев. Еще рассмотрели способы представления дерева — можно изображать иерархию с помощью схемы, кругов Эйлера, списком с отступами, а также в коде.\n"},"id":288,"slug":"algorithms-trees","challenges_count":0,"name":"Алгоритмы на деревьях","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"В этом курсе вы научитесь работать с древовидными структурами данных. Вы узнаете, зачем нужны деревья, как с их помощью сделать быстрый поиск в словаре и на карте, и почему базы данных работают так быстро. Еще познакомитесь со специальными видами деревьев, которые используются в браузере и компиляторах.","kind":"additional","updated_at":"2026-02-12T14:03:27.752Z","language":"javascript","duration_cache":37800,"skills":["Создавать алгоритмы для древовидных структур","Использовать рекурсию для обхода деревьев","Выбирать эффективную структуру данных для решения задач","Создавать поиск ближайших мест"],"keywords":[],"lessons_count":8,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyNjcsInB1ciI6ImJsb2JfaWQifX0=--9de1210451f6271cde3b5c010530f2d03e819ef5/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":34,"slug":"algorithms","title":"Алгоритмы и структуры данных","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4000,"duration_in_months":2},"id":56,"slug":"algorithms","title":"Алгоритмы и структуры данных","subtitle":"Навык, который увеличит ваши шансы пройти алгоритмическое интервью в международные компании на 80%","subtitle_for_lists":"Алгоритмы для собеседований","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"algorithms","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/algorithms-trees/lessons/balancing/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы на деревьях</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Балансировка</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Балансировка","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Алгоритмы на деревьях"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Древовидные структуры достаточно гибкие по своей задумке. Один и тот же набор значений можно расположить в виде дерева, если использовать разные топологические решения. В итоге два дерева с одинаковым содержимым будут обладать противоположными характеристиками, например, по скорости поиска узлов.</p>
<p>Ученые еще в середине прошлого века заинтересовались вопросом оптимизации хранения данных в деревьях. Они хотели обеспечить максимальную скорость поиска в них. Так появились сбалансированные деревья.</p>
<p>В этом уроке мы детально познакомимся с балансировкой деревьев, способами ребалансировки при добавлении новых узлов, а также рассмотрим новые виды древовидных структур.</p>
<h2 id="heading-2-1">Сбалансированные деревья</h2>
<p><strong>Идеальная сбалансированность</strong> — это свойство дерева, при котором все его уровни, иногда кроме последнего, полностью заполнены.</p>
<p>Для сравнения рассмотрим два бинарных дерева поиска, которые будут представлять собой одну последовательность элементов — 1,2,3,4,5,6. Из этой последовательности можно построить несколько деревьев, например:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyODgsInB1ciI6ImJsb2JfaWQifX0=--6782c86576b9ef0dda722f7e8d0ec60a3dab018d/01.png" alt="01" loading="lazy"/></p>
<p>В дереве (б) каждый из уровней, кроме левой ветки узла 1, заполнены. Значит, дерево (б) — идеально сбалансированное дерево. Что нельзя сказать о дереве (а).</p>
<p>Дерево (а) и дерево (б) можно отнести к бинарным деревьям поиска. Но для худшего случая — поиска в дереве элемента №6 в дереве (а) — требуется выполнить шесть операций перехода между вершинами, а в случае (б) — только три.</p>
<p>Дерево с максимально возможной высотой (а) называется <strong>разбалансированным</strong> или <strong>вырожденным деревом</strong>. Оно не отличается от двусвязного списка, значит, теряет свое основное преимущество — скорость поиска.</p>
<p>Для разбалансированных деревьев скорость составляет <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(N)</code>. А для идеально сбалансированного дерева (б) поиск будет завершен за число шагов, которые не превышают высоту дерева или за <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(logN)</code>.</p>
<p>Состояние идеальной сбалансированности в дереве трудно поддерживать. Любое добавление или удаление узла в сбалансированном дереве может привести к ситуации, когда дерево выйдет из идеально сбалансированного состояния. В таком случае скорость поиска будет значительно деградировать после каждой вставки или удаления узла.</p>
<p>Чтобы вернуть дерево в сбалансированный вид, его перестраивают после каждой манипуляции с составом узлов. Рассмотрим пример с добавлением элемента №7 в наше дерево. Это можно сделать несколькими способами:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyODksInB1ciI6ImJsb2JfaWQifX0=--bbf42b23d764195ed013ea04bf69c2bfdd3bb125/02.png" alt="02" loading="lazy"/></p>
<p>В случае (б) дерево вышло из состояния идеальной сбалансированности, так как оба нижних уровня не заполнены полностью. В таком случае поиск элемента №7 будет выполнен за четыре операции.</p>
<p>В случае (а) элементы были перераспределены, чтобы сохранить сбалансированное состояние. В таком случае поиск элемента №7 будет выполнен всего за три операции.</p>
<p>На таких маленьких примерах выигрыш в одну операцию во время поиска может показаться слишком незначительным, чтобы постоянно перестраивать дерево. Но при работе с деревьями из миллионов узлов и повышенных требованиях к скорости выборки такое перестроение может себя оправдать. Пользователям очень нравится, когда приложение работает со скоростью человеческой мысли.</p>
<p>Ресурсы вычислительных машин не безграничны. Чтобы снизить нагрузку на них и одновременно с этим максимально сохранить преимущества идеально сбалансированного дерева, придумали новые виды деревьев. Среди них особенно выделяются АВЛ-деревья и красно-черные деревья, с которыми мы познакомимся подробнее.</p>
<h3 id="heading-3-2">АВЛ-деревья</h3>
<p>Этот вид деревьев представили в 1962 году двое советских ученых: Адельсон-Вельский Г.М. и Ландис Е.М. Именно по сокращению от их фамилий <strong>АВЛ-деревья</strong> и получили такое название.</p>
<p>АВЛ-деревья отличаются от идеально сбалансированных. АВЛ-дерево считается сбалансированным, если для каждого узла дерева высота его правого и левого поддеревьев отличаются не более чем на единицу. Если модификация структуры узлов приводит к нарушению сбалансированности дерева, то необходимо выполнить его балансировку.</p>
<p>Поиск в АВЛ-дереве выполняется аналогично работе с бинарным деревом поиска. При этом благодаря поддержке возможности ребалансировки при вставки и удалении узлов в АВЛ-деревьях есть особенности реализации.</p>
<p>В качестве индикатора наличия разбалансированности в поддереве на узел выносят показатель баланса, который принимает значения от -1 до +1. Их значения:</p>
<ul>
<li>–1 — в правом поддереве больше высота</li>
<li>0 — поддеревья равной высоты</li>
<li>+1 — высота больше в левом поддереве</li>
</ul>
<p>В итоге код нашего узла принимает следующий вид:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">class AvlTreeNode {
constructor(value, parent) {
this.left = null // ссылка на левый дочерний узел
this.right = null // ссылка на правый дочерний
this.balanceFactor = 0 // показатель сбалансированности
this.parent = parent // ссылка на родителя
this.value = value // полезная нагрузка
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Этот фрагмент кода определяет базовый скелет узла нашего будущего дерева. Чтобы превратить его в полноценное АВЛ-дерево, нужно добавить возможность искать и модифицировать узлы. Операцию поиска можно переиспользовать из бинарных деревьев поиска, а изменение в структуре дерева потребует дополнительной частичной ребалансировки.</p>
<p>Далее мы подробно поговорим об операциях добавления и удаления узлов в АВЛ-деревьях.</p>
<h3 id="heading-3-3">Модификация структуры узлов</h3>
<p>Добавить новую вершину можно с помощью рекурсивного спуска к месту вставки нового узла. Если возвращаться к корневой вершине через каждый промежуточный узел, то добавивший новую вершину метод модифицирует значение balanceFactor. Если новое значение выходит из допустимого диапазона, то выполняется ребалансировка данного поддерева.</p>
<p>Ребалансировка деревьев осуществляется при помощи специальных механизмов — <strong>методов вращения</strong>. Вращения бывают двух видов: левое и правое.</p>
<p>Вращение вправо выполняется за три шага:</p>
<ol>
<li>Текущий корень поддерева (D) заменяется на левый дочерний узел (B)</li>
<li>Предыдущий корень (D) становится правым дочерним узлом для (B)</li>
<li>Предыдущее правое поддерево узла (B) становится левым поддеревом для (D)</li>
</ol>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTAsInB1ciI6ImJsb2JfaWQifX0=--ef62fb7d558dc11e095a31763d5a9c23cc78dfca/03.png" alt="03" loading="lazy"/></p>
<p>Вращение влево выполняется аналогично:</p>
<ol>
<li>Текущий корень поддерева (D) заменяется на правый дочерний узел (C)</li>
<li>Предыдущий корень (D) становится левым дочерним узлом для (C)</li>
<li>Предыдущее левое поддерево узла (C) становится правым поддеревом для (D)</li>
</ol>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTEsInB1ciI6ImJsb2JfaWQifX0=--1322e9014de585c3a0bb1584d3283ec863c1623d/04.png" alt="04" loading="lazy"/></p>
<p>В зависимости от того, куда добавили новую вершину, возможны различные комбинации выполнения вращений. Они помогут вернуть дерево в сбалансированное состояние.</p>
<p>Всего выделяется четыре варианта развития событий:</p>
<ul>
<li>Левое поддерево левой дочерней вершины</li>
<li>Левое поддерево правой дочерней вершины</li>
<li>Правое поддерево левой дочерней вершины</li>
<li>Правое поддерево правой дочерней вершины</li>
</ul>
<p>Рассмотрим пример, который описывает первый случай — вставку в левое поддерево левой дочерней вершины. Изображенные треугольники представляют собой сбалансированные АВЛ-поддеревья. Они могут содержать большое количество вершин. У вершины В дерево не сбалансировано, поскольку поддерево А1 в вершине А на два уровня выше, чем поддерево В2:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTIsInB1ciI6ImJsb2JfaWQifX0=--3a3cc67f33baa18368c9da483d75fd279340f265/05.png" alt="05" loading="lazy"/></p>
<p>Чтобы сбалансировать дерево, необходимо совершить правое вращение — заменить вершину В вершиной А и сделать поддерево А2 левым поддеревом вершины В. После такого преобразования наше поддерево примет следующий вид:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTMsInB1ciI6ImJsb2JfaWQifX0=--070edce58fad23b71ecf08cbc15027a012bb2507/06.png" alt="06" loading="lazy"/></p>
<p>Четвертый сценарий будет выглядеть аналогично кроме замены способа вращения на левое.</p>
<p>Для второго и третьего сценариев необходимо выполнить вращение дважды:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTQsInB1ciI6ImJsb2JfaWQifX0=--a193a17bc6ee7251d50f5b7cbad28bc334266b84/07.png" alt="07" loading="lazy"/></p>
<p>Удаление узлов также осуществляется при помощи механизмов вращения. При возврате во время рекурсивного спуска осуществляется вычисление balanceFactor. Если он отклоняется от допустимых значений, то выполняется ребалансировка аналогично добавлению узла.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTUsInB1ciI6ImJsb2JfaWQifX0=--137e68fe2b69fbcc6af0d2af80d0896b65bdefa6/08.png" alt="08" loading="lazy"/></p>
<p>Другим видом автоматически балансирующихся деревьев являются красно-черные деревья, с которыми мы познакомимся дальше.</p>
<h3 id="heading-3-4">Красно-черные деревья</h3>
<p><strong>Красно-черные деревья</strong> — одни из наиболее активно используемых на практике самобалансирующихся деревьев. Они так называются из-за наличия на узле дополнительного поля, в котором размещается цвет узла. В качестве стандартных цветов используют обычно красные и черные узлы, а сам цвет узла используется во время балансировки.</p>
<p>Так как красно-черные деревья самобалансирующиеся, то среднее и худшее время поиска тоже составляют <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(logN)</code>. А операции вставки и удаления узла могут потребовать поворот поддерева.</p>
<p>Для красно-черного дерева наш код узла примет следующий вид:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">class RBTreeNode {
constructor(value, parent) {
this.left = null // ссылка на левый дочерний узел
this.right = null // ссылка на правый дочерний
this.isRed = false // цвет узла. Если не красный, то считаем что узел черный
this.parent = parent // ссылка на родителя
this.value = value // полезная нагрузка
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>В отличие от других видов деревьев в листовых узлах красно-черных деревьев не хранят полезную нагрузку. А цвет листовых узлов без данных всегда считается черным. Такая особенность позволяет считать ссылку на null валидным узлом. Эта особенность позволит сэкономить память. А само дерево принимает следующий вид:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyOTYsInB1ciI6ImJsb2JfaWQifX0=--49c8df23481e3d6d6ab9af570c37afbbc38fed81/09.png" alt="09" loading="lazy"/></p>
<p>Помимо особенностей работы с листовыми узлами к свойствам красно-черного так же относят:</p>
<ol>
<li>Корень красно-черного дерева черный</li>
<li>Две красные вершины не могут идти подряд ни на одном пути. Оба потомка каждого красного узла — черные</li>
<li>Для каждой вершины, в каждом исходящем из нее пути, одинаковое число черных вершин</li>
</ol>
<p>Иногда при работе с узлами красно-черного дерева используют <strong>черную высоту</strong> — количество черных вершин на исходящих из нее путях, не включая саму исходную вершину.</p>
<p>Чтобы вставить узел, мы сначала ищем в дереве место, куда его следует добавить. Новый узел всегда добавляется как лист, поэтому оба его потомка являются пустыми узлами и предполагаются черными. После вставки красим узел в красный цвет. Далее смотрим на предка и проверяем, не нарушается ли свойства дерева, которые описали выше. Если необходимо, мы перекрашиваем узел и производим поворот, чтобы сбалансировать дерево.</p>
<p>Сбалансированность этих деревьев хуже, чем у АВЛ-деревьев. При этом затраты на поддержание состояния сбалансированности и потребление памяти меньше, а операцию поиска можно выполнять одновременно с выполнением перестроения дерева.</p>
<p>Благодаря этим преимуществам сфера применения красно-черных деревьев существенно шире. Так, например, в стандартной библиотеке шаблонов языка C++ STL и TreeMap в Java применяются именно красно-черные деревья.</p>
<h2 id="heading-2-5">Выводы</h2>
<p>Хранение данных в идеально сбалансированных деревьях позволяет достичь скорости поиска <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(logN)</code>. Но чтобы поддерживать идеальную сбалансированность, требуются существенные ресурсные затраты на полное перестроение дерева при добавлении нового элемента.</p>
<p>Чтобы решить эту проблему, были придуманы новые виды деревьев: АВЛ-деревья и красно-черные деревья. Использование таких подходов поможет сохранить преимущества бинарных деревьев поиска и не тратить большое количество ресурсов. Так можно поддерживать идеальную согласованность.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/algorithms?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы и структуры данных</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы для собеседований</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png" alt="Алгоритмы и структуры данных" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/algorithms-trees/lessons/balancing/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 8</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/algorithms-trees/lessons/balancing/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-CdBlNCiQ.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>