<!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:28:14 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="97-0aIefiq5yV12f6AfSc9la_ujkWuEgjphuMB0MZyEYbn9fdeEnzsQUeQfkCCIEGVPTQuxtH4IzePRkTwuATw";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>KD-деревья | Алгоритмы на деревьях</title>
<meta name="description" content="KD-деревья / Алгоритмы на деревьях: Познакомимся с KD-деревьями и особенностями реализации операций с ними">
<link rel="canonical" href="https://ru.hexlet.io/courses/algorithms-trees/lessons/kdtrees/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="KD-деревья">
<meta property="og:title" content="Алгоритмы на деревьях">
<meta property="og:description" content="KD-деревья / Алгоритмы на деревьях: Познакомимся с KD-деревьями и особенностями реализации операций с ними">
<meta property="og:url" content="https://ru.hexlet.io/courses/algorithms-trees/lessons/kdtrees/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="zA9447LxsHr3cW7jg7bzToXoI1NH5EC5jYqYVMZufmEj3rPUQI8dGkEySnuPuQM5ReEO-U_TvhswagIAlGmZDw" />
<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:28:14.493Z","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":"E6u4R1HF5S2ospU-OihODyrXpJ7CGGdI7wzeW13f0Kv8enNwo7tITR7xsaY2J7546t6JNMovmepS7EQPD9g3xQ","topics":[{"id":99211,"title":"А это окей, что я могу поменять файл index.test.js и пройти все тесты с неправильным кодом?)","plain_title":"А это окей, что я могу поменять файл index.test.js и пройти все тесты с неправильным кодом?) ","creator":{"public_name":"Stepan Savin","id":610403,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":190588,"body":"Степан, файл с тестами умышленно сделан доступным для редактирования, чтобы дать вам больше возможностей отлаживать свой код. Можно дописать свой тест или временно закоментировать один из имеющихся, чтобы разобраться, где ошибка в коде. Хочу заметить, что меняя код тестов на заведомо неверный ради прохождения тестов, вы вредите сами себе. Ведь основная цель - получить знания, а не просто номинально пройти упражнение","topic_id":99211}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":95215,"title":"После\n\"Структура KD-дерева не отличается от бинарного дерева. Поэтому методы удаления и поиска узлов работают так же, как в бинарном дереве:\"\nидет пример, в котором допущена ошибка - в методе #insertNode на верхнем уровне два одинаковых условия \"if (value[this.dimension] < parentNode.obj[parentNode.dimension]){\"","plain_title":"После \"Структура KD-дерева не отличается от бинарного дерева. Поэтому методы удаления и поиска узлов работают так же, как в бинарном дереве:\" идет пример, в котором допущена ошибка - в методе #insertNode на верхнем уровне два одинаковых условия \"if (value[this.dimension] < parentNode.obj[parentNode.dimension]){\" ","creator":{"public_name":"Сергей","id":362906,"is_tutor":false},"comments":[{"creator":{"public_name":"Elena Gromova","id":548102,"is_tutor":true},"id":185911,"body":"Добрый день!\n\nДа, действительно, поправили","topic_id":95215}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":95730,"title":"Не совсем понял решение учителя, т.к. все равно обходятся все узлы дерева? Зачем тогда нужен dimension?","plain_title":"Не совсем понял решение учителя, т.к. все равно обходятся все узлы дерева? Зачем тогда нужен dimension? ","creator":{"public_name":"Кирилл Петров","id":165983,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":186503,"body":"Когда мы обходим дерево, мы сравниваем координаты текущей точки с центральной точкой и используем это сравнение, чтобы определить, в каком направлении (x или y) продолжить поиск. Это происходит благодаря переменной dimension, которая указывает на текущее измерение (x или y). Это позволяет нам эффективно обходить дерево и не перебирать все узлы.\n\nИзначально, мы сравниваем расстояние между центральной точкой и текущей точкой с квадратом радиуса. Если расстояние оказывается внутри радиуса, мы добавляем текущую точку в результат. Затем, в зависимости от значения `dimension`, мы делаем выбор, в каком поддереве искать дальше - либо в левом, либо в правом. Таким образом, мы оставляем за пределами нашего интереса только часть дерева, в которой могут находиться точки внутри заданного радиуса.\n\nКонечно, при рекурсивных вызовах некоторые узлы могут быть посещены несколько раз, но в итоге алгоритм все равно обходит только часть дерева, уменьшая количество узлов, который нужно рассмотреть.\n","topic_id":95730},{"creator":{"public_name":"Кирилл Петров","id":165983,"is_tutor":false},"id":186506,"body":"**Nikolai Gagarinov**, я так и предполагал, но не осилил с направлением сделать и просто сделал обход дерева и у меня получилось столько же console.log'ов как и в решении учителя.","topic_id":95730}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":100823,"title":"Добрый день.\n1. Подниму тему с предыдущего топика. Все таки в решении преподавателя обходятся все ноды. В ответе поддержки говорится что некоторые ноды могут проверяться несколько раз, но здесь проверяются все ноды, даже те, которые явно не нужны. Например, если взять за основу второй тест и добавить туда еще точки с координатами:\n(100, 100), (101, 101), (102, 102), (103, 103), (104, 104), (105, 105), (106, 106), (107, 107), (108, 108)\nто они тоже будут проверяться. Но если посмотреть само дерево, то в этом смысла нет\n2. Предлагаю добавить в задание еще тестов. Мое первое решение прошло оба теста, но было совсем не правильным. ","plain_title":"Добрый день. 1. Подниму тему с предыдущего топика. Все таки в решении преподавателя обходятся все ноды. В ответе поддержки говорится что некоторые ноды могут проверяться несколько раз, но здесь проверяются все ноды, даже те, которые явно не нужны. Например, если взять за основу второй тест и добавить туда еще точки с координатами: (100, 100), (101, 101), (102, 102), (103, 103), (104, 104), (105, 105), (106, 106), (107, 107), (108, 108) то они тоже будут проверяться. Но если посмотреть само дерево, то в этом смысла нет 2. Предлагаю добавить в задание еще тестов. Мое первое решение прошло оба теста, но было совсем не правильным. ","creator":{"public_name":"Антон","id":774473,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":192770,"body":"**Антон**, спасибо!","topic_id":100823},{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":192690,"body":"**Антон**, \n\nспасибо, что обратили внимание. Создала тикет на доработку тестов. Приложите, пожалуйста, ваше решение, это поможет нам в совершенствовании тестов.","topic_id":100823},{"creator":{"public_name":"Антон","id":774473,"is_tutor":false},"id":192742,"body":"Мое текущее решение на Python https://ru.hexlet.io/code_reviews/1463381\nЗакомментировал блок кода и тесты все равно проходят. Но если во второй тест добавить точку (-100, -100), то решение его уже не пройдет, пока не раскомментировать блок кода\n","topic_id":100823}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":102761,"title":"Тут так мало обсуждений... Как эту тему понять на практике, если здесь нет ни одного объяснения кода, основное - поиск ближайшего соседа, просто вкинули код и всё. Хоть капельку объяснений... \n\n```\ndef nearestSearch(point, node):\n bestChild = None\n dimension = dimensions[node.dimension]\n ownDistance = metric(point, node.obj)\n linearPoint = {}\n linearDistance = 0\n otherChild = None\n\n for i in range(len(dimensions)):\n if i == node.dimension:\n linearPoint[dimensions[i]] = point[dimensions[i]]\n else:\n linearPoint[dimensions[i]] = node.obj[dimensions[i]]\n\n linearDistance = metric(linearPoint, node.obj)\n\n if node.right == None and node.left == None:\n if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]:\n saveNode(node, ownDistance)\n return\n\n if node.right == None:\n bestChild = node.left\n elif node.left == None:\n bestChild = node.right\n else:\n if point[dimension] < node.obj[dimension]:\n bestChild = node.left\n else:\n bestChild = node.right\n\n nearestSearch(point, bestChild)\n\n if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]:\n saveNode(node, ownDistance)\n\n if bestNodes.qsize() < maxNodes or abs(linearDistance) < bestNodes.queue[0][1]:\n if bestChild == node.left:\n otherChild = node.right\n else:\n otherChild = node.left\n if otherChild != None:\n nearestSearch(point, otherChild)\n```\nкак это понять самостоятельно ?) я уже на моменте, где появился `dimensions ` запутался... откуда взялась эта переменная ","plain_title":"Тут так мало обсуждений... Как эту тему понять на практике, если здесь нет ни одного объяснения кода, основное - поиск ближайшего соседа, просто вкинули код и всё. Хоть капельку объяснений... def nearestSearch(point, node): bestChild = None dimension = dimensions[node.dimension] ownDistance = metric(point, node.obj) linearPoint = {} linearDistance = 0 otherChild = None for i in range(len(dimensions)): if i == node.dimension: linearPoint[dimensions[i]] = point[dimensions[i]] else: linearPoint[dimensions[i]] = node.obj[dimensions[i]] linearDistance = metric(linearPoint, node.obj) if node.right == None and node.left == None: if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]: saveNode(node, ownDistance) return if node.right == None: bestChild = node.left elif node.left == None: bestChild = node.right else: if point[dimension] < node.obj[dimension]: bestChild = node.left else: bestChild = node.right nearestSearch(point, bestChild) if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]: saveNode(node, ownDistance) if bestNodes.qsize() < maxNodes or abs(linearDistance) < bestNodes.queue[0][1]: if bestChild == node.left: otherChild = node.right else: otherChild = node.left if otherChild != None: nearestSearch(point, otherChild) как это понять самостоятельно ?) я уже на моменте, где появился dimensions запутался... откуда взялась эта переменная ","creator":{"public_name":"Алексей Олещенко","id":582226,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":195119,"body":"Приветствую, Алексей. Все ваши вопросы и замечания по курсу из всех топиков оформил, они не потеряются. Все учтем при переработке. Спасибо за такую обширную и подробную обратную связь","topic_id":102761}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":104715,"title":"Согласен с последним комментарием. Самую основную функцию и алгоритм и вышвырнули без объяснения, без ничего. Думайте как хотите.","plain_title":"Согласен с последним комментарием. Самую основную функцию и алгоритм и вышвырнули без объяснения, без ничего. Думайте как хотите. ","creator":{"public_name":"Николай Мельников","id":614078,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":197403,"body":"Добрый день, спасибо за замечание, дополним урок.","topic_id":104715}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}},{"id":106983,"title":"Очень не хватает разбора метода поиска ближайших соседей. Пары комментариев в огромном куске кода- недостаточно.\nЧто такое metric? Зачем нам нужен квадратный корень для ее нахождения?\n```\nmetric(point1, point2) {\n return Math.sqrt(\n Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2),\n );\n }\n```\nЗа что отвечает каждая из этих переменных?\n```\nlet ownDistance = this.metric(point, node.obj);\n let linearPoint = {};\n let linearDistance;\n let bestChild, otherChild;\n```","plain_title":"Очень не хватает разбора метода поиска ближайших соседей. Пары комментариев в огромном куске кода- недостаточно. Что такое metric? Зачем нам нужен квадратный корень для ее нахождения? metric(point1, point2) { return Math.sqrt( Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2), ); } За что отвечает каждая из этих переменных? let ownDistance = this.metric(point, node.obj); let linearPoint = {}; let linearDistance; let bestChild, otherChild; ","creator":{"public_name":"Никита Ласьковых","id":705254,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":200456,"body":"Добрый день.\n\nПередал обратную связь автору курса, по текущему попытаюсь пояснить:\n\n\nmetric - это расстояни е между точками [в евклидовом пространстве](https://ru.wikipedia.org/wiki/Евклидова_метрика). \n\nПо переменным:\n\n1. **`ownDistance`**: \n - Хранит расстояние между искомой точкой и текущим узлом дерева.\n\n2. **`linearPoint`**: \n - Пустой объект для хранения координат точки, которую сравниваем с узлом.\n\n3. **`linearDistance`**: \n - Расстояние между `linearPoint` и объектом текущего узла. Помогает решить, стоит ли искать в другом поддереве.\n\n4. **`bestChild` и `otherChild`**: \n - Ссылки на дочерние узлы. `bestChild` — поддерево с вероятным ближайшим соседом, `otherChild` — другое поддерево для проверки.\n","topic_id":106983}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"KD-деревья","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2499,"slug":"algorithms_trees_kdtrees_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"В данном упражнении мы попытаемся создать прогностическую модель заболеваемости. Для этого используется KD-дерево, где точки представляют здоровых пациентов. Также имеется точка, которая представляет заболевшего пациента с координатами x и y, а также радиус опасной зоны вокруг него. Наша задача - оценить количество пациентов, находящихся в опасной зоне - окружности с определенным радиусом.\n\n## solutions/solution.js\n\nРеализуйте функцию `findPointsInRadius()`, которая принимает три параметра:\n\n* KD дерево с точками, которые представляют здоровых пациентов\n* Точку – центр окружности, которая представляет заболевшего пациента. Точка - это объект со свойствами *x* и *y*\n* Радиус окружности – опасная зона вокруг заболевшего пациента\n\nФункция должна вернуть массив всех точек из дерева, которые находятся внутри окружности\n\n```javascript\n// Массив точек\nconst points = [\n { x: 1, y: 2 },\n { x: 3, y: 4 },\n { x: 5, y: 6 },\n { x: 7, y: 8 },\n];\n\n// Строим дерево\n// Вам этого делать не нужно, функция принимает уже готовое дерево\n// Пример приведен для наглядности\nconst tree = buildTree(points, 0, ['x', 'y'], null);\n\n// Точка-центр окружности\nconst dangerousPoint = { x: 4, y: 5 };\n// Радиус окружности - размер опасной зоны\nconst radius = 2;\n\n\nfindPointsInRadius(tree, dangerousPoint, radius);\n// [\n// { x:3, y:4 },\n// { x:5, y:6 }\n// ]\n\n```\n\nДобавьте в файл импорт\n\n```javascript\nimport buildKDTree from '../helpers/helpers.js';\n```\n\nи создайте в файле функцию со следующим кодом\n\n```javascript\nexport default (points, centerPoint, radius) => {\n const tree = buildKDTree(points, 0, ['x', 'y'], null);\n return findPointsInRadius(tree, centerPoint, radius);\n};\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/solution.php\n\nУсловия такие же как для JavaScript\n\n```php\n<?php\n\nfindPointsInRadius($tree, $dangerousPoint, $radius);\n// [\n// { x:3, y:4 },\n// { x:5, y:6 }\n// ]\n```\n\nПодключите файл\n\n```php\nrequire_once(__DIR__ . '/../helpers/helpers.php');\n```\n\nи создайте в файле функцию со следующим кодом\n\n```php\nfunction solution($points, $centerPoint, $radius)\n{\n $tree = buildTree($points, 0, ['x', 'y'], null);\n return findPointsInRadius($tree, $centerPoint, $radius);\n}\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/solution.py\n\nРеализуйте функцию `find_points_in_radius()`, остальные условия такие же, как для JavaScript\n\n```python\nfind_points_in_radius(tree, dangerousPoint, radius);\n# [\n# { x:3, y:4 },\n# { x:5, y:6 }\n# ]\n```\n\nДобавьте в файл импорты\n\n```python\nimport os\nimport sys\n\nsys.path.append(os.path.abspath('/usr/src/app/'))\n\nfrom helpers.helpers import build_tree\n```\n\nи создайте в файле функцию со следующим кодом\n\n```javascript\ndef solution(points, center_point, radius):\n tree = build_tree(points, 0, ['x', 'y'], None)\n return find_points_in_radius(tree, center_point, radius)\n\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `findPointsInRadius()`. Метод принимает три параметра:\n\n* KD дерево с точками, объект класса `KDTreeNode`\n* Точку – центр окружности. Точка – это словарь `Map<String, Integer>` со свойствами *x* и *y*\n* Радиус окружности, целое число\n\nФункция должна вернуть список `List<Map<String, Integer>>` всех точек из дерева, которые находятся внутри окружности\n\n```java\nMap<String, Integer> dangerousPoint = Map.of(\n \"x\", 4,\n \"y\", 5\n);\n\nvar radius = 2;\n\nList<Map<String, Integer>> risked = Solution.findPointsInRadius(tree, dangerousPoint, radius);\nSystem.out.println(risked); //=> [{y=4, x=3}, {y=6, x=5}]\n\n\nДобавьте в класс метод со следующим кодом\n\n```java\npublic static List<Map<String, Integer>> run(\n List<Map<String, Integer>> points, Map<String, Integer> center, int radius) {\n return helpers.Helper.run(points, center, radius);\n}\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n","prepared_readme":"В данном упражнении мы попытаемся создать прогностическую модель заболеваемости. Для этого используется KD-дерево, где точки представляют здоровых пациентов. Также имеется точка, которая представляет заболевшего пациента с координатами x и y, а также радиус опасной зоны вокруг него. Наша задача - оценить количество пациентов, находящихся в опасной зоне - окружности с определенным радиусом.\n\n## solutions/solution.js\n\nРеализуйте функцию `findPointsInRadius()`, которая принимает три параметра:\n\n* KD дерево с точками, которые представляют здоровых пациентов\n* Точку – центр окружности, которая представляет заболевшего пациента. Точка - это объект со свойствами *x* и *y*\n* Радиус окружности – опасная зона вокруг заболевшего пациента\n\nФункция должна вернуть массив всех точек из дерева, которые находятся внутри окружности\n\n```javascript\n// Массив точек\nconst points = [\n { x: 1, y: 2 },\n { x: 3, y: 4 },\n { x: 5, y: 6 },\n { x: 7, y: 8 },\n];\n\n// Строим дерево\n// Вам этого делать не нужно, функция принимает уже готовое дерево\n// Пример приведен для наглядности\nconst tree = buildTree(points, 0, ['x', 'y'], null);\n\n// Точка-центр окружности\nconst dangerousPoint = { x: 4, y: 5 };\n// Радиус окружности - размер опасной зоны\nconst radius = 2;\n\n\nfindPointsInRadius(tree, dangerousPoint, radius);\n// [\n// { x:3, y:4 },\n// { x:5, y:6 }\n// ]\n\n```\n\nДобавьте в файл импорт\n\n```javascript\nimport buildKDTree from '../helpers/helpers.js';\n```\n\nи создайте в файле функцию со следующим кодом\n\n```javascript\nexport default (points, centerPoint, radius) => {\n const tree = buildKDTree(points, 0, ['x', 'y'], null);\n return findPointsInRadius(tree, centerPoint, radius);\n};\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/solution.php\n\nУсловия такие же как для JavaScript\n\n```php\n<?php\n\nfindPointsInRadius($tree, $dangerousPoint, $radius);\n// [\n// { x:3, y:4 },\n// { x:5, y:6 }\n// ]\n```\n\nПодключите файл\n\n```php\nrequire_once(__DIR__ . '/../helpers/helpers.php');\n```\n\nи создайте в файле функцию со следующим кодом\n\n```php\nfunction solution($points, $centerPoint, $radius)\n{\n $tree = buildTree($points, 0, ['x', 'y'], null);\n return findPointsInRadius($tree, $centerPoint, $radius);\n}\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/solution.py\n\nРеализуйте функцию `find_points_in_radius()`, остальные условия такие же, как для JavaScript\n\n```python\nfind_points_in_radius(tree, dangerousPoint, radius);\n# [\n# { x:3, y:4 },\n# { x:5, y:6 }\n# ]\n```\n\nДобавьте в файл импорты\n\n```python\nimport os\nimport sys\n\nsys.path.append(os.path.abspath('/usr/src/app/'))\n\nfrom helpers.helpers import build_tree\n```\n\nи создайте в файле функцию со следующим кодом\n\n```javascript\ndef solution(points, center_point, radius):\n tree = build_tree(points, 0, ['x', 'y'], None)\n return find_points_in_radius(tree, center_point, radius)\n\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n\n## solutions/Solution.java\n\nВ файле определите пакет `solutions` и создайте в нем публичный класс `Solution`. В классе реализуйте публичный статический метод `findPointsInRadius()`. Метод принимает три параметра:\n\n* KD дерево с точками, объект класса `KDTreeNode`\n* Точку – центр окружности. Точка – это словарь `Map<String, Integer>` со свойствами *x* и *y*\n* Радиус окружности, целое число\n\nФункция должна вернуть список `List<Map<String, Integer>>` всех точек из дерева, которые находятся внутри окружности\n\n```java\nMap<String, Integer> dangerousPoint = Map.of(\n \"x\", 4,\n \"y\", 5\n);\n\nvar radius = 2;\n\nList<Map<String, Integer>> risked = Solution.findPointsInRadius(tree, dangerousPoint, radius);\nSystem.out.println(risked); //=> [{y=4, x=3}, {y=6, x=5}]\n\n\nДобавьте в класс метод со следующим кодом\n\n```java\npublic static List<Map<String, Integer>> run(\n List<Map<String, Integer>> points, Map<String, Integer> center, int radius) {\n return helpers.Helper.run(points, center, radius);\n}\n```\n\nЭтот код необходим для проверки решения автоматическими тестами\n","has_solution":true,"entity_name":"KD-деревья"},"units":[{"id":8382,"name":"theory","url":"/courses/algorithms-trees/lessons/kdtrees/theory_unit"},{"id":8696,"name":"quiz","url":"/courses/algorithms-trees/lessons/kdtrees/quiz_unit"},{"id":8526,"name":"exercise","url":"/courses/algorithms-trees/lessons/kdtrees/exercise_unit"}],"links":[],"ordered_units":[{"id":8382,"name":"theory","url":"/courses/algorithms-trees/lessons/kdtrees/theory_unit"},{"id":8696,"name":"quiz","url":"/courses/algorithms-trees/lessons/kdtrees/quiz_unit"},{"id":8526,"name":"exercise","url":"/courses/algorithms-trees/lessons/kdtrees/exercise_unit"}],"id":3726,"slug":"kdtrees","state":"approved","name":"KD-деревья","course_order":160,"goal":"Познакомимся с KD-деревьями и особенностями реализации операций с ними","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Одна из самых популярных практических задач в современном программировании — это **поиск ближайших соседей**. Например, поиск ближайших соседей встречается в медицине. Так строятся прогнозные модели заболеваемости, в которых оцениваются контакты в ближайшем окружении заболевшего:\n\n\n\nНа рисунке выше вы можете увидеть координатную плоскость, на которой расположены:\n\n* «Заболевшие» красные точки\n* «Здоровые» синие точки\n* «Опасные зоны» — розовые окружности вокруг красных точек\n\nСиняя точка подвергается риску заболеть, если она входит в опасную зону — располагается слишком близко к красной точке. Другими словами, чтобы синяя точка не заболела, расстояние между ней и красной точкой должно быть выше **порогового значения**.\n\nВ этом примере задача сводится к поиску синих точек с высоким риском заболеть. Один из способов решения такой задачи — это кластеризация на основе методов машинного обучения. Но есть и альтернатива — это **KD-деревья**, о которых мы и поговорим в этом уроке.\n\n## Что такое KD-деревья\n\n**KD-деревья** — это дерево, вершины которого представлены в форме точек в некоторой K-мерной системе координат. Еще их называют _K-dimensional trees_ или «K-мерные деревья».\n\nВ этом курсе мы рассматриваем только KD-деревья в двумерном пространстве. Но с его помощью можно вычислять ближайшего соседа и на более сложных системах координат. Например, так выглядит трехмерное дерево:\n\n\n\nОбратим внимание, что эффективность поиска ближайших соседей в KD-дереве снижается при больших значениях K.\n\nВ качестве правила обычно принимают, что число вершин в дереве должно быть намного больше значения 2\\^K. Если это правило не соблюдать, то алгоритм поиска на основе KD-дерева будет работать с почти той же скоростью, что и обычный последовательный поиск.\n\n## Как устроено KD-дерево\n\nЧтобы изучить строение KD-дерева, возьмем для примера 13 точек в двумерной системе координат:\n\n\n\nЧтобы построить по ним дерево, мы будем руководствоваться следующим алгоритмом:\n\n1. Выберем ось в наборе данных\n2. Найдем на этой оси **медианное значение числа точек**. Для двумерного пространства это значит, что справа и слева от значения должно быть одинаковое число точек. Если у нас четное число точек, то можно левое подпространство сделать больше правого\n3. Проведем линию, которая разделит пространство на две части\n4. Изменим ось и нарисуем свою медиану для каждого нового подпространства\n\nПройдя эти четыре шага, мы выполним первое **разделение** дерева. Далее мы повторяем все шаги до тех пор, пока точек больше не останется.\n\nПосмотрим, как разделение работает на нашем примере — двумерном KD-дереве с 13 точками:\n\n**Этап 1**. Разделим пространство на основании оси X:\n\n\n\n**Этап 2**. Выполним второе разделение на основании оси Y:\n\n\n\n**Этап 3**. Продолжаем разделение, пока это возможно:\n\n\n\n**Этап 4**. Строим итоговое дерево, исходя из разделения пространства:\n\n\n\nНа последнем рисунке видно, что получившееся дерево аналогично сбалансированному бинарному дереву. Разница только в том, что в качестве полезной нагрузки в KD-дереве хранится точка с координатами.\n\nВ таком случае JavaScript-код узла будет выглядеть так:\n\n```javascript\nclass KDTreeNode {\n constructor(obj, dimension, parent) {\n this.obj = obj\n this.dimension = dimension\n this.right = null\n this.left = null\n this.parent = parent\n }\n}\n```\n\n```java\nclass KDTreeNode {\n List<Map<String, Integer>> obj;\n List<String> dimension;\n KDTreeNode right;\n KDTreeNode left;\n KDTreeNode parent;\n\n KDTreeNode(List<Map<String, Integer>> obj, List<String> dimension, KDTreeNode parent) {\n this.parent = parent;\n this.obj = obj;\n this.dimension = dimension;\n }\n}\n```\n\n```python\nclass KDTreeNode:\n def __init__(self, obj, dimension, parent):\n self.obj = obj\n self.dimension = dimension\n self.right = None\n self.left = None\n self.parent = parent\n```\n\n```php\n<?php\n\nclass KDTreeNode {\n public $obj;\n public array $dimension;\n public ?self $right = null;\n public ?self $left = null;\n public self $parent;\n\n public function __construct($obj, $dimension, $parent) {\n $this->parent = $parent;\n $this->obj = $obj;\n $this->dimension = $dimension;\n }\n}\n```\n\n## Операции над KD-деревом\n\nОсновное отличие KD-дерева можно увидеть при работе с методом, который отвечает за построение дерева из массива точек:\n\n```javascript\nclass KDTreeNode {\n // ...\n\n static buildTree(points, depth, dimensions, parent) {\n let dim = depth % dimensions.length\n let median, node\n\n if (points.length === 0) {\n return null\n }\n if (points.length === 1) {\n return new KDTreeNode(points[0], dim, parent)\n }\n\n points.sort(function (a, b) {\n return a[dimensions[dim]] - b[dimensions[dim]]\n })\n\n median = Math.floor(points.length / 2)\n node = new KDTreeNode(points[median], dim, parent)\n\n node.left = KDTreeNode.buildTree(points.slice(0, median), depth + 1, dimensions, node)\n node.right = KDTreeNode.buildTree(points.slice(median + 1), depth + 1, dimensions, node)\n\n return node\n }\n}\n```\n\n```java\nclass KDTreeNode {\n // ...\n\n public static KDTreeNode buildTree(\n List<Map<String, Integer>>points, int depth, List<String> dimensions, KDTreeNode parent) {\n var dim = depth % dimensions.size(); // Здесь выбираем, на какой оси проводим разбиение пространства\n int median;\n KDTreeNode node;\n\n if (points.size() == 0) {\n return null;\n }\n if (points.size() == 1) {\n return new KDTreeNode(points.get(0), dim, parent);\n }\n\n points.sort((a, b) -> a.get(dimensions.get(dim)) - b.get(dimensions.get(dim)));\n\n median = points.size() / 2; // Выбираем медиану и добавляем ее в дерево\n node = new KDTreeNode(points.get(median), dim, parent);\n\n // Правое и левое подпростанство продолжаем делить рекурсивно\n node.left = KDTreeNode.buildTree(points.subList(0, median), depth + 1, dimensions, node);\n node.right = KDTreeNode.buildTree(points.subList(median + 1, points.size()), depth + 1, dimensions, node);\n\n return node;\n\n }\n}\n```\n\n```python\n@staticmethod\ndef build_tree(points, depth, dimensions, parent):\n dim = depth % len(dimensions)\n if len(points) == 0:\n return None\n if len(points) == 1:\n return KDTreeNode(points[0], dim, parent)\n points.sort(key=lambda point: point[dimensions[dim]])\n median = len(points) // 2\n node = KDTreeNode(points[median], dim, parent)\n node.left = KDTreeNode.build_tree(points[:median], depth+1, dimensions, node)\n node.right = KDTreeNode.build_tree(points[median+1:], depth+1, dimensions, node)\n return node\n```\n\n```php\n<?php\n\nclass KDTreeNode\n{\n // ...\n\n public static function buildTree($points, $depth, $dimensions, $parent)\n {\n // Здесь выбираем, на какой оси проводим разбиение пространства\n $dim = $depth % count($dimensions);\n $median;\n $node;\n\n if (count($points) == 0) {\n return null;\n }\n if (count($points) == 1) {\n return new KDTreeNode($points[0], $dim, $parent);\n }\n\n usort($points, function($a, $b) use ($dimensions, $dim) {\n return $a[$dimensions[$dim]] - $b[$dimensions[$dim]];\n });\n\n // Выбираем медиану и добавляем ее в дерево\n $median = count($points) / 2;\n $node = new KDTreeNode($points[$median], $dim, $parent);\n\n // Правое и левое подпростанство продолжаем делить рекурсивно\n $node->left = self::buildTree(array_slice($points, 0, $median), $depth + 1, $dimensions, $node);\n $node->right = self::buildTree(array_slice($points, $median + 1, count($points)), $depth + 1, $dimensions, $node);\n\n return $node;\n }\n}\n```\n\nВызвать построение дерева можно при помощи следующего примера:\n\n```javascript\nconst points = [\n { x: 1, y: 2 },\n { x: 3, y: 4 },\n { x: 5, y: 6 },\n { x: 7, y: 8 },\n]\n\nconst tree = KDTreeNode.buildTree(points, 0, ['x', 'y'], null)\n```\n\n```java\nList<Map<String, Integer>> points = new ArrayList<>();\npoints.add(Map.of(\"x\", 1, \"y\", 1));\npoints.add(Map.of(\"x\", 3, \"y\", 4));\npoints.add(Map.of(\"x\", 5, \"y\", 6));\npoints.add(Map.of(\"x\", 7, \"y\", 8));\n\nList<String> dimensions = List.of(\"x\", \"y\");\n\nKDTreeNode tree = KDTreeNode.buildTree(points, 0, dimensions, null);\n```\n\n```python\npoints = [\n {\"x\": 1, \"y\": 2},\n {\"x\": 3, \"y\": 4},\n {\"x\": 5, \"y\": 6},\n {\"x\": 7, \"y\": 8},\n]\n\ntree = KDTreeNode.buildTree(points, 0, [\"x\", \"y\"], None)\n```\n\n```php\n<?php\n\n$points = [\n [\"x\" => 1, \"y\" => 1],\n [\"x\" => 3, \"y\" => 4],\n [\"x\" => 5, \"y\" => 6],\n [\"x\" => 7, \"y\" => 8]\n];\n\n$dimensions = [\"x\", \"y\"];\n\n$tree = KDTreeNode::buildTree($points, 0, $dimensions, null);\n```\n\nСтруктура KD-дерева не отличается от бинарного дерева. Поэтому методы удаления и вставки узлов работают так же, как в бинарном дереве:\n\n```javascript\nclass KDTreeNode {\n // ...\n\n insertNode(value) {\n this.insertNodeHelper(value, this)\n }\n\n insertNodeHelper(value, parentNode) {\n if (value[this.dimension] < parentNode.obj[parentNode.dimension]) {\n if (parentNode.left === null) {\n parentNode.left = new KDTreeNode(\n value,\n this.dimension,\n parentNode,\n )\n }\n else {\n this.insertNodeHelper(value, parentNode.left)\n }\n }\n if (value[this.dimension] >= parentNode.obj[parentNode.dimension]) {\n if (parentNode.right === null) {\n parentNode.right = new KDTreeNode(\n value,\n this.dimension,\n parentNode,\n )\n }\n else {\n this.insertNodeHelper(value, parentNode.right)\n }\n }\n }\n}\n```\n\n```java\nclass KDTreeNode {\n // ...\n public void insertNode(Map<String, Integer> value) {\n insertNode(value, this);\n }\n\n private void insertNode(Map<String, Integer> value, KDTreeNode parent) {\n if (value.get(dimension) < parentNode.obj.get(parentNode.dimension)){\n if (parentNode.left == null){\n parentNode.left = new KDTreeNode(value, dimension, parentNode);\n }\n else {\n insertNode(value, parentNode.left);\n }\n }\n if (value[this.dimension] < parentNode.obj[parentNode.dimension]){\n if (parentNode.right == null) {\n parentNode.right = new KDTreeNode(value, this.dimension, parentNode);\n }\n else {\n this.#insertNode(value, parentNode.right);\n }\n }\n }\n}\n```\n\n```python\ndef insertNode(self, value):\n return self._insertNode(value, self)\n\ndef _insertNode(self, value, parentNode):\n if value[parentNode.dimension] < parentNode.obj[parentNode.dimension]:\n if parentNode.left is None:\n parentNode.left = KDTreeNode(value, parentNode.dimension, parentNode)\n else:\n self._insertNode(value, parentNode.left)\n if value[parentNode.dimension] >= parentNode.obj[parentNode.dimension]:\n if parentNode.right is None:\n parentNode.right = KDTreeNode(value, parentNode.dimension, parentNode)\n else:\n self._insertNode(value, parentNode.right)\n```\n\n```php\n<?php\n\nclass KDTreeNode\n{\n // ...\n\n public function insertNode($value)\n {\n $this->insertNodeRecursive($value, $this);\n }\n\n private function insertNodeRecursive($value, $parentNode) {\n if ($value[$this->dimension] < $parentNode->obj[$parentNode->dimension]) {\n if ($parentNode->left == null) {\n $parentNode->left = new KDTreeNode($value, $this->dimension, $parentNode);\n } else {\n $this->insertNodeRecursive($value, $parentNode->left);\n }\n }\n if ($value[$this->dimension] >= $parentNode->obj[$parentNode->dimension]) {\n if ($parentNode->right == null) {\n $parentNode->right = new KDTreeNode($value, $this->dimension, $parentNode);\n } else {\n $this->insertNodeRecursive($value, $parentNode->right);\n }\n }\n }\n}\n```\n\nУдаление:\n\n```javascript\nclass KDTreeNode {\n // ...\n\n removeNode(value) {\n this.#removeNodeHelper(value, this)\n }\n\n #removeNodeHelper(value, node) {\n if (node === null) {\n return null // Узел не найден, возвращаем null\n }\n\n if (value[this.dimension] < node.obj[node.dimension]) {\n node.left = this.removeNodeHelper(value, node.left)\n }\n else if (value[this.dimension] > node.obj[node.dimension]) {\n node.right = this.removeNodeHelper(value, node.right)\n }\n else {\n // Узел найден\n if (node.left === null) {\n return node.right // Узел имеет только правого потомка\n }\n else if (node.right === null) {\n return node.left // Узел имеет только левого потомка\n }\n\n // Узел имеет обоих потомков\n let original = node\n node = node.right\n while (node.left) {\n node = node.left\n }\n\n node.right = this.#removeMin(original.right)\n node.left = original.left\n }\n return node\n }\n\n #removeMin(node) {\n if (node.left === null) {\n return node.right // Если нет левого потомка, возвращаем правого потомка\n }\n node.left = this.removeMin(node.left) // Рекурсивно вызываем removeMin для левого потомка\n return node\n }\n}\n```\n\n```java\nclass KDTreeNode {\n // ...\n\n public void removeNode(Map<String, Integer> value) {\n removeNode(value, this);\n }\n\n private KDTreeNode removeNode(Map<String, Integer> value, KDTreeNode node) {\n if (node == null) {\n return null;\n }\n\n if (value.get(this.dimension) < node.obj.get(this.dimension)) {\n node.left = this.removeNode(node.left, value);\n }\n else if (value.get(this.dimension) > node.obj.get(this.dimension)) {\n node.right = this.removeNode(node.right, value);\n }\n else {\n if (node.left == null) {\n return node.right;\n }\n if (node.right == null) {\n return node.left;\n }\n }\n\n KDTreeNode original = node;\n node = node.right;\n while (node.left != null) {\n node = node.left;\n }\n\n node.right = removeMin(original.right);\n node.left = original.left;\n }\n}\n```\n\n```python\ndef removeNode(self, value):\n return self._removeNode(value, self)\n\ndef _removeNode(self, value, node):\n if node is None:\n return None\n\n if value[self.dimension] < node.obj[self.dimension]:\n node.left = self._removeNode(value, node.left)\n elif value[self.dimension] > node.obj[self.dimension]:\n node.right = self._removeNode(value, node.right)\n else:\n if node.left is None:\n return node.right\n if node.right is None:\n return node.left\n\n original = node\n node = node.right\n while node.left:\n node = node.left\n\n node.right = self._removeMin(original.right)\n node.left = original.left\n```\n\n```php\n<?php\nclass KDTreeNode\n{\n // ...\n\n public function removeNode($value)\n {\n return $this->removeNodeRecursive($value, $this);\n }\n\n private function removeNodeRecursive($value, $node)\n {\n if ($node == null) return null;\n\n if ($value$this->dimension < $node->obj[$this->dimension]) {\n $node->left = $this->removeNodeRecursive($value, $node->left);\n } else if ($value[$this->dimension] > $node->obj[$this->dimension]) {\n $node->right = $this->removeNodeRecursive($value, $node->right);\n } else {\n if ($node->left == null) return $node->right;\n if ($node->right == null) return $node->left;\n\n $original = $node;\n $node = $node->right;\n\n while ($node->left) {\n $node = $node->left;\n }\n\n $node->right = $this->removeMin($original->right);\n $node->left = $original->left;\n }\n }\n}\n```\n\nЕще одной отличительной особенностью KD-дерева считается реализация метода поиска ближайшего соседа:\n\n```javascript\nclass KDTreeNode {\n // ...\n\n metric(point1, point2) {\n return Math.sqrt(\n Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2),\n )\n }\n\n nearestSearch(point, node, bestNodes, maxNodes) {\n if (node === null) {\n return // Достигнут конец дерева\n }\n\n let dimension = this.dimension\n // Хранит расстояние между искомой точкой и текущим узлом дерева.\n let ownDistance = this.metric(point, node.obj)\n // для хранения координат точки, которую сравниваем с узлом.\n let linearPoint = {}\n // Расстояние между linearPoint и объектом текущего узла. Помогает решить, стоит ли искать в другом поддереве.\n let linearDistance\n // Ссылки на дочерние узлы. bestChild — поддерево с вероятным ближайшим соседом, otherChild — другое поддерево для проверки.\n let bestChild, otherChild\n\n for (let i = 0; i < this.dimension.length; i++) {\n if (i === this.dimension) {\n linearPoint[this.dimension[i]] = point[this.dimension[i]]\n }\n else {\n linearPoint[this.dimension[i]] = node.obj[this.dimension[i]]\n }\n }\n\n linearDistance = this.metric(linearPoint, node.obj)\n\n if (\n bestNodes.length < maxNodes\n || ownDistance < bestNodes[bestNodes.length - 1][1]\n ) {\n if (bestNodes.length === maxNodes) {\n bestNodes.pop() // Удаляем последний элемент, если массив лучших узлов уже заполнен\n }\n bestNodes.push([node.obj, ownDistance]) // Добавляем текущий узел в массив лучших узлов\n bestNodes.sort((a, b) => a[1] - b[1]) // Сортируем массив лучших узлов по расстоянию\n }\n\n if (\n bestNodes.length < maxNodes\n || Math.abs(linearDistance) < bestNodes[bestNodes.length - 1][1]\n ) {\n if (linearDistance < 0) {\n bestChild = node.right\n otherChild = node.left\n }\n else {\n bestChild = node.left\n otherChild = node.right\n }\n this.nearestSearch(point, bestChild, bestNodes, maxNodes) // Рекурсивно ищем в ближайшем поддереве\n if (Math.abs(linearDistance) < bestNodes[bestNodes.length - 1][1]) {\n this.nearestSearch(point, otherChild, bestNodes, maxNodes) // Поиск в другом поддереве, если необходимо\n }\n }\n }\n}\n```\n\n```java\nclass KDTreeNode {\n // ...\n\n public KDTreeNode nearestSearch(Map<String, Integer> point, KDTreeNode node) {\n KDTreeNode bestChild;\n var dimension = dimensions.get(node.get(\"dimension\"));\n var ownDistance = metric(point, node.obj),\n linearPoint = new HashMap<String, Integer>();\n double linearDistance;\n KDTreeNode otherChild;\n int i;\n\n for (var i = 0; i < dimensions.length(); i += 1) {\n if (i == node.dimension) {\n linearPoint.put(dimensions.get(i), point.get(dimensions.get(i)));\n } else {\n linearPoint.put(dimensions.get(i), node.obj.get(dimensions.get(i)));\n }\n }\n\n linearDistance = metric(linearPoint, node.obj);\n\n if (node.right == null && node.left == null) {\n if (bestNodes.size() < maxNodes || ownDistance < bestNodes.get(0).get(1)) {\n this.saveNode(node, ownDistance);\n }\n return;\n }\n\n if (node.right == null) {\n bestChild = node.left;\n } else if (node.left == null) {\n bestChild = node.right;\n } else {\n if (point.get(dimension) < node.obj.get(dimension)) {\n bestChild = node.left;\n } else {\n bestChild = node.right;\n }\n }\n\n nearestSearch(point, bestChild);\n\n if (bestNodes.size() < maxNodes || ownDistance < bestNodes.get(0).get(1)) {\n this.saveNode(node, ownDistance);\n }\n\n if (bestNodes.size() < maxNodes || Math.abs(linearDistance) < bestNodes.get(0).get(1)) {\n if (bestChild == node.left) {\n otherChild = node.right;\n } else {\n otherChild = node.left;\n }\n if (otherChild != null) {\n nearestSearch(point, otherChild);\n }\n }\n }\n}\n```\n\n```python\ndef nearestSearch(point, node):\n bestChild = None\n dimension = dimensions[node.dimension]\n ownDistance = metric(point, node.obj)\n linearPoint = {}\n linearDistance = 0\n otherChild = None\n\n for i in range(len(dimensions)):\n if i == node.dimension:\n linearPoint[dimensions[i]] = point[dimensions[i]]\n else:\n linearPoint[dimensions[i]] = node.obj[dimensions[i]]\n\n linearDistance = metric(linearPoint, node.obj)\n\n if node.right == None and node.left == None:\n if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]:\n saveNode(node, ownDistance)\n return\n\n if node.right == None:\n bestChild = node.left\n elif node.left == None:\n bestChild = node.right\n else:\n if point[dimension] < node.obj[dimension]:\n bestChild = node.left\n else:\n bestChild = node.right\n\n nearestSearch(point, bestChild)\n\n if bestNodes.qsize() < maxNodes or ownDistance < bestNodes.queue[0][1]:\n saveNode(node, ownDistance)\n\n if bestNodes.qsize() < maxNodes or abs(linearDistance) < bestNodes.queue[0][1]:\n if bestChild == node.left:\n otherChild = node.right\n else:\n otherChild = node.left\n if otherChild != None:\n nearestSearch(point, otherChild)\n```\n\n```php\n<?php\n\nclass KDTreeNode\n{\n private function nearestSearch($point, $node)\n {\n $bestChild = null;\n $dimension = $dimensions[$node->dimension];\n $ownDistance = metric($point, $node->obj);\n $linearPoint = [];\n $linearDistance = null;\n $otherChild = null;\n\n for ($i = 0; $i < count($dimensions); $i += 1) {\n if ($i === $node->dimension) {\n $linearPoint[$dimensions[$i]] = $point[$dimensions[$i]];\n } else {\n $linearPoint[$dimensions[$i]] = $node->obj[$dimensions[$i]];\n }\n }\n\n $linearDistance = metric($linearPoint, $node->obj);\n\n if ($node->right === null && $node->left === null) {\n if (count($bestNodes) < $maxNodes || $ownDistance < $bestNodes[0][1]) {\n $this->saveNode($node, $ownDistance);\n }\n return;\n }\n\n if ($node->right === null) {\n $bestChild = $node->left;\n } elseif ($node->left === null) {\n $bestChild = $node->right;\n } else {\n if ($point[$dimension] < $node->obj[$dimension]) {\n $bestChild = $node->left;\n } else {\n $bestChild = $node->right;\n }\n }\n\n $this->nearestSearch($point, $bestChild);\n\n if (count($bestNodes) < $maxNodes || $ownDistance < $bestNodes[0][1]) {\n $this->saveNode($node, $ownDistance);\n }\n\n if (count($bestNodes) < $maxNodes || abs($linearDistance) < $bestNodes[0][1]) {\n if ($bestChild === $node->left) {\n $otherChild = $node->right;\n } else {\n $otherChild = $node->left;\n }\n if ($otherChild !== null) {\n $this->nearestSearch($point, $otherChild);\n }\n }\n }\n}\n\n```\n\nДля определения расстояния между точками используется метрика, чаще всего евклидова. Она позволяет вычислить, насколько близко одна точка находится к другой, что является ключевым аспектом при поиске ближайших соседей.\n\n### Процесс поиска ближайших соседей\n\n1. **Инициализация**:\n * Поиск начинается с корневого узла дерева и заданной точки, для которой необходимо найти ближайшие соседи.\n2. **Рекурсивный поиск**:\n * На каждом уровне дерева происходит сравнение координат искомой точки с координатами узла. В зависимости от результата сравнения, поиск продолжается в одном из дочерних узлов (левом или правом).\n * Если текущий узел ближе к искомой точке, чем уже найденные соседи, он добавляется в список лучших соседей.\n3. **Обновление списка лучших соседей**:\n * Если количество найденных соседей меньше заданного максимума, или если текущий узел ближе, чем самый дальний из уже найденных, он добавляется в список.\n * Если список заполнен, удаляется самый дальний сосед, чтобы освободить место для нового.\n4. **Проверка других поддеревьев**:\n * После проверки одного из дочерних узлов, если расстояние до текущего узла позволяет, происходит проверка другого дочернего узла. Это необходимо для того, чтобы убедиться, что не пропущены более близкие соседи.\n\n## Выводы\n\nВ этом уроке мы познакомились с KD-деревьями, которые помогают организовать хранение пространственных данных. KD-деревья — это основная альтернатива методам машинного обучения при решении кластеризационных задач.\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/kdtrees/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">Теория: KD-деревья</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"KD-деревья","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>Одна из самых популярных практических задач в современном программировании — это <strong>поиск ближайших соседей</strong>. Например, поиск ближайших соседей встречается в медицине. Так строятся прогнозные модели заболеваемости, в которых оцениваются контакты в ближайшем окружении заболевшего:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDMsInB1ciI6ImJsb2JfaWQifX0=--86a0f587ba38fd3530e2410e65a6bb96e51b5e61/01.png" alt="01" loading="lazy"/></p>
<p>На рисунке выше вы можете увидеть координатную плоскость, на которой расположены:</p>
<ul>
<li>«Заболевшие» красные точки</li>
<li>«Здоровые» синие точки</li>
<li>«Опасные зоны» — розовые окружности вокруг красных точек</li>
</ul>
<p>Синяя точка подвергается риску заболеть, если она входит в опасную зону — располагается слишком близко к красной точке. Другими словами, чтобы синяя точка не заболела, расстояние между ней и красной точкой должно быть выше <strong>порогового значения</strong>.</p>
<p>В этом примере задача сводится к поиску синих точек с высоким риском заболеть. Один из способов решения такой задачи — это кластеризация на основе методов машинного обучения. Но есть и альтернатива — это <strong>KD-деревья</strong>, о которых мы и поговорим в этом уроке.</p>
<h2 id="heading-2-1">Что такое KD-деревья</h2>
<p><strong>KD-деревья</strong> — это дерево, вершины которого представлены в форме точек в некоторой K-мерной системе координат. Еще их называют <em>K-dimensional trees</em> или «K-мерные деревья».</p>
<p>В этом курсе мы рассматриваем только KD-деревья в двумерном пространстве. Но с его помощью можно вычислять ближайшего соседа и на более сложных системах координат. Например, так выглядит трехмерное дерево:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDQsInB1ciI6ImJsb2JfaWQifX0=--e780d60595224da701a4b9df54c00e896f8797e6/07.png" alt="07" loading="lazy"/></p>
<p>Обратим внимание, что эффективность поиска ближайших соседей в KD-дереве снижается при больших значениях K.</p>
<p>В качестве правила обычно принимают, что число вершин в дереве должно быть намного больше значения 2^K. Если это правило не соблюдать, то алгоритм поиска на основе KD-дерева будет работать с почти той же скоростью, что и обычный последовательный поиск.</p>
<h2 id="heading-2-2">Как устроено KD-дерево</h2>
<p>Чтобы изучить строение KD-дерева, возьмем для примера 13 точек в двумерной системе координат:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDUsInB1ciI6ImJsb2JfaWQifX0=--7601166d54230fc1bb686e4efab0bb81954af493/02.jpg" alt="02" loading="lazy"/></p>
<p>Чтобы построить по ним дерево, мы будем руководствоваться следующим алгоритмом:</p>
<ol>
<li>Выберем ось в наборе данных</li>
<li>Найдем на этой оси <strong>медианное значение числа точек</strong>. Для двумерного пространства это значит, что справа и слева от значения должно быть одинаковое число точек. Если у нас четное число точек, то можно левое подпространство сделать больше правого</li>
<li>Проведем линию, которая разделит пространство на две части</li>
<li>Изменим ось и нарисуем свою медиану для каждого нового подпространства</li>
</ol>
<p>Пройдя эти четыре шага, мы выполним первое <strong>разделение</strong> дерева. Далее мы повторяем все шаги до тех пор, пока точек больше не останется.</p>
<p>Посмотрим, как разделение работает на нашем примере — двумерном KD-дереве с 13 точками:</p>
<p><strong>Этап 1</strong>. Разделим пространство на основании оси X:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDYsInB1ciI6ImJsb2JfaWQifX0=--5b9738474b7e3fb92703a6d6113d651d1fbf1813/03.jpg" alt="03" loading="lazy"/></p>
<p><strong>Этап 2</strong>. Выполним второе разделение на основании оси Y:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDcsInB1ciI6ImJsb2JfaWQifX0=--eca354d531d8f8f4b0a2ba8cfcf97e57cd8e72a6/04.jpg" alt="04" loading="lazy"/></p>
<p><strong>Этап 3</strong>. Продолжаем разделение, пока это возможно:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDgsInB1ciI6ImJsb2JfaWQifX0=--c462ad0ff4effd5c8c6812e6e4b24fd3a556a54b/05.jpg" alt="05" loading="lazy"/></p>
<p><strong>Этап 4</strong>. Строим итоговое дерево, исходя из разделения пространства:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzNDksInB1ciI6ImJsb2JfaWQifX0=--d2d4b67fa33e716a9b4a8bed52c074ff660f94e5/06.png" alt="06" loading="lazy"/></p>
<p>На последнем рисунке видно, что получившееся дерево аналогично сбалансированному бинарному дереву. Разница только в том, что в качестве полезной нагрузки в KD-дереве хранится точка с координатами.</p>
<p>В таком случае JavaScript-код узла будет выглядеть так:</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 KDTreeNode {
constructor(obj, dimension, parent) {
this.obj = obj
this.dimension = dimension
this.right = null
this.left = null
this.parent = parent
}
}</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><h2 id="heading-2-3">Операции над KD-деревом</h2>
<p>Основное отличие KD-дерева можно увидеть при работе с методом, который отвечает за построение дерева из массива точек:</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 KDTreeNode {
// ...
static buildTree(points, depth, dimensions, parent) {
let dim = depth % dimensions.length
let median, node
if (points.length === 0) {
return null
}
if (points.length === 1) {
return new KDTreeNode(points[0], dim, parent)
}
points.sort(function (a, b) {
return a[dimensions[dim]] - b[dimensions[dim]]
})
median = Math.floor(points.length / 2)
node = new KDTreeNode(points[median], dim, parent)
node.left = KDTreeNode.buildTree(points.slice(0, median), depth + 1, dimensions, node)
node.right = KDTreeNode.buildTree(points.slice(median + 1), depth + 1, dimensions, node)
return node
}
}</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>
<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">const points = [
{ x: 1, y: 2 },
{ x: 3, y: 4 },
{ x: 5, y: 6 },
{ x: 7, y: 8 },
]
const tree = KDTreeNode.buildTree(points, 0, ['x', 'y'], null)</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>Структура KD-дерева не отличается от бинарного дерева. Поэтому методы удаления и вставки узлов работают так же, как в бинарном дереве:</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 KDTreeNode {
// ...
insertNode(value) {
this.insertNodeHelper(value, this)
}
insertNodeHelper(value, parentNode) {
if (value[this.dimension] < parentNode.obj[parentNode.dimension]) {
if (parentNode.left === null) {
parentNode.left = new KDTreeNode(
value,
this.dimension,
parentNode,
)
}
else {
this.insertNodeHelper(value, parentNode.left)
}
}
if (value[this.dimension] >= parentNode.obj[parentNode.dimension]) {
if (parentNode.right === null) {
parentNode.right = new KDTreeNode(
value,
this.dimension,
parentNode,
)
}
else {
this.insertNodeHelper(value, parentNode.right)
}
}
}
}</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>
<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 KDTreeNode {
// ...
removeNode(value) {
this.#removeNodeHelper(value, this)
}
#removeNodeHelper(value, node) {
if (node === null) {
return null // Узел не найден, возвращаем null
}
if (value[this.dimension] < node.obj[node.dimension]) {
node.left = this.removeNodeHelper(value, node.left)
}
else if (value[this.dimension] > node.obj[node.dimension]) {
node.right = this.removeNodeHelper(value, node.right)
}
else {
// Узел найден
if (node.left === null) {
return node.right // Узел имеет только правого потомка
}
else if (node.right === null) {
return node.left // Узел имеет только левого потомка
}
// Узел имеет обоих потомков
let original = node
node = node.right
while (node.left) {
node = node.left
}
node.right = this.#removeMin(original.right)
node.left = original.left
}
return node
}
#removeMin(node) {
if (node.left === null) {
return node.right // Если нет левого потомка, возвращаем правого потомка
}
node.left = this.removeMin(node.left) // Рекурсивно вызываем removeMin для левого потомка
return node
}
}</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>Еще одной отличительной особенностью KD-дерева считается реализация метода поиска ближайшего соседа:</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 KDTreeNode {
// ...
metric(point1, point2) {
return Math.sqrt(
Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2),
)
}
nearestSearch(point, node, bestNodes, maxNodes) {
if (node === null) {
return // Достигнут конец дерева
}
let dimension = this.dimension
// Хранит расстояние между искомой точкой и текущим узлом дерева.
let ownDistance = this.metric(point, node.obj)
// для хранения координат точки, которую сравниваем с узлом.
let linearPoint = {}
// Расстояние между linearPoint и объектом текущего узла. Помогает решить, стоит ли искать в другом поддереве.
let linearDistance
// Ссылки на дочерние узлы. bestChild — поддерево с вероятным ближайшим соседом, otherChild — другое поддерево для проверки.
let bestChild, otherChild
for (let i = 0; i < this.dimension.length; i++) {
if (i === this.dimension) {
linearPoint[this.dimension[i]] = point[this.dimension[i]]
}
else {
linearPoint[this.dimension[i]] = node.obj[this.dimension[i]]
}
}
linearDistance = this.metric(linearPoint, node.obj)
if (
bestNodes.length < maxNodes
|| ownDistance < bestNodes[bestNodes.length - 1][1]
) {
if (bestNodes.length === maxNodes) {
bestNodes.pop() // Удаляем последний элемент, если массив лучших узлов уже заполнен
}
bestNodes.push([node.obj, ownDistance]) // Добавляем текущий узел в массив лучших узлов
bestNodes.sort((a, b) => a[1] - b[1]) // Сортируем массив лучших узлов по расстоянию
}
if (
bestNodes.length < maxNodes
|| Math.abs(linearDistance) < bestNodes[bestNodes.length - 1][1]
) {
if (linearDistance < 0) {
bestChild = node.right
otherChild = node.left
}
else {
bestChild = node.left
otherChild = node.right
}
this.nearestSearch(point, bestChild, bestNodes, maxNodes) // Рекурсивно ищем в ближайшем поддереве
if (Math.abs(linearDistance) < bestNodes[bestNodes.length - 1][1]) {
this.nearestSearch(point, otherChild, bestNodes, maxNodes) // Поиск в другом поддереве, если необходимо
}
}
}
}</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>
<h3 id="heading-3-4">Процесс поиска ближайших соседей</h3>
<ol>
<li><strong>Инициализация</strong>:
<ul>
<li>Поиск начинается с корневого узла дерева и заданной точки, для которой необходимо найти ближайшие соседи.</li>
</ul>
</li>
<li><strong>Рекурсивный поиск</strong>:
<ul>
<li>На каждом уровне дерева происходит сравнение координат искомой точки с координатами узла. В зависимости от результата сравнения, поиск продолжается в одном из дочерних узлов (левом или правом).</li>
<li>Если текущий узел ближе к искомой точке, чем уже найденные соседи, он добавляется в список лучших соседей.</li>
</ul>
</li>
<li><strong>Обновление списка лучших соседей</strong>:
<ul>
<li>Если количество найденных соседей меньше заданного максимума, или если текущий узел ближе, чем самый дальний из уже найденных, он добавляется в список.</li>
<li>Если список заполнен, удаляется самый дальний сосед, чтобы освободить место для нового.</li>
</ul>
</li>
<li><strong>Проверка других поддеревьев</strong>:
<ul>
<li>После проверки одного из дочерних узлов, если расстояние до текущего узла позволяет, происходит проверка другого дочернего узла. Это необходимо для того, чтобы убедиться, что не пропущены более близкие соседи.</li>
</ul>
</li>
</ol>
<h2 id="heading-2-5">Выводы</h2>
<p>В этом уроке мы познакомились с KD-деревьями, которые помогают организовать хранение пространственных данных. KD-деревья — это основная альтернатива методам машинного обучения при решении кластеризационных задач.</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/kdtrees/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/kdtrees/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>