В программировании встречаются задачи, которые на первый взгляд не имеют отношения к графам, но решаются именно с помощью алгоритмов на графах. Одна из таких задач возникает на швейных фабриках, где роботы вырезают детали по выкройкам из больших кусков ткани. Для экономии ткани важно оставлять как можно меньше обрезков:
Для примера посмотрим, как выглядит неэкономный раскрой ткани. Здесь остается большой обрезок ткани:
Но если переставить детали местами, можно выкроить на одну красную деталь больше — и тогда обрезка не будет:
Чтобы решить такую задачу, можно использовать неявное представление графов. Действуя методом полного перебора, можно найти все варианты размещения деталей, чтобы площадь обрезков была как можно меньше.
Рассмотрим еще один пример похожей задачи. Она встречается при программировании банкоматов или вендинговых автоматов, где требуется выдать сумму наименьшим количеством банкнот и монет.
Представим, что нам нужно выдать 11 рублей. Эффективнее всего выдать две монеты: 10 рублей и 1 рубль. Но в автомате может не оказаться десятирублевых монет. Тогда простой алгоритм не найдет альтернативный вариант — одна монета в 5 рублей и три монеты по 2 рубля. Как и в случае с раскроем, все возможные решения можно найти только методом перебора.
Перечисленные задачи не похожи друг на друга, но их принято относить к одному классу, потому что решаются они одинаково. Название всему классу дала задача о рюкзаке. Одна из ее формулировок звучит так:
Попав в пещеру к разбойникам, Али-баба решил забрать как можно больше ценностей. У него есть рюкзак, рассчитанный на 20 килограмм. Нужно выбрать самые ценные предметы так, чтобы они уместились в рюкзак
В чем сложность задачи? Отобрать самое ценное не так уж и сложно, но есть и второе ограничение — вес предмета. Без перебора мы можем выбрать самый дорогой предмет в комнате — двадцатикилограммовый сундук. Но если проверить все варианты перебором, может оказаться, что два десятикилограммовых кувшина в сумме стоят больше. Тогда нужно унести два кувшина, а не один сундук.
В целом, в задачах о рюкзаке речь идет о множестве предметов, из которых мы должны выбрать оптимальное подмножество с учетом ограничений. В задаче о раскрое мы минимизировали площадь обрезков, а при выдаче сдачи — количество монет. В задаче о рюкзаке мы максимизируем стоимость предметов. Другими словами, во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве.
Метод перебора
Перебор — это простейший способ решения задач на графе. В случае задач о рюкзаке граф обычно представлен неявно.
При этом алгоритм работает рекурсивно. Это значит, что мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии.
Рекурсия завершается, когда вес рюкзака превышен. В этот момент мы фиксируем текущее состояние рюкзака — одно из возможных решений. При этом нам не надо хранить все найденные решения, достаточно хранить самое дорогое.
Второй повод для завершения рекурсии — закончились предметы, то есть в рюкзак поместились все предметы. К сожалению, в реальной жизни так почти не бывает.
Взглянем на реализацию такого алгоритма:
Разберем этот код подробнее. Сначала мы видим функцию backpack(), внутри которой мы реализуем bruteforce() — рекурсивную функцию перебора.
В коде мы обращаемся к двум внешним переменным:
- valuables — массив ценностей
- seen — множество просмотренных ценностей
В качестве параметров мы передаем:
- sumPrice — общая цена просмотренных ценностей
- sumWeight - общий вес
Можно было бы перебирать ценности в множестве seen, но это слишком накладно для каждого шага рекурсии. Поэтому мы перебираем ценности в цикле for. Если предмет уже просмотрен, мы сразу переходим к следующему предмету:
Если возникает перевес, мы также переходим к следующему предмету:
Мы выполняем рекурсивные вызовы, пока мы не заполнили весь рюкзак и не достигли вершины-листа в нашем графе. Как и в дереве, листом в графе называется вершина, из которой больше некуда идти:
Представим, что мы больше не можем положить в рюкзак ни одного предмета. Это значит, что мы достигли вершины-листа. Флаг isLeaf остается равным true:
Если мы достигли листа и нашли новый лучший результат, сохраняем его в полях переменной result.
Вот что у нас получается в результате:
Оценка сложности
Попробуем оценить алгоритмическую сложность метода перебора. Как и следует из названия, он перебирает все возможные решения, добираясь до них через все промежуточные вершины графа.
Представим, что у нас есть предметы 1, 2, 3, 4. Так будет выглядеть граф, который обходит метод перебора:
В этом графе есть 16 вершин, в том числе пустая. Рассмотрим, к какому количеству решений ведет разное количество предметов:
- Ноль предметов дает 1 решение, то есть Али-Баба уходит с пустым рюкзаком. Так произойдет, если в пещере не найдется предмет весом менее 20 килограмм
- Один предмет дает 2 решения — пустой рюкзак и рюкзак с этим самым предметом
- Два предмета дают 4 решения — пустой рюкзак, рюкзак с первым предметом, рюкзак со вторым и с двумя предметами сразу. Отметим, что 2*2=4
- Три предмета дают 8 решений 2*2*2=8
- Четыре предмета дают 16 решений 2*2*2*2=16
Как мы видим, каждый новый предмет удваивает количество вершин. Поэтому метод перебора обладает экспоненциальной сложностью O(2^n).
Это довольно медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле.
Жадный алгоритм
Как отказаться от перебора предметов на каждом шаге рекурсии? Один из подходов заключается в том, чтобы всякий раз выбирать самый ценный предмет. Работающие по этому принципу алгоритмы называются жадными.
Чтобы жадный алгоритм выбирал самые ценные предметы, нужно сравнивать не абсолютную, а удельную стоимость — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает одну ветвь — с наибольшей удельной стоимостью.
Без предварительной сортировки поиск максимума обладает линейной сложностью. Другими словами, в худшем случае нам придется перебрать все предметы в пещере. Итого, временная сложность жадного алгоритма равна O(n*n) или O(n^2).
К сожалению, жадный алгоритм не всегда находит оптимальное решение. Для примера представим, что нам нужно выбрать из двух вариантов:
- 2 короны, каждая весит по 1 килограмму и стоит по 80 тысяч долларов
- 1 сундук, который весит 20 килограмм и стоит 500 тысяч долларов
Удельная стоимость корон выше, но сундук выгоднее, поэтому стоит забрать именно его. Жадный алгоритм не учтет такой вариант.
Для некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма.
В таких автоматах применяется сложная математика, которая выходит за пределы данного курса. Если вам интересно изучить эту тему подробнее, можете почитать статью Canonical Coin Systems for Change-Making Problems.
Выводы
В этом уроке мы познакомились с жадными алгоритмами. Повторим ключевые мысли из урока:
- Бывают задачи, которые похожи друг на друга по сути, несмотря на внешние различия. Такие задачи относят к одному классу. Например, существует класс «задачи о рюкзаке», по которому можно посчитать сдачу в автомате минимальным количеством монет
- Во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве. Их можно решить двумя способами — с помощью перебора или жадных алгоритмов
- При переборе алгоритм работает рекурсивно — мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии. Рекурсия завершается, когда вес рюкзака превышен или закончились предметы
- Перебор — это медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле
- Жадный алгоритм выбирает самые ценные предметы, сравнивая удельную стоимость — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает ветвь с наибольшей удельной стоимостью
- Без предварительной сортировки жадный алгоритм обладает линейной сложностью. Для некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма
<!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 20:27:19 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="ZDvVQH7scyiqD6Vq3G_zI1GIEzfESdBB31pdzUtcCzmL6h53jJLeSBxMgfLQYANUkYE-ncx-LuNiuseZGVvsVw";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Жадные алгоритмы | Алгоритмы на графах</title>
<meta name="description" content="Жадные алгоритмы / Алгоритмы на графах: Изучаем жадные алгоритмы в сравнении с методом перебора">
<link rel="canonical" href="https://ru.hexlet.io/courses/algorithms-graphs/lessons/greedy-algorithms/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Жадные алгоритмы">
<meta property="og:title" content="Алгоритмы на графах">
<meta property="og:description" content="Жадные алгоритмы / Алгоритмы на графах: Изучаем жадные алгоритмы в сравнении с методом перебора">
<meta property="og:url" content="https://ru.hexlet.io/courses/algorithms-graphs/lessons/greedy-algorithms/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="T6REGhqazq6Y2ZIwN3xnY9RoDvayIwpJlZZ0UMwiWpCgdY8t6ORjzi6atqg7c5cUFGEjXLoU9Osodu4EniW9_g" />
<script src="/vite/assets/inertia-DfXos102.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-BrRXra1y.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-cb8xch9l.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-26T20:27:19.306Z","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":"ILQmGFN2x77ofDmf11XgkgnwkXATxmUc4b-kAR_RrHfPZe0voQhq3l4_HQfbWhDlyfm82hvxm75cXz5VTdZLGQ","topics":[],"lesson":{"exercise":{"id":2987,"slug":"algorithms_graphs_greedy_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Упражнение можно решить на одном из четырех языков программирования на выбор: JavaScript, PHP, Python или Java. Запишите решение в соответствующем файле в каталоге *solutions*.\n\nВ этом упражнении нужно реализовать алгоритм жадного поиска для задачи о рюкзаке.\n\nБыстрое решение с алгоритмической сложностью `[O(n times log n)]` состоит в том, чтобы упорядочить все ценности по убыванию удельной стоимости. После сортировки надо взять из начала массива столько ценностей, чтобы их суммарная стоимость не превышала грузоподъемность рюкзака.\n\n## solutions/solution.js\n\nНапишите функцию `backpack(worths, maxWeight)`, которая получает на вход:\n\n* `worths` — массив ценностей с полями `name`, `price` и `weight`\n* `maxWeight` — грузоподъемность рюкзака\n\nФункция `backpack()` должна возвращать объект с полями `names`, `sumPrice` и `sumWeight`.\n\n## Пример\n\n```javascript\n\nconst worths = [\n { name: 'Ноутбук', weight: 5, price: 1500 },\n { name: 'Фотоаппарат', weight: 3, price: 1000 },\n { name: 'Книги', weight: 8, price: 200 },\n { name: 'Планшет', weight: 4, price: 800 },\n { name: 'Зонт', weight: 6, price: 300 },\n];\n\nconsole.log(backpack(worths, 15)); \n// ==> {\n// names: [ 'Фотоаппарат', 'Ноутбук', 'Планшет' ],\n// sumPrice: 3300,\n// sumWeight: 12\n// }\n```\n## solutions/solution.php\n\nУсловия такие же как для JavaScript.\n```php\n<?php\n\n$worths = [\n ['name' => 'Ноутбук', 'weight' => 5, 'price' => 1500],\n ['name' => 'Фотоаппарат', 'weight' => 3, 'price' => 1000],\n ['name' => 'Книги', 'weight' => 8, 'price' => 200],\n ['name' => 'Планшет', 'weight' => 4, 'price' => 800],\n ['name' => 'Зонт', 'weight' => 6, 'price' => 300],\n];\n\nprint_r(backpack($worths, 15));\n// ==> Array\n// (\n// [names] => Array\n// (\n// [0] => Фотоаппарат\n// [1] => Ноутбук\n// [2] => Планшет\n// )\n\n// [sumPrice] => 3300\n// [sumWeight] => 12\n// )\n```\n## solutions/solution.py\n\nУсловия такие же как для JavaScript.\n\n```python\nworths = [\n {'name': 'Ноутбук', 'weight': 5, 'price': 1500},\n {'name': 'Фотоаппарат', 'weight': 3, 'price': 1000},\n {'name': 'Книги', 'weight': 8, 'price': 200},\n {'name': 'Планшет', 'weight': 4, 'price': 800},\n {'name': 'Зонт', 'weight': 6, 'price': 300},\n]\n\nprint(backpack(worths, 15))\n# ==> {'names': ['Фотоаппарат', 'Ноутбук', 'Планшет'], 'sumPrice': 3300, 'sumWeight': 12}\n```\n\n## solutions/Solution.java\n\nСоздайте в классе пакет `solutions` и определите в нем публичный класс `Solution`. Создайте в классе публичный статический метод `backpack()`, который решает задачу о рюкзаке. Метод принимает два параметра\n\n* Список ценностей, `List<Worth>`\n* Грузоподъемность рюкзака, целое число\n\nКаждая ценность представлена объектом класса `Worth`. Изучить этот класс можно в файле *model/Worth.java*\n\nМетод `backpack()` должна вернуть `Map<String, Object>` с полями `names`, `sumPrice` и `sumWeight`\n\n```java\nvar worths = List.of(\n new Worth(\"Ноутбук\", 5, 1500),\n new Worth(\"Фотоаппарат\", 3, 1000),\n new Worth(\"Книги\", 8, 200),\n new Worth(\"Планшет\", 4, 800),\n new Worth(\"Зонт\", 6, 300)\n);\n\nvar result = Solution.backpack(worths, 15);\nSystem.out.println(result);\n// => {names: [\"Фотоаппарат\", \"Ноутбук\", \"Планшет\"], sumPrice: 3300, sumWeight: 12}\n```\n\n\nДобавьте в класс метод `run()` с таким кодом:\n\n```java\npublic static Map<String, Object> run(List<Map<String, Object>> coll, int maxWeight) {\n return helpers.Helper.run(coll, maxWeight);\n}\n```\n\nЭтот метод нужен для проверки вашего решения тестами\n","prepared_readme":"Упражнение можно решить на одном из четырех языков программирования на выбор: JavaScript, PHP, Python или Java. Запишите решение в соответствующем файле в каталоге *solutions*.\n\nВ этом упражнении нужно реализовать алгоритм жадного поиска для задачи о рюкзаке.\n\nБыстрое решение с алгоритмической сложностью `[O(n times log n)]` состоит в том, чтобы упорядочить все ценности по убыванию удельной стоимости. После сортировки надо взять из начала массива столько ценностей, чтобы их суммарная стоимость не превышала грузоподъемность рюкзака.\n\n## solutions/solution.js\n\nНапишите функцию `backpack(worths, maxWeight)`, которая получает на вход:\n\n* `worths` — массив ценностей с полями `name`, `price` и `weight`\n* `maxWeight` — грузоподъемность рюкзака\n\nФункция `backpack()` должна возвращать объект с полями `names`, `sumPrice` и `sumWeight`.\n\n## Пример\n\n```javascript\n\nconst worths = [\n { name: 'Ноутбук', weight: 5, price: 1500 },\n { name: 'Фотоаппарат', weight: 3, price: 1000 },\n { name: 'Книги', weight: 8, price: 200 },\n { name: 'Планшет', weight: 4, price: 800 },\n { name: 'Зонт', weight: 6, price: 300 },\n];\n\nconsole.log(backpack(worths, 15)); \n// ==> {\n// names: [ 'Фотоаппарат', 'Ноутбук', 'Планшет' ],\n// sumPrice: 3300,\n// sumWeight: 12\n// }\n```\n## solutions/solution.php\n\nУсловия такие же как для JavaScript.\n```php\n<?php\n\n$worths = [\n ['name' => 'Ноутбук', 'weight' => 5, 'price' => 1500],\n ['name' => 'Фотоаппарат', 'weight' => 3, 'price' => 1000],\n ['name' => 'Книги', 'weight' => 8, 'price' => 200],\n ['name' => 'Планшет', 'weight' => 4, 'price' => 800],\n ['name' => 'Зонт', 'weight' => 6, 'price' => 300],\n];\n\nprint_r(backpack($worths, 15));\n// ==> Array\n// (\n// [names] => Array\n// (\n// [0] => Фотоаппарат\n// [1] => Ноутбук\n// [2] => Планшет\n// )\n\n// [sumPrice] => 3300\n// [sumWeight] => 12\n// )\n```\n## solutions/solution.py\n\nУсловия такие же как для JavaScript.\n\n```python\nworths = [\n {'name': 'Ноутбук', 'weight': 5, 'price': 1500},\n {'name': 'Фотоаппарат', 'weight': 3, 'price': 1000},\n {'name': 'Книги', 'weight': 8, 'price': 200},\n {'name': 'Планшет', 'weight': 4, 'price': 800},\n {'name': 'Зонт', 'weight': 6, 'price': 300},\n]\n\nprint(backpack(worths, 15))\n# ==> {'names': ['Фотоаппарат', 'Ноутбук', 'Планшет'], 'sumPrice': 3300, 'sumWeight': 12}\n```\n\n## solutions/Solution.java\n\nСоздайте в классе пакет `solutions` и определите в нем публичный класс `Solution`. Создайте в классе публичный статический метод `backpack()`, который решает задачу о рюкзаке. Метод принимает два параметра\n\n* Список ценностей, `List<Worth>`\n* Грузоподъемность рюкзака, целое число\n\nКаждая ценность представлена объектом класса `Worth`. Изучить этот класс можно в файле *model/Worth.java*\n\nМетод `backpack()` должна вернуть `Map<String, Object>` с полями `names`, `sumPrice` и `sumWeight`\n\n```java\nvar worths = List.of(\n new Worth(\"Ноутбук\", 5, 1500),\n new Worth(\"Фотоаппарат\", 3, 1000),\n new Worth(\"Книги\", 8, 200),\n new Worth(\"Планшет\", 4, 800),\n new Worth(\"Зонт\", 6, 300)\n);\n\nvar result = Solution.backpack(worths, 15);\nSystem.out.println(result);\n// => {names: [\"Фотоаппарат\", \"Ноутбук\", \"Планшет\"], sumPrice: 3300, sumWeight: 12}\n```\n\n\nДобавьте в класс метод `run()` с таким кодом:\n\n```java\npublic static Map<String, Object> run(List<Map<String, Object>> coll, int maxWeight) {\n return helpers.Helper.run(coll, maxWeight);\n}\n```\n\nЭтот метод нужен для проверки вашего решения тестами\n","has_solution":true,"entity_name":"Жадные алгоритмы"},"units":[{"id":6624,"name":"theory","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/theory_unit"},{"id":11265,"name":"quiz","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/quiz_unit"},{"id":10981,"name":"exercise","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/exercise_unit"}],"links":[],"ordered_units":[{"id":6624,"name":"theory","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/theory_unit"},{"id":11265,"name":"quiz","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/quiz_unit"},{"id":10981,"name":"exercise","url":"/courses/algorithms-graphs/lessons/greedy-algorithms/exercise_unit"}],"id":2905,"slug":"greedy-algorithms","state":"approved","name":"Жадные алгоритмы","course_order":500,"goal":"Изучаем жадные алгоритмы в сравнении с методом перебора","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В программировании встречаются задачи, которые на первый взгляд не имеют отношения к графам, но решаются именно с помощью алгоритмов на графах. Одна из таких задач возникает на швейных фабриках, где роботы вырезают детали по выкройкам из больших кусков ткани. Для экономии ткани важно оставлять как можно меньше обрезков:\n\nДля примера посмотрим, как выглядит неэкономный раскрой ткани. Здесь остается большой обрезок ткани:\n\n\n\nНо если переставить детали местами, можно выкроить на одну красную деталь больше — и тогда обрезка не будет:\n\n\n\nЧтобы решить такую задачу, можно использовать неявное представление графов. Действуя **методом полного перебора**, можно найти все варианты размещения деталей, чтобы площадь обрезков была как можно меньше.\n\nРассмотрим еще один пример похожей задачи. Она встречается при программировании банкоматов или вендинговых автоматов, где требуется выдать сумму наименьшим количеством банкнот и монет.\n\nПредставим, что нам нужно выдать 11 рублей. Эффективнее всего выдать две монеты: 10 рублей и 1 рубль. Но в автомате может не оказаться десятирублевых монет. Тогда простой алгоритм не найдет альтернативный вариант — одна монета в 5 рублей и три монеты по 2 рубля. Как и в случае с раскроем, все возможные решения можно найти только методом перебора.\n\nПеречисленные задачи не похожи друг на друга, но их принято относить к одному классу, потому что решаются они одинаково. Название всему классу дала **задача о рюкзаке**. Одна из ее формулировок звучит так:\n\n> Попав в пещеру к разбойникам, Али-баба решил забрать как можно больше ценностей. У него есть рюкзак, рассчитанный на 20 килограмм. Нужно выбрать самые ценные предметы так, чтобы они уместились в рюкзак\n\nВ чем сложность задачи? Отобрать самое ценное не так уж и сложно, но есть и второе ограничение — вес предмета. Без перебора мы можем выбрать самый дорогой предмет в комнате — двадцатикилограммовый сундук. Но если проверить все варианты перебором, может оказаться, что два десятикилограммовых кувшина в сумме стоят больше. Тогда нужно унести два кувшина, а не один сундук.\n\nВ целом, в задачах о рюкзаке речь идет о множестве предметов, из которых мы должны выбрать оптимальное подмножество с учетом ограничений. В задаче о раскрое мы минимизировали площадь обрезков, а при выдаче сдачи — количество монет. В задаче о рюкзаке мы максимизируем стоимость предметов. Другими словами, во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве.\n\n## Метод перебора\n\nПеребор — это простейший способ решения задач на графе. В случае задач о рюкзаке граф обычно представлен неявно.\n\nПри этом алгоритм работает рекурсивно. Это значит, что мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии.\n\nРекурсия завершается, когда вес рюкзака превышен. В этот момент мы фиксируем текущее состояние рюкзака — одно из возможных решений. При этом нам не надо хранить все найденные решения, достаточно хранить самое дорогое.\n\nВторой повод для завершения рекурсии — закончились предметы, то есть в рюкзак поместились все предметы. К сожалению, в реальной жизни так почти не бывает.\n\nВзглянем на реализацию такого алгоритма:\n\n```javascript\nconst backpack = (valuables, maxWeight) => {\n const seen = new Set()\n const result = { names: [], sumPrice: 0.0, sumWeight: 0 }\n\n const bruteforce = (sumPrice, sumWeight) => {\n let isLeaf = true\n\n for (const val of valuables) {\n if (seen.has(val)) continue\n if (val.weight + sumWeight > maxWeight) continue\n\n isLeaf = false\n seen.add(val)\n bruteforce(sumPrice + val.price, sumWeight + val.weight)\n seen.delete(val)\n }\n\n if (isLeaf && sumPrice >= result.sumPrice) {\n result.sumPrice = sumPrice\n result.sumWeight = sumWeight\n result.names = Array.from(seen).map(val => val.name)\n }\n }\n\n bruteforce(0.0, 0.0)\n return result\n}\n```\n\nРазберем этот код подробнее. Сначала мы видим функцию `backpack()`, внутри которой мы реализуем `bruteforce()` — рекурсивную функцию перебора.\n\nВ коде мы обращаемся к двум внешним переменным:\n\n* `valuables` — массив ценностей\n* `seen` — множество просмотренных ценностей\n\nВ качестве параметров мы передаем:\n\n* `sumPrice` — общая цена просмотренных ценностей\n* `sumWeight` - общий вес\n\nМожно было бы перебирать ценности в множестве `seen`, но это слишком накладно для каждого шага рекурсии. Поэтому мы перебираем ценности в цикле `for`. Если предмет уже просмотрен, мы сразу переходим к следующему предмету:\n\n```javascript\nif (seen.has(val)) {\n continue\n}\n```\n\nЕсли возникает перевес, мы также переходим к следующему предмету:\n\n```javascript\nif (val.weight + sumWeight > maxWeight) {\n continue\n}\n```\n\nМы выполняем рекурсивные вызовы, пока мы не заполнили весь рюкзак и не достигли вершины-листа в нашем графе. Как и в дереве, листом в графе называется вершина, из которой больше некуда идти:\n\n```javascript\nseen.add(val)\nbruteforce(sumPrice + val.price, sumWeight + val.weight)\nseen.delete(val)\nisLeaf = false\n```\n\nПредставим, что мы больше не можем положить в рюкзак ни одного предмета. Это значит, что мы достигли вершины-листа. Флаг `isLeaf` остается равным `true`:\n\n```javascript\nif (isLeaf && sumPrice >= result.sumPrice) {\n result.sumPrice = sumPrice\n result.sumWeight = sumWeight\n result.names = Array.from(seen.values())\n .map(val => val.name)\n}\n```\n\nЕсли мы достигли листа и нашли новый лучший результат, сохраняем его в полях переменной `result`.\n\nВот что у нас получается в результате:\n\n```javascript\nconst valuables\n= [\n { name: 'Корона', weight: 1.5, price: 150000 },\n { name: 'Кувшинчик', weight: 4, price: 60000 },\n { name: 'Кувшин', weight: 8, price: 80000 },\n { name: 'Шкатулка', weight: 2, price: 40000 },\n { name: 'Сундучок', weight: 8, price: 90000 },\n { name: 'Сундук', weight: 12, price: 120000 },\n { name: 'Диадема', weight: 1.2, price: 70000 },\n { name: 'Горшок с монетами', weight: 3, price: 40000 },\n { name: 'Сабля', weight: 8, price: 90000 },\n { name: 'Скипетр', weight: 6, price: 30000 },\n]\n\nconst decision = backpack(valuables, 20)\n// {\n// names: [\n// 'Сабля',\n// 'Горшок с монетами',\n// 'Диадема',\n// 'Шкатулка',\n// 'Кувшинчик',\n// 'Корона'\n// ],\n// sumPrice: 450000,\n// sumWeight: 19.7\n// }\n```\n\n## Оценка сложности\n\nПопробуем оценить алгоритмическую сложность метода перебора. Как и следует из названия, он перебирает все возможные решения, добираясь до них через все промежуточные вершины графа.\n\nПредставим, что у нас есть предметы `1`, `2`, `3`, `4`. Так будет выглядеть граф, который обходит метод перебора:\n\n\n\nВ этом графе есть 16 вершин, в том числе пустая. Рассмотрим, к какому количеству решений ведет разное количество предметов:\n\n* Ноль предметов дает 1 решение, то есть Али-Баба уходит с пустым рюкзаком. Так произойдет, если в пещере не найдется предмет весом менее 20 килограмм\n* Один предмет дает 2 решения — пустой рюкзак и рюкзак с этим самым предметом\n* Два предмета дают 4 решения — пустой рюкзак, рюкзак с первым предметом, рюкзак со вторым и с двумя предметами сразу. Отметим, что `2*2=4`\n* Три предмета дают 8 решений `2*2*2=8`\n* Четыре предмета дают 16 решений `2*2*2*2=16`\n\nКак мы видим, каждый новый предмет удваивает количество вершин. Поэтому метод перебора обладает экспоненциальной сложностью `O(2^n)`.\n\nЭто довольно медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле.\n\n## Жадный алгоритм\n\nКак отказаться от перебора предметов на каждом шаге рекурсии? Один из подходов заключается в том, чтобы всякий раз выбирать самый ценный предмет. Работающие по этому принципу алгоритмы называются **жадными**.\n\nЧтобы жадный алгоритм выбирал самые ценные предметы, нужно сравнивать не абсолютную, а **удельную стоимость** — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает одну ветвь — с наибольшей удельной стоимостью.\n\nБез предварительной сортировки поиск максимума обладает линейной сложностью. Другими словами, в худшем случае нам придется перебрать все предметы в пещере. Итого, временная сложность жадного алгоритма равна `O(n*n)` или `O(n^2)`.\n\nК сожалению, жадный алгоритм не всегда находит оптимальное решение. Для примера представим, что нам нужно выбрать из двух вариантов:\n\n* 2 короны, каждая весит по 1 килограмму и стоит по 80 тысяч долларов\n* 1 сундук, который весит 20 килограмм и стоит 500 тысяч долларов\n\nУдельная стоимость корон выше, но сундук выгоднее, поэтому стоит забрать именно его. Жадный алгоритм не учтет такой вариант.\n\nДля некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма.\n\nВ таких автоматах применяется сложная математика, которая выходит за пределы данного курса. Если вам интересно изучить эту тему подробнее, можете почитать статью [Canonical Coin Systems for Change-Making Problems](https://arxiv.org/pdf/0809.0400.pdf).\n\n## Выводы\n\nВ этом уроке мы познакомились с жадными алгоритмами. Повторим ключевые мысли из урока:\n\n* Бывают задачи, которые похожи друг на друга по сути, несмотря на внешние различия. Такие задачи относят к одному классу. Например, существует класс «задачи о рюкзаке», по которому можно посчитать сдачу в автомате минимальным количеством монет\n* Во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве. Их можно решить двумя способами — с помощью перебора или жадных алгоритмов\n* При переборе алгоритм работает рекурсивно — мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии. Рекурсия завершается, когда вес рюкзака превышен или закончились предметы\n* Перебор — это медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле\n* Жадный алгоритм выбирает самые ценные предметы, сравнивая **удельную стоимость** — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает ветвь с наибольшей удельной стоимостью\n* Без предварительной сортировки жадный алгоритм обладает линейной сложностью. Для некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":8213,"name":"theory","url":"/courses/algorithms-graphs/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":8213,"name":"theory","url":"/courses/algorithms-graphs/lessons/intro/theory_unit"}],"id":3653,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с темой курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Сначала графы использовались только в математике — с их помощью решались задачи, связанные с картами. Со временем графы пришли и в программирование, потому что они подходят для решения широкого круга задач:\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Обычно банкоматы стараются выдать сумму наименьшим числом банкнот. Если вы попросили 5000 рублей, банкомат выдаст 5000 рублей одной купюрой. Если такой купюры нет, то банкомат выдаст 2000, 2000 и 1000 рублей. Существует быстрый алгоритм, который всегда оптимально решает эту задачу.\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Мы узнаем, что такое **задачи класса NP** и почему они считаются сложным. Также мы познакомимся с несколькими приближенными алгоритмами и решим с их помощью несколько разных задач.\n\n## Выводы\n\nПовторим ключевые выводы курса:\n\n* Задачи на графах часто решаются графически — в таких случаях очевидно, что задачу надо решать через графы\n* Иногда с первого взгляда не понятно, что задача решается с помощью графов. В этом курсе мы научимся распознавать такие задачи\n* Точные алгоритмы на графах работают очень медленно\n* Чтобы быстро решать задачи на графах, программисты прибегают к эвристическим алгоритмам\n* В курсе мы разберем несколько эвристических алгоритмов\n"},"id":289,"slug":"algorithms-graphs","challenges_count":0,"name":"Алгоритмы на графах","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"В этом курсе мы познакомимся с базовыми понятиями из теории графов: NP-полные задачи, поиск пути, жадные алгоритмы. Вы узнаете, решается ли задача коммивояжера, научитесь строить расписание, находить кратчайший путь.","kind":"additional","updated_at":"2026-02-12T14:03:36.118Z","language":"javascript","duration_cache":38220,"skills":["Определять NP-полные задачи и находить приближенное решение","Строить эффективные алгоритмы при работе с графами","Поиску кратчайшего пути","Различию циклических и ациклических графов","Строить граф зависимостей"],"keywords":[],"lessons_count":12,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQyODMsInB1ciI6ImJsb2JfaWQifX0=--f0d34a91cff91cafe6f82811a94fb39b4d710dfb/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-graphs/lessons/greedy-algorithms/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы на графах</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Жадные алгоритмы</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Жадные алгоритмы","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Алгоритмы на графах"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>В программировании встречаются задачи, которые на первый взгляд не имеют отношения к графам, но решаются именно с помощью алгоритмов на графах. Одна из таких задач возникает на швейных фабриках, где роботы вырезают детали по выкройкам из больших кусков ткани. Для экономии ткани важно оставлять как можно меньше обрезков:</p>
<p>Для примера посмотрим, как выглядит неэкономный раскрой ткани. Здесь остается большой обрезок ткани:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzODUsInB1ciI6ImJsb2JfaWQifX0=--cc47819975a4bc1dcab1487f74bffc2257a031c6/02.png" alt="02" loading="lazy"/></p>
<p>Но если переставить детали местами, можно выкроить на одну красную деталь больше — и тогда обрезка не будет:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzODYsInB1ciI6ImJsb2JfaWQifX0=--40d258ba19e428e53526daee667e991e46430438/01.png" alt="01" loading="lazy"/></p>
<p>Чтобы решить такую задачу, можно использовать неявное представление графов. Действуя <strong>методом полного перебора</strong>, можно найти все варианты размещения деталей, чтобы площадь обрезков была как можно меньше.</p>
<p>Рассмотрим еще один пример похожей задачи. Она встречается при программировании банкоматов или вендинговых автоматов, где требуется выдать сумму наименьшим количеством банкнот и монет.</p>
<p>Представим, что нам нужно выдать 11 рублей. Эффективнее всего выдать две монеты: 10 рублей и 1 рубль. Но в автомате может не оказаться десятирублевых монет. Тогда простой алгоритм не найдет альтернативный вариант — одна монета в 5 рублей и три монеты по 2 рубля. Как и в случае с раскроем, все возможные решения можно найти только методом перебора.</p>
<p>Перечисленные задачи не похожи друг на друга, но их принято относить к одному классу, потому что решаются они одинаково. Название всему классу дала <strong>задача о рюкзаке</strong>. Одна из ее формулировок звучит так:</p>
<blockquote>
<p>Попав в пещеру к разбойникам, Али-баба решил забрать как можно больше ценностей. У него есть рюкзак, рассчитанный на 20 килограмм. Нужно выбрать самые ценные предметы так, чтобы они уместились в рюкзак</p>
</blockquote>
<p>В чем сложность задачи? Отобрать самое ценное не так уж и сложно, но есть и второе ограничение — вес предмета. Без перебора мы можем выбрать самый дорогой предмет в комнате — двадцатикилограммовый сундук. Но если проверить все варианты перебором, может оказаться, что два десятикилограммовых кувшина в сумме стоят больше. Тогда нужно унести два кувшина, а не один сундук.</p>
<p>В целом, в задачах о рюкзаке речь идет о множестве предметов, из которых мы должны выбрать оптимальное подмножество с учетом ограничений. В задаче о раскрое мы минимизировали площадь обрезков, а при выдаче сдачи — количество монет. В задаче о рюкзаке мы максимизируем стоимость предметов. Другими словами, во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве.</p>
<h2 id="heading-2-1">Метод перебора</h2>
<p>Перебор — это простейший способ решения задач на графе. В случае задач о рюкзаке граф обычно представлен неявно.</p>
<p>При этом алгоритм работает рекурсивно. Это значит, что мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии.</p>
<p>Рекурсия завершается, когда вес рюкзака превышен. В этот момент мы фиксируем текущее состояние рюкзака — одно из возможных решений. При этом нам не надо хранить все найденные решения, достаточно хранить самое дорогое.</p>
<p>Второй повод для завершения рекурсии — закончились предметы, то есть в рюкзак поместились все предметы. К сожалению, в реальной жизни так почти не бывает.</p>
<p>Взглянем на реализацию такого алгоритма:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const backpack = (valuables, maxWeight) => {
const seen = new Set()
const result = { names: [], sumPrice: 0.0, sumWeight: 0 }
const bruteforce = (sumPrice, sumWeight) => {
let isLeaf = true
for (const val of valuables) {
if (seen.has(val)) continue
if (val.weight + sumWeight > maxWeight) continue
isLeaf = false
seen.add(val)
bruteforce(sumPrice + val.price, sumWeight + val.weight)
seen.delete(val)
}
if (isLeaf && sumPrice >= result.sumPrice) {
result.sumPrice = sumPrice
result.sumWeight = sumWeight
result.names = Array.from(seen).map(val => val.name)
}
}
bruteforce(0.0, 0.0)
return result
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Разберем этот код подробнее. Сначала мы видим функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">backpack()</code>, внутри которой мы реализуем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bruteforce()</code> — рекурсивную функцию перебора.</p>
<p>В коде мы обращаемся к двум внешним переменным:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">valuables</code> — массив ценностей</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">seen</code> — множество просмотренных ценностей</li>
</ul>
<p>В качестве параметров мы передаем:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">sumPrice</code> — общая цена просмотренных ценностей</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">sumWeight</code> - общий вес</li>
</ul>
<p>Можно было бы перебирать ценности в множестве <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">seen</code>, но это слишком накладно для каждого шага рекурсии. Поэтому мы перебираем ценности в цикле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">for</code>. Если предмет уже просмотрен, мы сразу переходим к следующему предмету:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">if (seen.has(val)) {
continue
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если возникает перевес, мы также переходим к следующему предмету:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">if (val.weight + sumWeight > maxWeight) {
continue
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Мы выполняем рекурсивные вызовы, пока мы не заполнили весь рюкзак и не достигли вершины-листа в нашем графе. Как и в дереве, листом в графе называется вершина, из которой больше некуда идти:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">seen.add(val)
bruteforce(sumPrice + val.price, sumWeight + val.weight)
seen.delete(val)
isLeaf = false</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Представим, что мы больше не можем положить в рюкзак ни одного предмета. Это значит, что мы достигли вершины-листа. Флаг <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">isLeaf</code> остается равным <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">if (isLeaf && sumPrice >= result.sumPrice) {
result.sumPrice = sumPrice
result.sumWeight = sumWeight
result.names = Array.from(seen.values())
.map(val => val.name)
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если мы достигли листа и нашли новый лучший результат, сохраняем его в полях переменной <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">result</code>.</p>
<p>Вот что у нас получается в результате:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-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-CodeHighlight-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-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const valuables
= [
{ name: 'Корона', weight: 1.5, price: 150000 },
{ name: 'Кувшинчик', weight: 4, price: 60000 },
{ name: 'Кувшин', weight: 8, price: 80000 },
{ name: 'Шкатулка', weight: 2, price: 40000 },
{ name: 'Сундучок', weight: 8, price: 90000 },
{ name: 'Сундук', weight: 12, price: 120000 },
{ name: 'Диадема', weight: 1.2, price: 70000 },
{ name: 'Горшок с монетами', weight: 3, price: 40000 },
{ name: 'Сабля', weight: 8, price: 90000 },
{ name: 'Скипетр', weight: 6, price: 30000 },
]
const decision = backpack(valuables, 20)
// {
// names: [
// 'Сабля',
// 'Горшок с монетами',
// 'Диадема',
// 'Шкатулка',
// 'Кувшинчик',
// 'Корона'
// ],
// sumPrice: 450000,
// sumWeight: 19.7
// }</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-2">Оценка сложности</h2>
<p>Попробуем оценить алгоритмическую сложность метода перебора. Как и следует из названия, он перебирает все возможные решения, добираясь до них через все промежуточные вершины графа.</p>
<p>Представим, что у нас есть предметы <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">1</code>, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">2</code>, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">3</code>, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">4</code>. Так будет выглядеть граф, который обходит метод перебора:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzODcsInB1ciI6ImJsb2JfaWQifX0=--d0f474102c88cf70d6733eb39f219cd1bb6c925d/03.png" alt="03" loading="lazy"/></p>
<p>В этом графе есть 16 вершин, в том числе пустая. Рассмотрим, к какому количеству решений ведет разное количество предметов:</p>
<ul>
<li>Ноль предметов дает 1 решение, то есть Али-Баба уходит с пустым рюкзаком. Так произойдет, если в пещере не найдется предмет весом менее 20 килограмм</li>
<li>Один предмет дает 2 решения — пустой рюкзак и рюкзак с этим самым предметом</li>
<li>Два предмета дают 4 решения — пустой рюкзак, рюкзак с первым предметом, рюкзак со вторым и с двумя предметами сразу. Отметим, что <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">2*2=4</code></li>
<li>Три предмета дают 8 решений <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">2*2*2=8</code></li>
<li>Четыре предмета дают 16 решений <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">2*2*2*2=16</code></li>
</ul>
<p>Как мы видим, каждый новый предмет удваивает количество вершин. Поэтому метод перебора обладает экспоненциальной сложностью <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(2^n)</code>.</p>
<p>Это довольно медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле.</p>
<h2 id="heading-2-3">Жадный алгоритм</h2>
<p>Как отказаться от перебора предметов на каждом шаге рекурсии? Один из подходов заключается в том, чтобы всякий раз выбирать самый ценный предмет. Работающие по этому принципу алгоритмы называются <strong>жадными</strong>.</p>
<p>Чтобы жадный алгоритм выбирал самые ценные предметы, нужно сравнивать не абсолютную, а <strong>удельную стоимость</strong> — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает одну ветвь — с наибольшей удельной стоимостью.</p>
<p>Без предварительной сортировки поиск максимума обладает линейной сложностью. Другими словами, в худшем случае нам придется перебрать все предметы в пещере. Итого, временная сложность жадного алгоритма равна <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(n*n)</code> или <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">O(n^2)</code>.</p>
<p>К сожалению, жадный алгоритм не всегда находит оптимальное решение. Для примера представим, что нам нужно выбрать из двух вариантов:</p>
<ul>
<li>2 короны, каждая весит по 1 килограмму и стоит по 80 тысяч долларов</li>
<li>1 сундук, который весит 20 килограмм и стоит 500 тысяч долларов</li>
</ul>
<p>Удельная стоимость корон выше, но сундук выгоднее, поэтому стоит забрать именно его. Жадный алгоритм не учтет такой вариант.</p>
<p>Для некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма.</p>
<p>В таких автоматах применяется сложная математика, которая выходит за пределы данного курса. Если вам интересно изучить эту тему подробнее, можете почитать статью <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://arxiv.org/pdf/0809.0400.pdf" rel="noopener noreferrer" target="_blank">Canonical Coin Systems for Change-Making Problems</a>.</p>
<h2 id="heading-2-4">Выводы</h2>
<p>В этом уроке мы познакомились с жадными алгоритмами. Повторим ключевые мысли из урока:</p>
<ul>
<li>Бывают задачи, которые похожи друг на друга по сути, несмотря на внешние различия. Такие задачи относят к одному классу. Например, существует класс «задачи о рюкзаке», по которому можно посчитать сдачу в автомате минимальным количеством монет</li>
<li>Во всех «задачах о рюкзаке» речь идет о множестве, наборе ограничений и оптимальном подмножестве. Их можно решить двумя способами — с помощью перебора или жадных алгоритмов</li>
<li>При переборе алгоритм работает рекурсивно — мы начинаем с пустого рюкзака и на каждом шаге рекурсии пытаемся положить в него один из оставшихся предметов. Если максимальный вес не превышен, то мы помещаем предмет в рюкзак и опускаемся на следующий уровень рекурсии. Рекурсия завершается, когда вес рюкзака превышен или закончились предметы</li>
<li>Перебор — это медленный алгоритм. Для десяти предметов он сработает почти мгновенно, а для тридцати — займет уже несколько часов. Для шестидесяти предметов задачу решить уже невозможно — не хватит мощности всех компьютеров на Земле</li>
<li>Жадный алгоритм выбирает самые ценные предметы, сравнивая <strong>удельную стоимость</strong> — то есть стоимость одного килограмма. Как и метод перебора, жадный алгоритм рекурсивно двигается по неявному графу, но на каждом шаге он выбирает ветвь с наибольшей удельной стоимостью</li>
<li>Без предварительной сортировки жадный алгоритм обладает линейной сложностью. Для некоторых задач жадный алгоритм дает самое быстрое и эффективное решение. Например, в большинстве стран вендинговые автоматы выдают сдачу наименьшим числом монет именно за счет жадного алгоритма</li>
</ul></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-graphs/lessons/greedy-algorithms/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 / 12</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><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-graphs/lessons/greedy-algorithms/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></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-Bukl1lYy.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-BrRXra1y.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>