<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.57.17.214&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.57.17.214&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/37.57.17.214"/>
		<updated>2026-05-19T18:04:10Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B5%D0%BA%D0%B0%D1%80%D1%82%D0%BE%D0%B2%D0%BE_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE&amp;diff=56296</id>
		<title>Декартово дерево</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B5%D0%BA%D0%B0%D1%80%D1%82%D0%BE%D0%B2%D0%BE_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE&amp;diff=56296"/>
				<updated>2016-11-26T08:32:36Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Другой алгоритм за O(n\log n) */ опечатка&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;''Эта статья про курево''&lt;br /&gt;
&lt;br /&gt;
'''Декартово дерево или дерамида''' (англ. ''Treap'') {{---}} это структура данных, объединяющая в себе [[Дерево поиска, наивная реализация|бинарное дерево поиска]] и [[Двоичная куча|бинарную кучу]] (отсюда и второе её название: treap (tree + heap) и дерамида (дерево + пирамида), также существует название курево (куча + дерево).&lt;br /&gt;
&lt;br /&gt;
Более строго, это бинарное дерево, в узлах которого хранится пары &amp;lt;tex&amp;gt; (x,y) &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; {{---}} это ключ, а &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; {{---}} это приоритет. Также оно является двоичным деревом поиска по &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; и пирамидой по &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;. Предполагая, что все &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; и все &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; являются различными, получаем, что если некоторый элемент дерева содержит &amp;lt;tex&amp;gt;(x_0,y_0)&amp;lt;/tex&amp;gt;, то у всех элементов в левом поддереве &amp;lt;tex&amp;gt;x &amp;lt; x_0&amp;lt;/tex&amp;gt;, у всех элементов в правом поддереве &amp;lt;tex&amp;gt; x &amp;gt; x_0&amp;lt;/tex&amp;gt;, а также и в левом, и в правом поддереве имеем: &amp;lt;tex&amp;gt; y &amp;lt; y_0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Дерамиды были предложены Сиделем (Siedel) и Арагоном (Aragon) в 1996 г.&lt;br /&gt;
&lt;br /&gt;
== Операции в декартовом дереве ==&lt;br /&gt;
=== split ===&lt;br /&gt;
[[file:split.png|thumb|400px|Операция split]]&lt;br /&gt;
&lt;br /&gt;
Операция &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt; (''разрезать'') позволяет сделать следующее, разрезать исходное дерево &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; по ключу &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;. Возвращать она будет такую пару деревьев &amp;lt;tex&amp;gt;\langle T_1, T_2\rangle &amp;lt;/tex&amp;gt;, что в дереве &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; ключи меньше &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, а в дереве &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; все остальные:  &amp;lt;tex&amp;gt;\mathrm{split}(T, k) \to \langle T_1, T_2\rangle &amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Эта операция устроена следующим образом.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня.&lt;br /&gt;
Посмотрим, как будут устроены результирующие деревья &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;:&lt;br /&gt;
* &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt;: левое поддерево &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; совпадёт с левым поддеревом &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;. Для нахождения правого поддерева &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt;, нужно разрезать правое поддерево &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;T^R_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T^R_2&amp;lt;/tex&amp;gt; по ключу &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; и взять &amp;lt;tex&amp;gt;T^R_1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
* &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; совпадёт с &amp;lt;tex&amp;gt;T^R_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Случай, в котором требуется разрезать дерево по ключу, меньше либо равному ключа в корне, рассматривается симметрично.&lt;br /&gt;
&lt;br /&gt;
=== Псевдокод ===&lt;br /&gt;
&lt;br /&gt;
 '''&amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;Treap, Treap&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt;''' split(t: '''Treap''', k: '''int'''):&lt;br /&gt;
   '''if''' t == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;&amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt;     &lt;br /&gt;
   '''else if''' k &amp;gt; t.x &lt;br /&gt;
     &amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;t1, t2&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt; = split(t.right, k)&lt;br /&gt;
     t.right = t1&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;t, t2&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt;&lt;br /&gt;
   '''else''' &lt;br /&gt;
     &amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;t1, t2&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt; = split(t.left, k)&lt;br /&gt;
     t.left = t2&lt;br /&gt;
     '''return''' &amp;lt;tex&amp;gt;\langle&amp;lt;/tex&amp;gt;t1, t&amp;lt;tex&amp;gt;\rangle&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Время работы ===&lt;br /&gt;
&lt;br /&gt;
Оценим время работы операции &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt;. Во время выполнения вызывается одна операция &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt; для&lt;br /&gt;
дерева хотя бы на один меньшей высоты и делается ещё &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; операций. Тогда итоговая трудоёмкость этой операции&lt;br /&gt;
равна &amp;lt;tex&amp;gt;O(h)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;h&amp;lt;/tex&amp;gt; {{---}} высота дерева.&lt;br /&gt;
&lt;br /&gt;
=== merge ===&lt;br /&gt;
[[file:merge.png|thumb|400px|Операция merge]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим вторую операцию с декартовыми деревьями {{---}} &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt; (''слить''). &lt;br /&gt;
&lt;br /&gt;
С помощью этой операции можно слить два декартовых дерева в одно.&lt;br /&gt;
Причем, все ключи в первом(''левом'') дереве должны быть меньше, чем&lt;br /&gt;
ключи во втором(''правом''). В результате получается дерево, в котором есть все ключи из первого и второго деревьев:  &amp;lt;tex&amp;gt;\mathrm{merge}(T_1, T_2) \to \{T\}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рассмотрим принцип работы этой операции. Пусть нужно слить деревья &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Тогда, очевидно, у результирующего дерева &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; есть корень. &lt;br /&gt;
Корнем станет вершина из &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; с наибольшим приоритетом &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;. Но вершина с самым большим &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; из всех вершин деревьев &lt;br /&gt;
&amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; может быть только либо корнем &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt;, либо корнем &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Рассмотрим случай, в котором корень &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; имеет больший &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;, чем корень &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Случай, в котором корень &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; имеет больший &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;, чем корень &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt;, симметричен этому.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; корня &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; больше &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; корня &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;, то он и будет являться корнем. Тогда левое поддерево &lt;br /&gt;
&amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; совпадёт с левым поддеревом &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt;. Справа же нужно подвесить объединение правого поддерева&lt;br /&gt;
&amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; и дерева &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Псевдокод ===&lt;br /&gt;
&lt;br /&gt;
 '''Treap''' merge(t1: '''Treap''', t2: '''Treap'''):&lt;br /&gt;
   '''if''' t2 == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' t1&lt;br /&gt;
   '''if''' t1 == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;&lt;br /&gt;
     '''return''' t2&lt;br /&gt;
   '''else if''' t1.y &amp;gt; t2.y&lt;br /&gt;
     t1.right = merge(t1.right, t2)&lt;br /&gt;
     '''return''' t1&lt;br /&gt;
   '''else''' &lt;br /&gt;
     t2.left = merge(t1, t2.left)&lt;br /&gt;
     '''return''' t2&lt;br /&gt;
&lt;br /&gt;
=== Время работы ===&lt;br /&gt;
&lt;br /&gt;
Рассуждая аналогично операции &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt; приходим к выводу, что трудоёмкость операции &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt; &lt;br /&gt;
равна &amp;lt;tex&amp;gt;O(h)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;h&amp;lt;/tex&amp;gt; {{---}} высота дерева.&lt;br /&gt;
&lt;br /&gt;
=== insert ===&lt;br /&gt;
Операция &amp;lt;tex&amp;gt;\mathrm{insert}(T, k)&amp;lt;/tex&amp;gt; добавляет в дерево &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; элемент &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;k.x&amp;lt;/tex&amp;gt; {{---}} ключ, а &amp;lt;tex&amp;gt;k.y&amp;lt;/tex&amp;gt;{{---}} приоритет.&lt;br /&gt;
&lt;br /&gt;
Представим что элемент &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, это декартово дерево из одного элемента, и для того чтобы его добавить в наше декартово дерево &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt;, очевидно, нам нужно их слить. Но &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; может содержать ключи как меньше, так и больше ключа &amp;lt;tex&amp;gt;k.x&amp;lt;/tex&amp;gt;, поэтому сначала нужно разрезать &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; по ключу &amp;lt;tex&amp;gt;k.x&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Реализация №1 &lt;br /&gt;
# Разобьём наше дерево по ключу, который мы хотим добавить, то есть &amp;lt;tex&amp;gt;\mathrm{split}(T, k.x) \to \langle T_1, T_2\rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Сливаем первое дерево с новым элементом, то есть &amp;lt;tex&amp;gt;\mathrm{merge}(T_1, k) \to T_1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Сливаем получившиеся дерево со вторым, то есть &amp;lt;tex&amp;gt;\mathrm{merge}(T_1, T_2) \to T&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Реализация №2  &lt;br /&gt;
# Сначала спускаемся по дереву (как в обычном бинарном дереве поиска по &amp;lt;tex&amp;gt;k.x&amp;lt;/tex&amp;gt;), но останавливаемся на первом элементе, в котором значение приоритета оказалось меньше &amp;lt;tex&amp;gt;k.y&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Теперь вызываем &amp;lt;tex&amp;gt;\mathrm{split}(T, k.x) \to \langle T_1, T_2\rangle&amp;lt;/tex&amp;gt; от найденного элемента (от элемента вместе со всем его поддеревом) &lt;br /&gt;
# Полученные &amp;lt;tex&amp;gt;T_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;T_2&amp;lt;/tex&amp;gt; записываем в качестве левого и правого сына добавляемого элемента.&lt;br /&gt;
# Полученное дерево ставим на место элемента, найденного в первом пункте.&lt;br /&gt;
&lt;br /&gt;
В первой реализации два раза используется &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt;, а во второй реализации слияние вообще не используется.&lt;br /&gt;
&lt;br /&gt;
=== remove ===&lt;br /&gt;
Операция &amp;lt;tex&amp;gt;\mathrm{remove}(T, x)&amp;lt;/tex&amp;gt; удаляет из дерева &amp;lt;tex&amp;gt;T&amp;lt;/tex&amp;gt; элемент с ключом &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Реализация №1&lt;br /&gt;
&lt;br /&gt;
# Разобьём наше дерево по ключу, который мы хотим удалить, то есть &amp;lt;tex&amp;gt;\mathrm{split }(T, k.x) \to \langle T_1, T_2\rangle&amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Теперь отделяем от первого дерева элемент &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;, то есть самого левого ребенка дерева &amp;lt;tex&amp;gt; T_2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Сливаем первое дерево со вторым, то есть &amp;lt;tex&amp;gt;\mathrm{merge }(T_1, T_2) \to T&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
* Реализация №2&lt;br /&gt;
# Спускаемся по дереву (как в обычном бинарном дереве поиска по &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;), и ищем удаляемый элемент. &lt;br /&gt;
# Найдя элемент, вызываем &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt; его левого и правого сыновей&lt;br /&gt;
# Результат процедуры &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt; ставим на место удаляемого элемента.&lt;br /&gt;
&lt;br /&gt;
В первой реализации один раз используется &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt;, а во второй реализации разрезание вообще не используется.&lt;br /&gt;
&lt;br /&gt;
== Построение декартова дерева ==&lt;br /&gt;
Пусть нам известно из каких пар &amp;lt;tex&amp;gt;(x_i, y_i)&amp;lt;/tex&amp;gt; требуется построить декартово дерево, причем также известно, что &amp;lt;tex&amp;gt;x_1 &amp;lt; x_2 &amp;lt; \ldots &amp;lt; x_n&amp;lt;/tex&amp;gt;.&lt;br /&gt;
=== Алгоритм за &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; ===&lt;br /&gt;
Отсортируем все приоритеты по убыванию за &amp;lt;tex&amp;gt; O(n\log n) &amp;lt;/tex&amp;gt; и выберем первый из них, пусть это будет &amp;lt;tex&amp;gt;y_k&amp;lt;/tex&amp;gt;. Сделаем &amp;lt;tex&amp;gt;(x_k, y_k)&amp;lt;/tex&amp;gt; корнем дерева. Проделав то же самое с остальными вершинами получим левого и правого сына &amp;lt;tex&amp;gt;(x_k, y_k)&amp;lt;/tex&amp;gt;. В среднем высота Декартова дерева &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;  (см. далее) и на каждом уровне мы сделали &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; операций. Значит такой алгоритм работает за &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Другой алгоритм за &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt; ===&lt;br /&gt;
Отсортируем парочки &amp;lt;tex&amp;gt;(x_i, y_i)&amp;lt;/tex&amp;gt; по убыванию &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt;  и положим их в очередь. Сперва достанем из очереди первые &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; элемента и сольем их в дерево и положим в конец очереди, затем сделаем то же самое со следующими двумя и т.д. Таким образом, мы сольем сначала &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; деревьев размера &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, затем &amp;lt;tex&amp;gt;\dfrac{n}{2}&amp;lt;/tex&amp;gt; деревьев размера &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; и так далее. При этом на уменьшение размера очереди в два раза мы будем тратить суммарно &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; время на слияния, а всего таких уменьшений будет &amp;lt;tex&amp;gt;\log n&amp;lt;/tex&amp;gt;. Значит полное время работы алгоритма будет &amp;lt;tex&amp;gt;O(n\log n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Алгоритм за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; ===&lt;br /&gt;
Будем строить дерево слева направо, то есть начиная с &amp;lt;tex&amp;gt;(x_1, y_1)&amp;lt;/tex&amp;gt; по &amp;lt;tex&amp;gt;(x_n, y_n)&amp;lt;/tex&amp;gt;, при этом помнить последний добавленный элемент &amp;lt;tex&amp;gt;(x_k, y_k)&amp;lt;/tex&amp;gt;. Он будет самым правым, так как у него будет максимальный ключ, а по ключам декартово дерево представляет собой [[Дерево поиска, наивная реализация|двоичное дерево поиска]]. При добавлении &amp;lt;tex&amp;gt;(x_{k+1}, y_{k+1})&amp;lt;/tex&amp;gt;, пытаемся сделать его правым сыном &amp;lt;tex&amp;gt;(x_k, y_k)&amp;lt;/tex&amp;gt;, это следует сделать если &amp;lt;tex&amp;gt;y_k &amp;gt; y_{k+1}&amp;lt;/tex&amp;gt;, иначе делаем шаг к предку последнего элемента и смотрим его значение &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;. Поднимаемся до тех пор, пока приоритет в рассматриваемом элементе меньше приоритета в добавляемом, после чего делаем &amp;lt;tex&amp;gt;(x_{k+1}, y_{k+1})&amp;lt;/tex&amp;gt; его правым сыном, а предыдущего правого сына делаем левым сыном &amp;lt;tex&amp;gt;(x_{k+1}, y_{k+1})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Заметим, что каждую вершину мы посетим максимум дважды: при непосредственном добавлении и, поднимаясь вверх (ведь после этого вершина будет лежать в чьем-то левом поддереве, а мы поднимаемся только по правому). Из этого следует, что построение происходит за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Случайные приоритеты ==&lt;br /&gt;
Мы уже выяснили, что сложность операций с декартовым деревом линейно зависит от его высоты. В действительности высота декартова дерева может быть линейной относительно его размеров. Например, высота декартова дерева, построенного по набору ключей &amp;lt;tex&amp;gt;(1, 1), \ldots, (n, n)&amp;lt;/tex&amp;gt;, будет равна &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;. Во избежание таких случаев, полезным оказывается выбирать приоритеты в ключах случайно.&lt;br /&gt;
&lt;br /&gt;
== Высота в декартовом дереве с случайными приоритетами ==&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement = В декартовом дереве из &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; узлов, приоритеты &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; которого являются [[Дискретная случайная величина|случайными величинами]] c равномерным распределением, средняя глубина вершины &amp;lt;tex&amp;gt;O(\log n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof=&lt;br /&gt;
&lt;br /&gt;
Будем считать, что все выбранные приоритеты &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; попарно различны.&lt;br /&gt;
&lt;br /&gt;
Для начала введем несколько обозначений:&lt;br /&gt;
* &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; {{---}} вершина с &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;-ым по величине ключом;&lt;br /&gt;
* индикаторная величина &amp;lt;tex&amp;gt;A_{i, j} = \left\{\begin{array}{lllc} 1 ,&amp;amp;&amp;amp; x_i\  \text{is ancestor of} \ x_j\\ &lt;br /&gt;
0 ,&amp;amp;&amp;amp; \text{otherwise}\\&lt;br /&gt;
\end{array}\right.&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;d(v)&amp;lt;/tex&amp;gt; {{---}} глубина вершины &amp;lt;tex&amp;gt;v&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
В силу обозначений глубину вершины можно записать как количество предков:&lt;br /&gt;
:&amp;lt;tex&amp;gt;d(x_k) = \sum\limits_{i = 1}^{n} A_{i,k} &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Теперь можно выразить [[Математическое ожидание случайной величины|математическое ожидание]] глубины конкретной вершины:&lt;br /&gt;
:&amp;lt;tex&amp;gt;E(d(x_k)) = \sum\limits_{i = 1}^{n} Pr[A_{i,k} = 1] &amp;lt;/tex&amp;gt; {{---}} здесь мы использовали линейность математического ожидания, и то что &amp;lt;tex&amp;gt;E(X) = Pr[X = 1]&amp;lt;/tex&amp;gt; для индикаторной величины &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; (&amp;lt;tex&amp;gt;Pr[A]&amp;lt;/tex&amp;gt; {{---}} вероятность события &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;).&lt;br /&gt;
Для подсчёта средней глубины вершин нам нужно сосчитать вероятность того,  что вершина &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; является предком вершины &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;Pr[A_{i,k} = 1]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Введем новое обозначение:&lt;br /&gt;
* &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt; {{---}}  множество ключей &amp;lt;tex&amp;gt;\{x_i, \ldots, x_k\}&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;\{x_k, \ldots, x_i\}&amp;lt;/tex&amp;gt;,  в зависимости от &amp;lt;tex&amp;gt;i &amp;lt; k&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;i &amp;gt; k&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;X_{k, i}&amp;lt;/tex&amp;gt; обозначают одно и тоже, их мощность равна &amp;lt;tex&amp;gt;|k - i| + 1&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=Для любых &amp;lt;tex&amp;gt;i \ne k&amp;lt;/tex&amp;gt; , &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; является предком &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; тогда и только тогда, когда &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; имеет наибольший приоритет среди &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof=Если &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; является корнем, то оно является предком &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; и по определению имеет максимальный приоритет среди всех вершин, следовательно, и среди &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
С другой стороны,  если &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; {{---}}  корень,  то &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; {{---}}  не предок &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt;, и &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; имеет максимальный приоритет в декартовом дереве; следовательно, &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; не имеет наибольший приоритет среди &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Теперь предположим, что какая-то другая   вершина &amp;lt;tex&amp;gt;x_m&amp;lt;/tex&amp;gt; {{---}} корень. Тогда, если &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; лежат в разных поддеревьях, то &amp;lt;tex&amp;gt;i &amp;lt; m &amp;lt; k&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;i &amp;gt; m &amp;gt; k&amp;lt;/tex&amp;gt;, следовательно, &amp;lt;tex&amp;gt;x_m&amp;lt;/tex&amp;gt; содержится в &amp;lt;tex&amp;gt;X_{i , k}&amp;lt;/tex&amp;gt;. В этом случае &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; {{---}} не предок &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt;, и наибольший приоритет среди &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt; имеет вершина с номером &amp;lt;tex&amp;gt;m&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Наконец,  если &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;x_k&amp;lt;/tex&amp;gt; лежат в одном поддереве,  то доказательство применяется по индукции: пустое декартово дерево есть тривиальная база,  а рассматриваемое поддерево является меньшим декартовым деревом. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Так как распределение приоритетов равномерное, каждая вершина среди &amp;lt;tex&amp;gt;X_{i, k}&amp;lt;/tex&amp;gt; может иметь максимальный приоритет,  мы немедленно приходим к следующему равенству: &lt;br /&gt;
: &amp;lt;tex&amp;gt;Pr[A_{i, j} = 1] = \left\{\begin{array}{lllc} \dfrac{1}{k - i + 1} ,&amp;amp;&amp;amp; k \ &amp;gt; \ i\\ &lt;br /&gt;
0 ,&amp;amp;&amp;amp; k\ =\ i\\&lt;br /&gt;
\dfrac{1}{i - k + 1} ,&amp;amp;&amp;amp; k \ &amp;lt; \ i\\&lt;br /&gt;
\end{array}\right.&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Подставив последнее в нашу формулу с математическим ожиданием получим: &lt;br /&gt;
:&amp;lt;tex&amp;gt;E(d(x_k)) = \sum\limits_{i = 1}^{n} Pr[A_{i,k} = 1] = \sum\limits_{i = 1}^{k - 1}\dfrac{1}{k - i + 1} + \sum\limits_{i = k + 1}^{n}\dfrac{1}{i - k + 1} \leqslant &amp;lt;/tex&amp;gt;&lt;br /&gt;
&amp;lt;tex&amp;gt;\leqslant \ln(k) +  \ln(n - k)+2&amp;lt;/tex&amp;gt; (здесь мы использовали неравенство &amp;lt;tex&amp;gt;\sum\limits_{i = 1}^{n}  \dfrac{1}{i} \leqslant \ln(n) + 1&amp;lt;/tex&amp;gt;)&lt;br /&gt;
: &amp;lt;tex&amp;gt;\log(n)&amp;lt;/tex&amp;gt; отличается от &amp;lt;tex&amp;gt;\ln(n)&amp;lt;/tex&amp;gt; в константу раз, поэтому &amp;lt;tex&amp;gt;\log(n) = O(\ln(n))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В итоге мы получили что &amp;lt;tex&amp;gt;E(d(x_k)) = O(\log(n))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Таким образом, среднее время работы операций &amp;lt;tex&amp;gt;\mathrm{split}&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathrm{merge}&amp;lt;/tex&amp;gt; будет &amp;lt;tex&amp;gt;O(\log(n))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
* [[Декартово дерево по неявному ключу]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
*[http://ru.wikipedia.org/wiki/Декартово_дерево Декартово дерево — Википедия]&lt;br /&gt;
*[http://rain.ifmo.ru/cat/data/theory/trees/treaps-2006/article.pdf Treaps и T-Treaps]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория:Деревья поиска]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D0%BA%D1%83%D1%87%D0%B0&amp;diff=55529</id>
		<title>Двоичная куча</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D0%BA%D1%83%D1%87%D0%B0&amp;diff=55529"/>
				<updated>2016-10-15T03:45:39Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Определение */ лишняя запятая&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Определение==&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Двоичная куча''' или '''пирамида''' (англ. ''Binary heap'') — такое двоичное [[Дерево, эквивалентные определения|подвешенное дерево]], для которого выполнены следующие три условия:&lt;br /&gt;
&lt;br /&gt;
* Значение в любой вершине не меньше (если куча для максимума), чем значения её потомков.&lt;br /&gt;
* На &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-ом слое &amp;lt;tex&amp;gt;2^i&amp;lt;/tex&amp;gt; вершин, кроме последнего. Слои нумеруются с нуля.&lt;br /&gt;
* Последний слой заполнен слева направо (как показано на рисунке)&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:Min_heap.png‎|thumb|325px|Пример кучи для минимума]]&lt;br /&gt;
[[Файл:Min_heap_array.png‎|thumb|325px|Хранение кучи в массиве, красная стрелка {{---}} левый сын, зеленая {{---}} правый]]&lt;br /&gt;
Удобнее всего двоичную кучу хранить в виде массива &amp;lt;tex&amp;gt;a[0..n-1]&amp;lt;/tex&amp;gt;, у которого нулевой элемент, &amp;lt;tex&amp;gt;a[0]&amp;lt;/tex&amp;gt; — элемент в корне, а потомками элемента &amp;lt;tex&amp;gt;a[i]&amp;lt;/tex&amp;gt; являются &amp;lt;tex&amp;gt;a[2i+1]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;a[2i+2]&amp;lt;/tex&amp;gt;. Высота кучи определяется как высота двоичного дерева. То есть она равна количеству рёбер в самом длинном простом пути, соединяющем корень кучи с одним из её листьев. Высота кучи есть &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; — количество узлов дерева.&lt;br /&gt;
&lt;br /&gt;
Чаще всего используют кучи для минимума (когда предок не больше детей) и для максимума (когда предок не меньше детей).&lt;br /&gt;
&lt;br /&gt;
Двоичные кучи используют, например, для того, чтобы извлекать минимум из набора чисел за &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;. Они являются частным случаем приоритетных очередей.&lt;br /&gt;
&lt;br /&gt;
==Базовые процедуры==&lt;br /&gt;
&lt;br /&gt;
===Восстановление свойств кучи===&lt;br /&gt;
&lt;br /&gt;
Если в куче изменяется один из элементов, то она может перестать удовлетворять свойству упорядоченности. Для восстановления этого свойства служат процедуры &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; (просеивание вниз)&lt;br /&gt;
и &amp;lt;tex&amp;gt; \mathrm {siftUp} &amp;lt;/tex&amp;gt; (просеивание вверх). &lt;br /&gt;
Если значение измененного элемента увеличивается, то свойства кучи восстанавливаются функцией &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt;.&lt;br /&gt;
Работа процедуры: если &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-й элемент меньше, чем его сыновья, всё поддерево уже является кучей, и делать ничего не надо. В противном случае меняем местами &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;-й элемент с наименьшим из его сыновей, после чего выполняем &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; для этого сына.&lt;br /&gt;
Процедура выполняется за время &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
====siftDown====&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' siftDown(i : '''int'''):&lt;br /&gt;
     '''while''' 2 * i + 1 &amp;lt;tex&amp;gt;&amp;lt;&amp;lt;/tex&amp;gt; a.heapSize     &amp;lt;font color = &amp;quot;green&amp;quot;&amp;gt;// heapSize {{---}} количество элементов в куче&amp;lt;/font&amp;gt;&lt;br /&gt;
         left = 2 * i + 1             &amp;lt;font color = &amp;quot;green&amp;quot;&amp;gt;// left {{---}} левый сын&amp;lt;/font&amp;gt;&lt;br /&gt;
         right = 2 * i + 2            &amp;lt;font color = &amp;quot;green&amp;quot;&amp;gt;// right {{---}} правый сын&amp;lt;/font&amp;gt;&lt;br /&gt;
         j = left&lt;br /&gt;
         '''if''' right &amp;lt;tex&amp;gt;&amp;lt;&amp;lt;/tex&amp;gt; a.heapSize '''and''' a[right] &amp;lt;tex&amp;gt;&amp;lt;&amp;lt;/tex&amp;gt; a[left]&lt;br /&gt;
             j = right&lt;br /&gt;
         '''if''' a[i] &amp;lt;tex&amp;gt;\leqslant&amp;lt;/tex&amp;gt; a[j]&lt;br /&gt;
             '''break'''&lt;br /&gt;
         swap(a[i], a[j])&lt;br /&gt;
         i = j&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====siftUp====&lt;br /&gt;
Если значение измененного элемента уменьшается, то свойства кучи восстанавливаются функцией &amp;lt;tex&amp;gt; \mathrm {siftUp} &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Работа процедуры: если элемент больше своего отца, условие 1 соблюдено для всего дерева, и больше ничего делать не нужно. Иначе, мы меняем местами его с отцом. После чего выполняем &amp;lt;tex&amp;gt; \mathrm {siftUp} &amp;lt;/tex&amp;gt;&lt;br /&gt;
для этого отца. Иными словами, слишком маленький элемент всплывает наверх.&lt;br /&gt;
Процедура выполняется за время &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' siftUp(i : '''int'''):&lt;br /&gt;
     '''while''' a[i] &amp;lt;tex&amp;gt;&amp;lt;&amp;lt;/tex&amp;gt; a[(i - 1) / 2]     &amp;lt;font color = &amp;quot;green&amp;quot;&amp;gt;// i &amp;lt;tex&amp;gt;==&amp;lt;/tex&amp;gt; 0 {{---}} мы в корне&amp;lt;/font&amp;gt;&lt;br /&gt;
         swap(a[i], a[(i - 1) / 2])&lt;br /&gt;
         i = (i - 1) / 2&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Извлечение минимального элемента===&lt;br /&gt;
&lt;br /&gt;
Выполняет извлечение минимального элемента из кучи за время &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Извлечение выполняется в четыре этапа:&lt;br /&gt;
# Значение корневого элемента (он и является минимальным) сохраняется для последующего возврата.&lt;br /&gt;
# Последний элемент копируется в корень, после чего удаляется из кучи.&lt;br /&gt;
# Вызывается &amp;lt;math&amp;gt; \mathrm {siftDown} &amp;lt;/math&amp;gt; для корня.&lt;br /&gt;
# Сохранённый элемент возвращается.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''int''' extractMin():&lt;br /&gt;
     '''int''' min = a[0]&lt;br /&gt;
     a[0] = a[a.heapSize - 1]&lt;br /&gt;
     a.heapSize = a.heapSize - 1&lt;br /&gt;
     siftDown(0)&lt;br /&gt;
     '''return''' min&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Добавление нового элемента===&lt;br /&gt;
&lt;br /&gt;
Выполняет добавление элемента в кучу за время &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Добавление произвольного элемента в конец кучи, и восстановление свойства упорядоченности с помощью процедуры &amp;lt;math&amp;gt; \mathrm {siftUp} &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' insert(key : '''int'''):&lt;br /&gt;
     a.heapSize = a.heapSize + 1&lt;br /&gt;
     a[a.heapSize - 1] = key&lt;br /&gt;
     siftUp(a.heapSize - 1)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Построение кучи за O(n) ===&lt;br /&gt;
{{Определение | definition =&lt;br /&gt;
'''&amp;lt;tex&amp;gt;D&amp;lt;/tex&amp;gt;-куча''' {{---}} это куча, в которой у каждого элемента, кроме, возможно, элементов на последнем уровне, ровно &amp;lt;tex&amp;gt;d&amp;lt;/tex&amp;gt; потомков. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Дан массив &amp;lt;tex&amp;gt;a[0.. n - 1].&amp;lt;/tex&amp;gt; Требуется построить &amp;lt;tex&amp;gt;d&amp;lt;/tex&amp;gt;-кучу с минимумом в корне. Наиболее очевидный способ построить такую кучу из неупорядоченного массива {{---}} сделать нулевой элемент массива корнем, а дальше по очереди добавить все его элементы в конец кучи и запускать от каждого добавленного элемента &amp;lt;math&amp;gt;\mathrm {siftUp}&amp;lt;/math&amp;gt;. Временная оценка такого алгоритма &amp;lt;tex&amp;gt; O(n\log{n})&amp;lt;/tex&amp;gt;. Однако можно построить кучу еще быстрее — за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Представим, что в массиве хранится дерево (&amp;lt;tex&amp;gt;a[0] - &amp;lt;/tex&amp;gt;  корень, а потомками элемента &amp;lt;tex&amp;gt;a[i]&amp;lt;/tex&amp;gt; являются &amp;lt;tex&amp;gt;a[di+1]...a[di+d]&amp;lt;/tex&amp;gt;). Сделаем &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; для вершин, имеющих хотя бы одного потомка: от &amp;lt;tex dpi=140&amp;gt;\dfrac{n}{d}&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;,{{---}} так как поддеревья, состоящие из одной вершины без потомков, уже упорядочены.&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement= На выходе получим искомую кучу. &lt;br /&gt;
|proof= При вызове &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; для вершины, ее поддеревья являются кучами. После выполнения &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; эта вершина с ее поддеревьями будут также являться кучей.  Значит, после выполнения всех &amp;lt;tex&amp;gt; \mathrm {siftDown} &amp;lt;/tex&amp;gt; получится куча.&lt;br /&gt;
}}&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement= Время работы этого алгоритма &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof= Число вершин на высоте &amp;lt;tex&amp;gt;h&amp;lt;/tex&amp;gt; в куче из &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; элементов не превосходит &amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt;  \left \lceil \frac{n}{d^h} \right \rceil &amp;lt;/tex&amp;gt;.  Высота кучи не превосходит &amp;lt;tex&amp;gt; \log_{d}n &amp;lt;/tex&amp;gt;. Обозначим за &amp;lt;tex&amp;gt; H &amp;lt;/tex&amp;gt; высоту дерева, тогда время построения не превосходит&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt; \sum_{h = 1}^H  \limits\frac{n}{d^h} \cdot d &amp;lt;/tex&amp;gt; &amp;lt;tex dpi = &amp;quot;150&amp;quot;&amp;gt;  \cdot  h &amp;lt;/tex&amp;gt; &amp;lt;tex  dpi = &amp;quot;160&amp;quot;&amp;gt; = n \cdot d \cdot {\sum_{h = 1}^H  \limits}\frac{h}{d^h}. &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Докажем вспомогательную лемму о сумме ряда.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement= &amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt; {\sum_{h = 1}^\infty  \limits}\frac{h}{d^h} = \frac{d}{(d - 1)^2} . &amp;lt;/tex&amp;gt; &lt;br /&gt;
|proof=&lt;br /&gt;
 Обозначим за &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; сумму ряда. Заметим, что&lt;br /&gt;
&amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt; \frac{n}{d^n} = \frac{1}{d} \cdot \frac{n - 1}{d ^{n - 1}} + \frac{1}{d^n}. &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt;{\sum_{n = 1}^\infty  \limits}\frac{1}{d^n}&amp;lt;/tex&amp;gt; {{---}} это сумма бесконечной убывающей геометрической прогрессии, и она равна &amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt; &lt;br /&gt;
\frac{\frac{1}{d}}{1 - \frac{1}{d}} = \frac{1}{d - 1}. &amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Получаем  &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; &amp;lt;tex dpi = &amp;quot;160&amp;quot; &amp;gt;=\frac{1}{d}&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;\cdot s +&amp;lt;/tex&amp;gt;  &amp;lt;tex dpi = &amp;quot;160&amp;quot; &amp;gt; \frac{1}{d - 1}. &amp;lt;/tex&amp;gt; Откуда &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; &amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt;  = \frac{d}{(d - 1)^2}. &amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Подставляя в нашу формулу результат леммы, получаем &amp;lt;tex &amp;gt;n&amp;lt;/tex&amp;gt; &amp;lt;tex dpi = &amp;quot;160&amp;quot;&amp;gt;\cdot (\frac {d}{d - 1})^2 &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;  \leqslant  4 \cdot n &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;=O(n).&amp;lt;/tex&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' heapify():&lt;br /&gt;
     '''for''' i = a.heapSize / 2 '''downto''' 0&lt;br /&gt;
         siftDown(i)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Слияние двух куч===&lt;br /&gt;
Даны две кучи &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;, размерами &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;m&amp;lt;/tex&amp;gt;, требуется объединить эти две кучи. &lt;br /&gt;
====Наивная реализация====&lt;br /&gt;
Поочередно добавим все элементы из &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;. Время работы {{---}} &amp;lt;tex&amp;gt;O(m \log(n+m))&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' merge(a, b : '''Heap'''):&lt;br /&gt;
     '''while''' b.heapSize &amp;lt;tex&amp;gt;\neq&amp;lt;/tex&amp;gt; 0  &lt;br /&gt;
         a.insert(b.extractMin())&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
====Реализация с помощью построения кучи====&lt;br /&gt;
Добавим все элементы кучи &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt; в конец массива &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, после чего вызовем функцию построения кучи. Процедура выполняется за время &amp;lt;tex&amp;gt;O(n + m)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
 '''function''' merge(a, b : '''Heap'''):&lt;br /&gt;
     '''for''' i = 0 '''to''' b.heapSize - 1  &lt;br /&gt;
         a.heapSize = a.heapSize + 1&lt;br /&gt;
         a[a.heapSize - 1] = b[i]&lt;br /&gt;
     a.heapify()&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
===Поиск k-ого элемента===&lt;br /&gt;
Требуется найти &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;-ый по величине элемент в куче.&lt;br /&gt;
&lt;br /&gt;
# Создаем новую кучу, в которой будем хранить пару &amp;lt;tex&amp;gt;\langle \mathtt{value}, \mathtt{index} \rangle&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;\mathtt{value}&amp;lt;/tex&amp;gt; {{---}} значение элемента, а &amp;lt;tex&amp;gt;\mathtt{index}&amp;lt;/tex&amp;gt; {{---}} индекс элемента в основном массиве, и добавляем в нее корень кучи. &lt;br /&gt;
# Возьмем корень новой кучи и добавим её детей из основной кучи, после чего удалим корень. Проделаем этот шаг &amp;lt;tex&amp;gt;k - 1&amp;lt;/tex&amp;gt; раз.&lt;br /&gt;
# В корне новой кучи будет находиться ответ.&lt;br /&gt;
&lt;br /&gt;
Время работы алгоритма {{---}} &amp;lt;tex&amp;gt;O(k \log k)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При &amp;lt;tex&amp;gt;n \lessapprox k \log k &amp;lt;/tex&amp;gt; выгоднее запускать [[поиск k-ой порядковой статистики]].&lt;br /&gt;
[[Файл:Min_heap_kth.png‎|thumb|center|650px|Пример при &amp;lt;tex&amp;gt;k = 5&amp;lt;/tex&amp;gt;, красные {{---}} уже удаленные из кучи элементы, зеленые находятся в куче, а голубые {{---}} еще не рассмотрены.]]&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
* [[Биномиальная куча]]&lt;br /&gt;
* [[Фибоначчиева куча]]&lt;br /&gt;
* [[Левосторонняя куча]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
* [[wikipedia:ru:Двоичная куча|Википедия {{---}} Двоичная куча]]&lt;br /&gt;
* [[wikipedia:ru:Очередь с приоритетом|Википедия {{---}} Очередь с приоритетом]]&lt;br /&gt;
* [[wikipedia:en:Binary heap|Wikipedia {{---}} Binary heap]]&lt;br /&gt;
* [[wikipedia:en:Priority queue|Wikipedia {{---}} Priority queue]]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Приоритетные очереди]]&lt;br /&gt;
[[Категория: Структуры данных]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B5%D1%80%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D1%8B%D0%B5_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85&amp;diff=55528</id>
		<title>Персистентные структуры данных</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D0%B5%D1%80%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D1%8B%D0%B5_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D1%8B_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85&amp;diff=55528"/>
				<updated>2016-10-15T01:03:00Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Получение полностью персистентных структур данных */ опечатка&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition= '''Персистенные структуры данных''' (англ. ''persistent data structure'') — это структуры данных, которые  при внесении в них каких-то изменений сохраняют все свои предыдущие состояния и доступ к этим состояния.}}&lt;br /&gt;
&lt;br /&gt;
==Уровни персистентности==&lt;br /&gt;
Есть несколько уровней персистентности:&lt;br /&gt;
*частичная (англ. ''partial''),&lt;br /&gt;
*полная (англ. ''full''),&lt;br /&gt;
*конфлюэнтная (англ. ''confluent''),&lt;br /&gt;
*фунциональная (англ. ''functional'').&lt;br /&gt;
&lt;br /&gt;
В частично персистентных структурах данных к каждой версии можно делать запросы, но изменять можно только последнюю версию структуры данных.&lt;br /&gt;
&lt;br /&gt;
В полностью персистентных структурах данных можно менять не только последнюю, но и любую версию структур данных, также к любой версии можно делать запросы.&lt;br /&gt;
&lt;br /&gt;
Конфлюэнтные  структуры данных позволяют объединять две структуры данных  в одну (деревья поиска, которые можно сливать).&lt;br /&gt;
&lt;br /&gt;
Функциональные структуры данных полностью персистентны по определению, так как в них запрещаются уничтожающие присваивания, т.е. любой переменной значение может быть присвоено только один раз и изменять значения переменных нельзя.&lt;br /&gt;
Если структура данных функциональна, то она и конфлюэнтна, если  конфлюэнтна, то и полностью персистентна, если полностью персистентна, то и частично персистентна. Однако бывают структуры данных не функциональные, но конфлюэнтные.&lt;br /&gt;
&lt;br /&gt;
==Способы преобразования структур данных в персистентные==&lt;br /&gt;
Есть несколько способов сделать любую структуру персистентной: &lt;br /&gt;
*полное копирование (англ. ''full copy'') когда при любой операции изменения  полностью копируется структура данных и в получившуюся  новую копию вносятся изменения,&lt;br /&gt;
* копирование пути (англ. ''path copying''),&lt;br /&gt;
&lt;br /&gt;
*метод «толстых» узлов (англ. ''fat node'').&lt;br /&gt;
Рассмотрим для начала частичную персистентность. Для наглядности занумеруем разные версии структур данных. История изменений структуры данных линейна,  в любой момент времени можно обратиться к любой версии структуры данных, но поменять возможно только последнюю версию (на рисунке она выделена синим цветом).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Список версий.png]]&lt;br /&gt;
&lt;br /&gt;
Сформулируем, что такое структура данных. В нашем понимании структурой данных будет называться набор узлов, в которых хранятся какие-то данные, и эти узлы связаны ссылками. Пример структуры данных  — [[Дерево поиска, наивная реализация|дерево]]. Рассмотрим, как методом копирования пути превратить дерево в персистентное.&lt;br /&gt;
&lt;br /&gt;
===Метод копирование пути===&lt;br /&gt;
Пусть есть [[АВЛ-дерево |сбалансированное дерево поиска]]. Все операции в нем делаются за &amp;lt;tex&amp;gt;O(h)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;h&amp;lt;/tex&amp;gt; — высота дерева, а высота дерева &amp;lt;tex&amp;gt;O&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;(\log n)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; — количество вершин. Пусть необходимо сделать какое-то обновление в этом сбалансированном дереве, например, добавить очередной элемент, но при этом нужно не потерять старое дерево. Возьмем узел, в который нужно добавить нового ребенка. Вместо того чтобы добавлять нового ребенка, скопируем этот узел, к копии добавим нового ребенка, также скопируем все узлы вплоть до корня, из которых достижим первый скопированный узел вместе со всеми указателями. Все вершины, из которых измененный узел не достижим, мы не трогаем. Количество новых узлов всегда будет порядка логарифма.  В результате имеем доступ к обоим версиям дерева.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Копирование пути.png]] &lt;br /&gt;
&lt;br /&gt;
Так как рассматривается сбалансированное дерево поиска, то поднимая вершину вверх при балансировке, нужно делать копии всех вершин, участвующих во вращениях, у которых изменились ссылки на детей. Таких всегда не более трех, поэтому ассимптотика &amp;lt;tex&amp;gt;O&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;( \log n)&amp;lt;/tex&amp;gt; не пострадает. Когда балансировка закончится, нужно дойти вверх до корня, делая копии вершин на пути. &lt;br /&gt;
&lt;br /&gt;
Этот метод хорошо работает на [[Стек|стеке]], двоичных ([[Декартово дерево |декартовых]], [[Красно- черное дерево | красно-черных]]) деревьях. Но в случае преобразования [[Очередь| очереди]] в персистентную операция добавления будет очень дорогой, так как элемент добавляется в хвост очереди, который достижим из всех остальных элементов. Также не выгодно применять этот метод и в случае, когда в структуре данных имеются ссылки на родителя.&lt;br /&gt;
&lt;br /&gt;
==== Реализация на основе дерева отрезков ====&lt;br /&gt;
&lt;br /&gt;
На основе дерева отрезков можно построить полностью персистентную структуру данных.&lt;br /&gt;
&lt;br /&gt;
Для реализации персистентного дерева отрезков удобно несколько изменить структуру дерева. Для этого будем использовать явные указатели &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;R&amp;lt;/tex&amp;gt; для дочерних элементов. Кроме того, заведем массив &amp;lt;tex&amp;gt;roots[]&amp;lt;/tex&amp;gt;, в котором &amp;lt;tex&amp;gt;roots[i]&amp;lt;/tex&amp;gt; указывает на корень дерева отрезков версии &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для построения персистентного дерева отрезков из &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; элементов необходимо применить &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; раз операцию добавления элемента к последней версии дерева. Для того, чтобы добавить новый элемент к &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;-ой версии дерева, необходимо проверить, является ли оно полным бинарным. Если да, то создадим новый корень, левым сыном сделаем &amp;lt;tex&amp;gt;roots[k]&amp;lt;/tex&amp;gt;. Иначе, сделаем копию корня исходной версии. Добавим корень в конец массива корней. Далее, спускаясь от корня к первому свободному листу, будем создавать несуществующие узлы и клонировать существующие. После этого в новой ветке необходимо обновить значение функции и некоторые указатели дочерних элементов. Поэтому, возвращаясь из рекурсии, будем менять один указатель на только что созданную или скопированную вершину, а также обновим значение функции, для которой строилось дерево. После этой операции в дереве появится новая версия, содержащая вставленный элемент.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы изменить элемент в персистентном дереве отрезков, необходимо сделать следующие действия: спустимся в дереве от корня нужной версии  до требуемого элемента, скопируем его, изменим значение, и, поднимаясь по дереву, будем клонировать узлы. При этом необходимо менять указатель на одного из детей на узел, созданный при предыдущем клонировании. После копирования корня, добавим новый корень в конец массива корней.&lt;br /&gt;
&lt;br /&gt;
[[Файл:persist.png]]&lt;br /&gt;
&lt;br /&gt;
Здесь изображено персистентное дерево отрезков с операцией минимум, в котором изначально было 3 вершины. Сперва к нему была добавлена вершина со значением 2, а потом изменена вершина со значением 7. Цвет ребер и вершин соответствует времени их появления. Синий цвет элементов означает, что они были изначально, зеленый - что они появились после добавления, а оранжевый - что они появились после изменения элемента.&lt;br /&gt;
&lt;br /&gt;
===Метод «толстых» узлов===&lt;br /&gt;
Пусть в структуре данных есть узел, в котором нужно сделать изменения (например, на рисунке ниже в первой версии структуры данных в узле &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; есть поле &amp;lt;tex&amp;gt;a=3&amp;lt;/tex&amp;gt;, а во второй версии это поле должно быть равно &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt;), но при этом нужно сохранить доступ к старой версии узла &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и не нужно экономить время. В таком случае можно хранить обе версии узла &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; в большом комбинированном узле. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Метод толстых узлов.png|600px|центр]] ‎&lt;br /&gt;
&lt;br /&gt;
В примере выше в этом «толстом» узле будет храниться первая версия &amp;lt;tex&amp;gt;V_1&amp;lt;/tex&amp;gt;,  у которой &amp;lt;tex&amp;gt;a=3&amp;lt;/tex&amp;gt; и вторая версия &amp;lt;tex&amp;gt;V_2&amp;lt;/tex&amp;gt;, у которой &amp;lt;tex&amp;gt;a=4&amp;lt;/tex&amp;gt;. Если далее последуют еще какие-то изменения (например, поле &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;  узла &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; станет равно &amp;lt;tex&amp;gt;5&amp;lt;/tex&amp;gt;) добавим в толстый узел &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;   еще одну версию — &amp;lt;tex&amp;gt;V_3&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Список версий1.png|500px|центр]]&lt;br /&gt;
&lt;br /&gt;
Пусть нужно сделать запрос ко второй версии структуры данных (на рисунке выше это запрос &amp;lt;tex&amp;gt;X.a-?)&amp;lt;/tex&amp;gt;. Чтобы сделать этот запрос, нужно зайти в узел &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и найти в списке версий максимальную версию, которая меньше или равна версии запроса (в примере на рисунке это версия &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt;), и в этой версии узла найти значение поля &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; (в примере &amp;lt;tex&amp;gt;a=4&amp;lt;/tex&amp;gt;).&lt;br /&gt;
Чтобы быстро найти нужную версию в списке версий, хранящихся в «толстом» узле, нужно хранить их в виде дерева. Тогда мы сможем за логарифм найти нужную версию и к ней обратиться. Значит, все операции, которые будут производиться на этой структуре данных, будут домножаться на логарифм от числа версий.&lt;br /&gt;
&lt;br /&gt;
Структура толстого узла может быть и другой: к каждой вершине можно хранить лог ее изменений, в который записывается версия, в которой произошло изменение, а также само изменение. Такая структура толстого узла рассмотрена ниже, в разделах об общих методах получения частично и полностью персистентных структур данных. Лог может быть организован по-разному. Обычно делают отдельный лог для каждого поля вершины. Когда что-то меняется в вершине, то в лог соответствующего поля записывается это изменение и номер версии, с которой данное изменение произошло. Когда нужно обратиться к старой версии, то двоичным поиском ищут в логе последнее изменение до этой версии и  находят  нужное значение. &lt;br /&gt;
Метод ''fat node'' дает замедление &amp;lt;tex&amp;gt; \log t&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;t&amp;lt;/tex&amp;gt; — число изменений структуры данных; памяти требуется  &amp;lt;tex&amp;gt;n+t&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; — число вершин в структуре данных.&lt;br /&gt;
&lt;br /&gt;
==Преобразование  списка в персистентный  за O(1)==&lt;br /&gt;
Если скомбинировать методы ''path copiyng'' и ''fat node'', то получим универсальный метод, который позволит преобразовывать структуры данных в частично персистентные без дополнительного логарифма памяти и времени.&lt;br /&gt;
Пусть мы имеем [[Список| двусвязный список]] и хотим внести в него какое-то изменение, например, добавить узел &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt; между узлами &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt;, то есть при переходе из версии &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; в версию &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; добавим в двусвязный список узел &amp;lt;tex&amp;gt;Z&amp;lt;/tex&amp;gt;. &lt;br /&gt;
Применим метод «толстых» узлов. Для этого в узлы  &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;  и &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt; добавим вторую версию и изменим ссылку, следующую из &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;, и предыдущую перед &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt;, как показано на рисунке.&lt;br /&gt;
Этот алгоритм работает следующим образом. Например, текущая  первая версия. Идем по списку слева направо от первого узла к узлу &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;, а затем нужно перейти к следующему узлу. В «толстом» узле &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; выбираем нужную (первую) версию и далее следуем по ссылкам.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Список1.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Пусть мы хотим добавить еще один элемент между узлами &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt;, но проблема в том, что у &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt; уже есть вторая версия, если будем добавлять еще новые версии, то получим дополнительный логарифм времени при обращении к узлу, как в рассмотренном выше методе &amp;quot;толстых&amp;quot; узлов. Поэтому более двух версий добавлять не будем. Используем метод копирования пути.  Скопируем узлы &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt;, начиная с их третьей версии, и свяжем новые узлы с исходным списком.  Для этого добавим вторые версии предыдущему перед &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и последующему после &amp;lt;tex&amp;gt;Y&amp;lt;/tex&amp;gt; узлам и свяжем эти узлы соответствующими ссылками. Так все версии остаются доступными.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Список2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
Будем называть  узел полным, если у него есть вторая версия. Если мы хотим вставить новый элемент в середину списка (на рисунке ниже он обозначен зеленым цветом), мы должны склонировать все полные узлы слева и справа от места добавления нового узла, дойти до ближайших элементов, у которых нет второй версии и добавить им вторую версию. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Аморанализ.png|700px]]&lt;br /&gt;
&lt;br /&gt;
Оценим амортизационное время работы такого алгоритма. У нас частично персистентная структура данных, изменять можно только ее последнюю версию. Примем функцию потенциала &amp;lt;tex&amp;gt;\Phi&amp;lt;/tex&amp;gt; равной числу полных узлов в последней версии.&lt;br /&gt;
# Амортизационная стоимость операции добавления:&lt;br /&gt;
#* &amp;lt;tex&amp;gt;a_{empty} = t + \Delta\Phi = O(1) + 2 = O(1), &amp;lt;/tex&amp;gt; так как если добавление узла не задевает полных узлов, то узел добавляется за константное время, а количество полных узлов увеличивается на &amp;lt;tex&amp;gt; 2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
#* &amp;lt;tex&amp;gt;a_{fat} = t + \Delta\Phi = O(1) + k - k + 2 = O(1), &amp;lt;/tex&amp;gt; так как если узел влечёт изменения полных узлов, то сначала потратится &amp;lt;tex&amp;gt; k &amp;lt;/tex&amp;gt; времени на копирование этих полных узлов, и в то же время потенциал уменьшится на &amp;lt;tex&amp;gt; k &amp;lt;/tex&amp;gt;, а потом увеличится максимум на &amp;lt;tex&amp;gt; 2 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
# Для любого &amp;lt;tex&amp;gt;i: \Phi_i = O(n),&amp;lt;/tex&amp;gt; так как полных узлов не больше общего количества узлов в списке.&lt;br /&gt;
&lt;br /&gt;
Таким образом, [[Амортизационный анализ#Метод потенциалов|по теореме о методе потенциалов]], амортизационное время работы по добавлению элемента будет &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Общий метод построения частично персистентных структур данных==&lt;br /&gt;
[[Файл:Частичная персистентность.png|мини|справа|500x300px| Пунктирные линии —  обратные ссылки,&amp;lt;br&amp;gt;  &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; —  исходный узел, актуальный до версии &amp;lt;tex&amp;gt;10&amp;lt;/tex&amp;gt;,&amp;lt;br&amp;gt;  &lt;br /&gt;
&amp;lt;tex&amp;gt;X'&amp;lt;/tex&amp;gt; —  склонированный узел, актуальный с версии &amp;lt;tex&amp;gt;11&amp;lt;/tex&amp;gt;, с пустым списком изменений]]&lt;br /&gt;
Применим методы, описанные выше, в общем случае для абстрактной структуры данных. &lt;br /&gt;
&lt;br /&gt;
Пусть есть структура данных, у каждого узла которой количество указателей на этот узел не больше некоторой константы &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;. При клонировании узла важно знать, откуда на этот узел идут указатели, чтобы затем их переставить. Поэтому необходимо в каждом узле хранить обратные ссылки на те узлы, которые ссылаются на клонируемый узел.&lt;br /&gt;
Все узлы будут храниться в виде «толстых» узлов, в которых содержится начальная версия этого узла и список внесенных в него изменений (англ. ''change log'') длиной не больше &amp;lt;tex&amp;gt;2P&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть нужно внести изменение в структуру данных в узел &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;. Если есть место в списке изменений, просто вносим туда изменение: записываем номер версии, с которой начинается это изменение, в какое поле узла вносится изменение и какое именно.  Если  ''change log'' заполнен, то клонируем узел &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;: берем стартовую версию узла, производим в ней все изменения, записанные в ''change log'', добавляем последнее изменение и делаем версию со свободным списком изменений.  Затем пройдем по обратным ссылкам от &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; и в ''change log'' каждого узла, ссылающегося на &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;, добавим изменение указателя начиная с этой версии структуры данных с &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;X'&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Оценим время работы этого алгоритма. Введем функцию потенциала, которая будет равна суммарному размеру всех списков изменений в последней версии. Посмотрим, как меняется суммарный размер списков изменений, когда мы совершаем одно изменение. Если ''change log'' был не полный, то мы просто добавляем туда один элемент, потенциал увеличится на единицу.&lt;br /&gt;
Если ''change log'' был полный, то потенциал уменьшается на его размер, так как мы склонировали узел с пустым списком изменений. После этого мы пошли по обратным ссылкам (их было &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; штук) и добавили в &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; узлов по одному значению. Таким образом амортизированное время работы будет &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Получение полностью персистентных структур данных==&lt;br /&gt;
Для полностью персистентных структур данных применить описанный выше метод преобразования не получится, так как история создания версий не линейна и нельзя отсортировать изменения по версиям, как в частично персистентных структурах данных. &lt;br /&gt;
Пусть мы храним историю  изменения версий в виде дерева. Сделаем[[Обход в глубину, цвета вершин| обход этого дерева в глубину]]. В порядке этого обхода запишем, когда мы входим, а когда выходим из каждой версии. Эта последовательность действий полностью задает дерево, сделаем из нее список.  Когда после какой-то версии (на рисунке ниже это версия &amp;lt;tex&amp;gt;6&amp;lt;/tex&amp;gt;) добавляется новая версия структуры данных (на рисунке версия &amp;lt;tex&amp;gt;8&amp;lt;/tex&amp;gt;), мы вставляем два элемента в список (на рисунке  это &amp;lt;tex&amp;gt;+8&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;-8&amp;lt;/tex&amp;gt;) после входа, но до выхода из той версии, когда произошло изменение (то есть между &lt;br /&gt;
элементами &amp;lt;tex&amp;gt;+6&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;-6&amp;lt;/tex&amp;gt;). Первый элемент вносит изменение, а второй будет возвращать обратно значение предыдущей версии. Таким образом, каждая операция разбивается на две: первая делает изменение,  а вторая его откатывает.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Полная персистентность.png‎]] &lt;br /&gt;
&lt;br /&gt;
Для реализации описанного в предыдущем пункте метода преобразования структур данных в полностью персистентные нам нужен такой список, который поддерживает операции &amp;lt;tex&amp;gt;\mathrm{insert  After(p,q})&amp;lt;/tex&amp;gt; (вставить &amp;lt;tex&amp;gt;q&amp;lt;/tex&amp;gt; после  &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt;) и &amp;lt;tex&amp;gt;\mathrm{order(p,q)}&amp;lt;/tex&amp;gt; (должен уметь отвечать на запросы вида &amp;quot;&amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; лежит в этом списке до &amp;lt;tex&amp;gt;q&amp;lt;/tex&amp;gt;&amp;quot;). &amp;lt;tex&amp;gt;\mathrm{order(p,q)}&amp;lt;/tex&amp;gt; возвращает &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, если &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; лежит до &amp;lt;tex&amp;gt;q&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt; иначе.   Это список с поддержкой запроса о порядке [[List order maintenance|''List Order Maintenance'']], который обе эти операции делает за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В ''change log'' «толстого» узла теперь будем добавлять два события: одно указывает на изменение, произошедшее в соответствующей версии, а другое на его отмену. События будут добавляться в ''change log'' не по номерам версий, а по их порядку в списке версий  ''List Order Maintenance''. &lt;br /&gt;
&lt;br /&gt;
Когда есть запрос к какой-то версии, то нужно найти в списке версий такую, после входа в которую, но до выхода из которой лежит версия запроса, а среди таких максимальную. Например, если приходит запрос к версии &amp;lt;tex&amp;gt;6&amp;lt;/tex&amp;gt; на рисунке выше, то мы видим, что она в списке версий лежит после входа, но до выхода в версии &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt;. Необходимо найти наибольшую из них. Список ''List Order Maintenance'' позволяет делать это за  &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; с помощью операции &amp;lt;tex&amp;gt;\mathrm{order(p,q)}&amp;lt;/tex&amp;gt;. В примере это версия &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt;. Так как ''change log'' каждого узла имеет константный размер, то поиск нужной версии в нем происходит за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
В какой-то момент ''change log'' «толстого» узла переполнится. Тогда нужно клонировать этот узел и нижнюю половину изменений перенести в ''change log'' склонированного узла.  Первую половину изменений применяем к исходной версии узла и сохраняем в качестве исходной в склонированном узле. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Полностью персистентные сд.png|900x700px]]&lt;br /&gt;
&lt;br /&gt;
Получится два узла: первый отвечает за отрезок версий до операции последнего изменения, а второй {{---}} после нее. Дальнейший порядок действий аналогичен тому, который использовался в общем методе построения частично персистентных структур данных.&lt;br /&gt;
&lt;br /&gt;
Оценим амортизационное время работы этого алгоритма. Введем функцию потенциала, равную числу полных узлов. Когда узел раздваивается, функция потенциала уменьшается на единицу, затем мы переставляем &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt; ссылок, потенциал увеличивается на  &amp;lt;tex&amp;gt;P&amp;lt;/tex&amp;gt;, значит, амортизационное время работы — &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Использование персистентных структур данных для решения геометрических задач==&lt;br /&gt;
&lt;br /&gt;
Персистентные структуры данных используются при решении геометрических задач. Примером может служить [[Локализация в ППЛГ методом полос (персистентные деревья)|Point location problem]] — задача о местоположении точки. Задачи такого рода решаются в ''offline'' и ''online''. В ''offline''-задачах все запросы даны заранее и можно обрабатывать их одновременно. В ''online''-задачах следующий запрос можно узнать только после того, как найден ответ на предыдущий.&lt;br /&gt;
&lt;br /&gt;
При решении ''offline''-задачи данный планарный граф разбивается на полосы вертикальными прямыми, проходящими через вершины многоугольников. Затем в этих полосах при помощи техники заметающей прямой ''(scane line)'' ищется местоположение точки-запроса. При переходе из одной полосы в другую изменяется [[Дерево поиска, наивная реализация|сбалансированное дерево поиска]]. Если использовать частично персистентную структуру данных, то для каждой полосы  будет своя версия дерева и сохранится возможность делать к ней запросы. Тогда ''Point location problem'' может быть решена в ''online''.&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
* [[Персистентный стек]]&lt;br /&gt;
* [[Персистентная очередь]]&lt;br /&gt;
* [[Персистентный дек]]&lt;br /&gt;
* [[List order maintance]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
*[https://www.lektorium.tv/lecture/14321/ Дополнительные главы алгоритмов. Лекции Андрея Станкевича]&lt;br /&gt;
*[http://logic.pdmi.ras.ru/csclub/node/2734/ Персистентные структуры данных. Лекции Павла Маврина]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Персистентные структуры данных]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55475</id>
		<title>Задача о рюкзаке</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55475"/>
				<updated>2016-10-08T03:32:22Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Задача о суммах подмножеств */ опечатка&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Задача о рюкзаке'''(''англ. Knapsack problem'') — дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет имеет массу &amp;lt;tex&amp;gt; w_i &amp;gt; 0&amp;lt;/tex&amp;gt; и стоимость &amp;lt;tex&amp;gt; p_i &amp;gt; 0&amp;lt;/tex&amp;gt;. Необходимо выбрать из этих предметов такой набор, чтобы суммарная масса не превосходила заданной величины &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; (вместимость рюкзака), а суммарная стоимость была максимальна.&lt;br /&gt;
&lt;br /&gt;
== Формулировка задачи ==&lt;br /&gt;
&lt;br /&gt;
Дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; - вместимость рюкзака, &amp;lt;tex&amp;gt;w=\{w_{1},w_{2},...,w_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых весов, &amp;lt;tex&amp;gt;p=\{p_{1},p_{2},...,p_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых стоимостей. Нужно найти набор бинарных величин &amp;lt;tex&amp;gt;B=\{b_{1},b_{2},...,b_{N}\}&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;b_{i} = 1 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; включен в набор, &amp;lt;tex&amp;gt; b_{i} = 0 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не включен, и такой что:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} w_{1}+ ... + b_{N} w_{N} \le W&amp;lt;/tex&amp;gt;&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} p_{1}+ ... + b_{N} p_{N} &amp;lt;/tex&amp;gt; максимальна.&lt;br /&gt;
&lt;br /&gt;
== Варианты решения ==&lt;br /&gt;
&lt;br /&gt;
Задачу о рюкзаке можно решить несколькими способами:&lt;br /&gt;
&lt;br /&gt;
* Перебирать все подмножества набора из N предметов. Сложность такого решения &amp;lt;tex&amp;gt;O({2^{N}})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Методом [[Meet-in-the-middle|Meet-in-the-middle]]. Сложность решения &amp;lt;tex&amp;gt; O({2^{N/2}}\times{N}) &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Метод динамического программирования. Сложность - &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Метод динамического программирования ==&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; есть максимальная стоимости предметов, которые можно уложить в рюкзак вместимости &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, если можно использовать только первые &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; предметов, то есть &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_k\}&amp;lt;/tex&amp;gt;, назовем этот набор допустимых предметов для &amp;lt;tex&amp;gt;A(k,s)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(k, 0) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(0, s) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Найдем &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt;. Возможны 2 варианта:&lt;br /&gt;
&lt;br /&gt;
#Если предмет &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; не попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k,s) = A(k-1, s)&amp;lt;/tex&amp;gt;&lt;br /&gt;
# Если &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака, где вес &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; уменьшаем на вес &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; -ого предмета и набор допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k-1, s-w_k) + p_k&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
То есть: &lt;br /&gt;
&amp;lt;tex&amp;gt;A(k,s) = max(A(k-1,s), A(k-1,s-w_{k}) + p_{k})&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоимость искомого набора равна &amp;lt;tex&amp;gt;A(N,W)&amp;lt;/tex&amp;gt;, так как нужно найти максимальную стоимость рюкзака, где все предметы допустимы и вместимость рюкзака &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановим набор предметов, входящих в рюкзак'''&lt;br /&gt;
&lt;br /&gt;
Будем определять, входит ли &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет в искомый набор. Начинаем с элемента &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;i = N&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;w = W&amp;lt;/tex&amp;gt;. Для этого сравниваем &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt; со следующими значениями:&lt;br /&gt;
&lt;br /&gt;
#Максимальная стоимость рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;&lt;br /&gt;
#Максимальная стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt; меньше и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;p_i&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w-w_i)+p_i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Заметим, что при построении &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; мы выбирали максимум из этих значений и записывали в &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt;. Тогда будем сравнивать &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt; c &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;, если равны, тогда &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не входит в искомый набор, иначе входит.&lt;br /&gt;
&lt;br /&gt;
== Реализация ==&lt;br /&gt;
Сначала генерируем &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
 for i = 0..W&lt;br /&gt;
   A[0][i] = 0;&lt;br /&gt;
 for i = 0..N&lt;br /&gt;
   A[i][0] = 0;   //Первые элементы приравниваем к 0&lt;br /&gt;
 for k = 1..N               &lt;br /&gt;
   for s = 1..W   //Перебираем для каждого k все вместимости &lt;br /&gt;
     if s &amp;gt;= w[k]    //Если текущий предмет вмещается в рюкзак&lt;br /&gt;
       A[k][s] = max(A[k-1][s], A[k-1][s-w[k]]+p[k]); //выбираем класть его или нет&lt;br /&gt;
     else &lt;br /&gt;
       A[k][s] = A[k-1][s];             //иначе, не кладем&lt;br /&gt;
&lt;br /&gt;
Затем найдем набор &amp;lt;tex&amp;gt;ans&amp;lt;/tex&amp;gt; предметов, входящих в рюкзак, рекурсивной функцией:&lt;br /&gt;
&lt;br /&gt;
 findAns(k, s)&lt;br /&gt;
   if A[k][s] == 0 &lt;br /&gt;
     return;&lt;br /&gt;
   if A[k-1][s] == A[k][s]&lt;br /&gt;
     findAns(k-1, s);&lt;br /&gt;
   else &lt;br /&gt;
     findAns(k-1, s - w[k]);&lt;br /&gt;
     ans.push(k);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Пример ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;W = 13, N = 5&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{1} = 3, p_{1} = 1 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{2} = 4, p_{2} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{3} = 5, p_{3} = 4 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{4} = 8, p_{4} = 7 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{5} = 9, p_{5} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Knapsack problem0.png]]&lt;br /&gt;
&lt;br /&gt;
Числа от 0 до 13 в первой строчке обозначают вместимость рюкзака.&lt;br /&gt;
&lt;br /&gt;
В первой строке как только вместимость рюкзака &amp;lt;tex&amp;gt;n \ge 3&amp;lt;/tex&amp;gt;, добавляем в рюкзак 1 предмет.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим &amp;lt;tex&amp;gt;k = 3&amp;lt;/tex&amp;gt;, при каждом &amp;lt;tex&amp;gt;s \ge 5 (&amp;lt;/tex&amp;gt;так как &amp;lt;tex&amp;gt;w_3 = 5)&amp;lt;/tex&amp;gt; сравниваем &amp;lt;tex&amp;gt;A[k-1][s]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;A[k-1][s-w_3]+p_3&amp;lt;/tex&amp;gt; и записываем в &amp;lt;tex&amp;gt;A[k][s]&amp;lt;/tex&amp;gt; стоимость либо рюкзака без третьего предмета, но с таким же весом, либо с третьим предметом, тогда стоимость равна стоимости третьего предмета плюс стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_3&amp;lt;/tex&amp;gt; меньше.     &lt;br /&gt;
&lt;br /&gt;
Максимальная стоимость рюкзака находится в &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановление набора предметов, из которых состоит максимально дорогой рюкзак.'''&lt;br /&gt;
&lt;br /&gt;
Начиная с &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt; восстанавливаем ответ.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Задача о рюкзаке3.png]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, в набор входит &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt; предмет.&lt;br /&gt;
&lt;br /&gt;
Стоимость рюкзака &amp;lt;tex&amp;gt;= 6 + 7 = 13&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вес рюкзака &amp;lt;tex&amp;gt;= 4 + 8 = 12&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Другие задачи семейства=&lt;br /&gt;
==Ограниченный рюкзак ==&lt;br /&gt;
'''Ограниченный рюкзак''' (англ. ''Bounded Knapsack Problem'') - обобщение классической задачи, когда любой предмет может быть взят некоторое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит склад. Он может унести ограниченный вес, каждый товар на складе содержится в определенном ограниченном количестве. Нужно унести предметов на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран ограниченное &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; число раз.&lt;br /&gt;
Задача выбрать число &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
* максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
* выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \in (0,1,...,b_i)&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
При небольших &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; решается сведением к классической задаче о рюкзаке. В иных случаях:&lt;br /&gt;
* Методом ветвей и границ.&lt;br /&gt;
* Методом динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого возможного числа предметов типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 1 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;d(i,c) = max(d(i - 1, c - lw_i) + lp_i) &amp;lt;/tex&amp;gt; по всем целым &amp;lt;tex&amp;gt; l &amp;lt;/tex&amp;gt; из промежутка &amp;lt;tex&amp;gt; 0 \le l \le min(b_i,\lfloor c/w_i \rfloor)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного.&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 for i = 0..W // база&lt;br /&gt;
   d[0][i] = 0;&lt;br /&gt;
 for i = 1..N             &lt;br /&gt;
   for c = 1..W   //Перебираем для каждого i, все вместимости &lt;br /&gt;
     d[i][c] = d[i - 1][c];&lt;br /&gt;
     for l = min(b[i], c / w[i]) .. 1 // ищем l для которого выполняется максимум&lt;br /&gt;
         d[i][c] =  max(d[i][c], d[i - 1][c - l * w[i]] + p[i] * l);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Неограниченный рюкзак==&lt;br /&gt;
'''Неограниченный рюкзак''' (англ.''Unbounded Knapsack Problem'') - обобщение ограниченного рюкзака, в котором любой предмет может быть выбран любое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Перекупщик закупается на оптовой базе. Он может увезти ограниченное количество товара, количество товара каждого типа на базе не ограниченно. Нужно увезти товар на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого количества вещей типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; включительно.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i, c - w_i) + p_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = max(d(c), d(c - w_i) + p_i)   &amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Непрерывный рюкзак==&lt;br /&gt;
'''Непрерывный рюкзак''' (англ. ''Continuous knapsack problem'') - вариант задачи, в котором возможно брать любою дробную часть от предмета, при этом удельная стоимость сохраняется.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит мясника. Суммарно он может унести ограниченный вес товаров. Вор может резать товары без ущерба к удельной стоимости. Нужно унести товара на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Задача выбрать часть &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; каждого предмета так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; 0 \le x_i \le 1&amp;lt;/tex&amp;gt; дробное, для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Возможность брать любую часть от предмета сильно упрощает задачу. Жадный алгоритм дает оптимальное решение в данном случае.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 sort(); // сортируем в порядке убывания удельной стоимости.&lt;br /&gt;
 for i = 1..N // идем по предметам            &lt;br /&gt;
       if W &amp;gt;= w[i]    //если помещается - берем&lt;br /&gt;
        sum += p[i];&lt;br /&gt;
        W -= w[i];&lt;br /&gt;
    else&lt;br /&gt;
        sum += W / w[i] * p[i];// иначе берем сколько можно и выходим&lt;br /&gt;
        break;&lt;br /&gt;
&lt;br /&gt;
==Задача о суммах подмножеств==&lt;br /&gt;
'''Задача о суммах подмножеств''' (англ. ''Subset-sum problem, Value Independent Knapsack Problem'') - задача из семейства, в которой стоимость предмета совпадает с его весом.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Машина может увезти определенное количество груза. Нужно увезти как можно больше крупного неделимого мусора за раз.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Нужно выбрать подмножество так, чтобы сумма ближе всего к &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, но не превысила его. Формально, нужно найти набор бинарных величин &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt;, так чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_j = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен рюкзаку, иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Для решения пригодны любые методы применяемые для классической задачи, однако специализированые алгоритмы обычно более оптимальны по параметрам. Используются:&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
* Гибридный метод на основе динамического программирования и поиска по дереву. В худшем случае, работает за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная сумма  &amp;lt;tex&amp;gt;\le c&amp;lt;/tex&amp;gt;, подмножества взятого из &amp;lt;tex&amp;gt; 1, ...,\ i&amp;lt;/tex&amp;gt; элементов.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i - 1, c - w_i) + w_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная сумма подмножества, не превышающая заданное значение.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача о размене==&lt;br /&gt;
'''Задача о размене''' (англ. ''Change-Making problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; неисчерпаемых типов предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;.  Нужно наполнить рюкзак предметами с суммарным весом &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Часто задачу ставят как, дать сдачу наименьшим количеством монет.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество взятых предметов: &amp;lt;tex&amp;gt;\sum_{i=1}^N x_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*сумма весов выбранных предметов равнялась вместимости рюкзака: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i = W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; минимальное число предметов, типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, необходимое, чтобы заполнить рюкзак вместимостью &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(0,0) = 0&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;d(0,c) = \inf&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt;c &amp;gt; 0&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 min(d(i - 1, c), d(i, c - w_i) + 1) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = min(d(c), d(c - w_i) + 1) \qquad  for\ c = w_i, ..., W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача об упаковке==&lt;br /&gt;
'''Задача об упаковке''' (англ. ''Bin Packing Problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; рюкзаков вместимости &amp;lt;tex&amp;gt; W &amp;lt;/tex&amp;gt; и столько же предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;. Нужно распределить все предметы, задействовав минимальное количество рюкзаков.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Нужно вывезти из шахты все куски руды, используя наименьшее число вагонеток.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Математически задачу можно представить так:&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество рюкзаков: &amp;lt;tex&amp;gt;\sum_{i=1}^N y_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*так чтобы выполнялось условие на совместность: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_{ij} \le Wy_j \qquad j \in {1, ..., N}&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; y_i = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; i&amp;lt;/tex&amp;gt; рюкзак используется. Иначе &amp;lt;tex&amp;gt; y_i = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Обычно применяют аппроксимационные алгоритмы, либо используют метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Мультипликативный рюкзак==&lt;br /&gt;
'''Мультипликативный рюкзак''' (англ. ''Multiple Knapsack Problem'') - есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;. Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' У транспортной компании есть парк машин разной грузоподъемности. Нужно перевезти товара на максимальную сумму с одного склада на другой единовременно.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Максимизировать &amp;lt;tex&amp;gt;\sum_{i=1}^M \sum_{j=1}^{N} p_jx_{ij}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так, чтобы &amp;lt;tex&amp;gt;\sum_{i=1}^N w_jx_{ij} \le W_i&amp;lt;/tex&amp;gt; выполнялось для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\sum_{j=1}^{M}x_{ij}=1&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования, для задач данного типа нецелесообразно. Используются вариации метода ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Задача о назначении==&lt;br /&gt;
'''Задача о назначении''' (англ. ''Generalized Assignment Problem'') - Наиболее общая задача семейства. Отличается от мультипликативного рюкзака тем, что каждый предмет имеет различные характеристики в зависимости от рюкзака, куда его помещают. Есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;, у &amp;lt;tex&amp;gt; j &amp;lt;/tex&amp;gt; предмета &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; стоимость и вес, при помещении его в &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; рюкзак, равны &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; w_{ij} &amp;lt;/tex&amp;gt;  соответственно.  Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
Весьма важная задача, так как она моделирует оптимальное распределение различных задач между вычислительными блоками.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
&lt;br /&gt;
Максимизировать стоимость выбранных предметов &amp;lt;tex&amp;gt;\sum_{i=1}^M\sum_{j=1}^N p_{ij} x_{ij}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
при выполнении условия совместности &amp;lt;tex&amp;gt;\sum_{j=1}^N w_{ij} x_{ij} \le W_i \qquad i=1, \ldots, M&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; \sum_{i=1}^M x_{ij} \leq 1 \qquad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} \in \{0,1\} \qquad i=1, \ldots, N, \quad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Наиболее используем метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*[http://informatics.mccme.ru/moodle/mod/book/view.php?id=815&amp;amp;chapterid=60 Дистанционная подготовка по информатике]&lt;br /&gt;
*[http://rosettacode.org/wiki/Knapsack_Problem Код для нескольких задач семейства на всевозможных языках]&lt;br /&gt;
*[http://www.diku.dk/users/pisinger/95-1.pdf David Pisinger Knapsack problems. — 1995]&lt;br /&gt;
*Silvano Martello, Paolo Toth. Knapsack Problems: Algorithms and Computer Implementations — 1990 г. — ISBN 0-471-92420-2&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55474</id>
		<title>Задача о рюкзаке</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55474"/>
				<updated>2016-10-08T03:28:58Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: Отмена правки 55473 участника 37.57.17.214 (обсуждение)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Задача о рюкзаке'''(''англ. Knapsack problem'') — дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет имеет массу &amp;lt;tex&amp;gt; w_i &amp;gt; 0&amp;lt;/tex&amp;gt; и стоимость &amp;lt;tex&amp;gt; p_i &amp;gt; 0&amp;lt;/tex&amp;gt;. Необходимо выбрать из этих предметов такой набор, чтобы суммарная масса не превосходила заданной величины &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; (вместимость рюкзака), а суммарная стоимость была максимальна.&lt;br /&gt;
&lt;br /&gt;
== Формулировка задачи ==&lt;br /&gt;
&lt;br /&gt;
Дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; - вместимость рюкзака, &amp;lt;tex&amp;gt;w=\{w_{1},w_{2},...,w_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых весов, &amp;lt;tex&amp;gt;p=\{p_{1},p_{2},...,p_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых стоимостей. Нужно найти набор бинарных величин &amp;lt;tex&amp;gt;B=\{b_{1},b_{2},...,b_{N}\}&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;b_{i} = 1 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; включен в набор, &amp;lt;tex&amp;gt; b_{i} = 0 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не включен, и такой что:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} w_{1}+ ... + b_{N} w_{N} \le W&amp;lt;/tex&amp;gt;&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} p_{1}+ ... + b_{N} p_{N} &amp;lt;/tex&amp;gt; максимальна.&lt;br /&gt;
&lt;br /&gt;
== Варианты решения ==&lt;br /&gt;
&lt;br /&gt;
Задачу о рюкзаке можно решить несколькими способами:&lt;br /&gt;
&lt;br /&gt;
* Перебирать все подмножества набора из N предметов. Сложность такого решения &amp;lt;tex&amp;gt;O({2^{N}})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Методом [[Meet-in-the-middle|Meet-in-the-middle]]. Сложность решения &amp;lt;tex&amp;gt; O({2^{N/2}}\times{N}) &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Метод динамического программирования. Сложность - &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Метод динамического программирования ==&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; есть максимальная стоимости предметов, которые можно уложить в рюкзак вместимости &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, если можно использовать только первые &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; предметов, то есть &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_k\}&amp;lt;/tex&amp;gt;, назовем этот набор допустимых предметов для &amp;lt;tex&amp;gt;A(k,s)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(k, 0) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(0, s) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Найдем &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt;. Возможны 2 варианта:&lt;br /&gt;
&lt;br /&gt;
#Если предмет &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; не попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k,s) = A(k-1, s)&amp;lt;/tex&amp;gt;&lt;br /&gt;
# Если &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака, где вес &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; уменьшаем на вес &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; -ого предмета и набор допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k-1, s-w_k) + p_k&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
То есть: &lt;br /&gt;
&amp;lt;tex&amp;gt;A(k,s) = max(A(k-1,s), A(k-1,s-w_{k}) + p_{k})&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоимость искомого набора равна &amp;lt;tex&amp;gt;A(N,W)&amp;lt;/tex&amp;gt;, так как нужно найти максимальную стоимость рюкзака, где все предметы допустимы и вместимость рюкзака &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановим набор предметов, входящих в рюкзак'''&lt;br /&gt;
&lt;br /&gt;
Будем определять, входит ли &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет в искомый набор. Начинаем с элемента &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;i = N&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;w = W&amp;lt;/tex&amp;gt;. Для этого сравниваем &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt; со следующими значениями:&lt;br /&gt;
&lt;br /&gt;
#Максимальная стоимость рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;&lt;br /&gt;
#Максимальная стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt; меньше и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;p_i&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w-w_i)+p_i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Заметим, что при построении &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; мы выбирали максимум из этих значений и записывали в &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt;. Тогда будем сравнивать &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt; c &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;, если равны, тогда &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не входит в искомый набор, иначе входит.&lt;br /&gt;
&lt;br /&gt;
== Реализация ==&lt;br /&gt;
Сначала генерируем &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
 for i = 0..W&lt;br /&gt;
   A[0][i] = 0;&lt;br /&gt;
 for i = 0..N&lt;br /&gt;
   A[i][0] = 0;   //Первые элементы приравниваем к 0&lt;br /&gt;
 for k = 1..N               &lt;br /&gt;
   for s = 1..W   //Перебираем для каждого k все вместимости &lt;br /&gt;
     if s &amp;gt;= w[k]    //Если текущий предмет вмещается в рюкзак&lt;br /&gt;
       A[k][s] = max(A[k-1][s], A[k-1][s-w[k]]+p[k]); //выбираем класть его или нет&lt;br /&gt;
     else &lt;br /&gt;
       A[k][s] = A[k-1][s];             //иначе, не кладем&lt;br /&gt;
&lt;br /&gt;
Затем найдем набор &amp;lt;tex&amp;gt;ans&amp;lt;/tex&amp;gt; предметов, входящих в рюкзак, рекурсивной функцией:&lt;br /&gt;
&lt;br /&gt;
 findAns(k, s)&lt;br /&gt;
   if A[k][s] == 0 &lt;br /&gt;
     return;&lt;br /&gt;
   if A[k-1][s] == A[k][s]&lt;br /&gt;
     findAns(k-1, s);&lt;br /&gt;
   else &lt;br /&gt;
     findAns(k-1, s - w[k]);&lt;br /&gt;
     ans.push(k);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Пример ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;W = 13, N = 5&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{1} = 3, p_{1} = 1 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{2} = 4, p_{2} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{3} = 5, p_{3} = 4 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{4} = 8, p_{4} = 7 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{5} = 9, p_{5} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Knapsack problem0.png]]&lt;br /&gt;
&lt;br /&gt;
Числа от 0 до 13 в первой строчке обозначают вместимость рюкзака.&lt;br /&gt;
&lt;br /&gt;
В первой строке как только вместимость рюкзака &amp;lt;tex&amp;gt;n \ge 3&amp;lt;/tex&amp;gt;, добавляем в рюкзак 1 предмет.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим &amp;lt;tex&amp;gt;k = 3&amp;lt;/tex&amp;gt;, при каждом &amp;lt;tex&amp;gt;s \ge 5 (&amp;lt;/tex&amp;gt;так как &amp;lt;tex&amp;gt;w_3 = 5)&amp;lt;/tex&amp;gt; сравниваем &amp;lt;tex&amp;gt;A[k-1][s]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;A[k-1][s-w_3]+p_3&amp;lt;/tex&amp;gt; и записываем в &amp;lt;tex&amp;gt;A[k][s]&amp;lt;/tex&amp;gt; стоимость либо рюкзака без третьего предмета, но с таким же весом, либо с третьим предметом, тогда стоимость равна стоимости третьего предмета плюс стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_3&amp;lt;/tex&amp;gt; меньше.     &lt;br /&gt;
&lt;br /&gt;
Максимальная стоимость рюкзака находится в &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановление набора предметов, из которых состоит максимально дорогой рюкзак.'''&lt;br /&gt;
&lt;br /&gt;
Начиная с &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt; восстанавливаем ответ.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Задача о рюкзаке3.png]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, в набор входит &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt; предмет.&lt;br /&gt;
&lt;br /&gt;
Стоимость рюкзака &amp;lt;tex&amp;gt;= 6 + 7 = 13&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вес рюкзака &amp;lt;tex&amp;gt;= 4 + 8 = 12&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Другие задачи семейства=&lt;br /&gt;
==Ограниченный рюкзак ==&lt;br /&gt;
'''Ограниченный рюкзак''' (англ. ''Bounded Knapsack Problem'') - обобщение классической задачи, когда любой предмет может быть взят некоторое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит склад. Он может унести ограниченный вес, каждый товар на складе содержится в определенном ограниченном количестве. Нужно унести предметов на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран ограниченное &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; число раз.&lt;br /&gt;
Задача выбрать число &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
* максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
* выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \in (0,1,...,b_i)&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
При небольших &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; решается сведением к классической задаче о рюкзаке. В иных случаях:&lt;br /&gt;
* Методом ветвей и границ.&lt;br /&gt;
* Методом динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого возможного числа предметов типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 1 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;d(i,c) = max(d(i - 1, c - lw_i) + lp_i) &amp;lt;/tex&amp;gt; по всем целым &amp;lt;tex&amp;gt; l &amp;lt;/tex&amp;gt; из промежутка &amp;lt;tex&amp;gt; 0 \le l \le min(b_i,\lfloor c/w_i \rfloor)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного.&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 for i = 0..W // база&lt;br /&gt;
   d[0][i] = 0;&lt;br /&gt;
 for i = 1..N             &lt;br /&gt;
   for c = 1..W   //Перебираем для каждого i, все вместимости &lt;br /&gt;
     d[i][c] = d[i - 1][c];&lt;br /&gt;
     for l = min(b[i], c / w[i]) .. 1 // ищем l для которого выполняется максимум&lt;br /&gt;
         d[i][c] =  max(d[i][c], d[i - 1][c - l * w[i]] + p[i] * l);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Неограниченный рюкзак==&lt;br /&gt;
'''Неограниченный рюкзак''' (англ.''Unbounded Knapsack Problem'') - обобщение ограниченного рюкзака, в котором любой предмет может быть выбран любое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Перекупщик закупается на оптовой базе. Он может увезти ограниченное количество товара, количество товара каждого типа на базе не ограниченно. Нужно увезти товар на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого количества вещей типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; включительно.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i, c - w_i) + p_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = max(d(c), d(c - w_i) + p_i)   &amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Непрерывный рюкзак==&lt;br /&gt;
'''Непрерывный рюкзак''' (англ. ''Continuous knapsack problem'') - вариант задачи, в котором возможно брать любою дробную часть от предмета, при этом удельная стоимость сохраняется.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит мясника. Суммарно он может унести ограниченный вес товаров. Вор может резать товары без ущерба к удельной стоимости. Нужно унести товара на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Задача выбрать часть &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; каждого предмета так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; 0 \le x_i \le 1&amp;lt;/tex&amp;gt; дробное, для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Возможность брать любую часть от предмета сильно упрощает задачу. Жадный алгоритм дает оптимальное решение в данном случае.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 sort(); // сортируем в порядке убывания удельной стоимости.&lt;br /&gt;
 for i = 1..N // идем по предметам            &lt;br /&gt;
       if W &amp;gt;= w[i]    //если помещается - берем&lt;br /&gt;
        sum += p[i];&lt;br /&gt;
        W -= w[i];&lt;br /&gt;
    else&lt;br /&gt;
        sum += W / w[i] * p[i];// иначе берем сколько можно и выходим&lt;br /&gt;
        break;&lt;br /&gt;
&lt;br /&gt;
==Задача о суммах подмножеств==&lt;br /&gt;
'''Задача о суммах подмножеств''' (англ. ''Subset-sum problem, Value Independent Knapsack Problem'') - задача из семейства, в которой стоимость предмета совпадает с его весом.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' В машина может увезти определенное количество груза. Нужно увезти как можно больше крупного неделимого мусора за раз.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Нужно выбрать подмножество так, чтобы сумма ближе всего к &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, но не превысила его. Формально, нужно найти набор бинарных величин &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt;, так чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_j = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен рюкзаку, иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Для решения пригодны любые методы применяемые для классической задачи, однако специализированые алгоритмы обычно более оптимальны по параметрам. Используются:&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
* Гибридный метод на основе динамического программирования и поиска по дереву. В худшем случае, работает за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная сумма  &amp;lt;tex&amp;gt;\le c&amp;lt;/tex&amp;gt;, подмножества взятого из &amp;lt;tex&amp;gt; 1, ...,\ i&amp;lt;/tex&amp;gt; элементов.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i - 1, c - w_i) + w_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная сумма подмножества, не превышающая заданное значение.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача о размене==&lt;br /&gt;
'''Задача о размене''' (англ. ''Change-Making problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; неисчерпаемых типов предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;.  Нужно наполнить рюкзак предметами с суммарным весом &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Часто задачу ставят как, дать сдачу наименьшим количеством монет.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество взятых предметов: &amp;lt;tex&amp;gt;\sum_{i=1}^N x_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*сумма весов выбранных предметов равнялась вместимости рюкзака: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i = W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; минимальное число предметов, типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, необходимое, чтобы заполнить рюкзак вместимостью &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(0,0) = 0&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;d(0,c) = \inf&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt;c &amp;gt; 0&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 min(d(i - 1, c), d(i, c - w_i) + 1) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = min(d(c), d(c - w_i) + 1) \qquad  for\ c = w_i, ..., W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача об упаковке==&lt;br /&gt;
'''Задача об упаковке''' (англ. ''Bin Packing Problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; рюкзаков вместимости &amp;lt;tex&amp;gt; W &amp;lt;/tex&amp;gt; и столько же предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;. Нужно распределить все предметы, задействовав минимальное количество рюкзаков.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Нужно вывезти из шахты все куски руды, используя наименьшее число вагонеток.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Математически задачу можно представить так:&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество рюкзаков: &amp;lt;tex&amp;gt;\sum_{i=1}^N y_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*так чтобы выполнялось условие на совместность: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_{ij} \le Wy_j \qquad j \in {1, ..., N}&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; y_i = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; i&amp;lt;/tex&amp;gt; рюкзак используется. Иначе &amp;lt;tex&amp;gt; y_i = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Обычно применяют аппроксимационные алгоритмы, либо используют метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Мультипликативный рюкзак==&lt;br /&gt;
'''Мультипликативный рюкзак''' (англ. ''Multiple Knapsack Problem'') - есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;. Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' У транспортной компании есть парк машин разной грузоподъемности. Нужно перевезти товара на максимальную сумму с одного склада на другой единовременно.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Максимизировать &amp;lt;tex&amp;gt;\sum_{i=1}^M \sum_{j=1}^{N} p_jx_{ij}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так, чтобы &amp;lt;tex&amp;gt;\sum_{i=1}^N w_jx_{ij} \le W_i&amp;lt;/tex&amp;gt; выполнялось для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\sum_{j=1}^{M}x_{ij}=1&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования, для задач данного типа нецелесообразно. Используются вариации метода ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Задача о назначении==&lt;br /&gt;
'''Задача о назначении''' (англ. ''Generalized Assignment Problem'') - Наиболее общая задача семейства. Отличается от мультипликативного рюкзака тем, что каждый предмет имеет различные характеристики в зависимости от рюкзака, куда его помещают. Есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;, у &amp;lt;tex&amp;gt; j &amp;lt;/tex&amp;gt; предмета &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; стоимость и вес, при помещении его в &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; рюкзак, равны &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; w_{ij} &amp;lt;/tex&amp;gt;  соответственно.  Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
Весьма важная задача, так как она моделирует оптимальное распределение различных задач между вычислительными блоками.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
&lt;br /&gt;
Максимизировать стоимость выбранных предметов &amp;lt;tex&amp;gt;\sum_{i=1}^M\sum_{j=1}^N p_{ij} x_{ij}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
при выполнении условия совместности &amp;lt;tex&amp;gt;\sum_{j=1}^N w_{ij} x_{ij} \le W_i \qquad i=1, \ldots, M&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; \sum_{i=1}^M x_{ij} \leq 1 \qquad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} \in \{0,1\} \qquad i=1, \ldots, N, \quad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Наиболее используем метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*[http://informatics.mccme.ru/moodle/mod/book/view.php?id=815&amp;amp;chapterid=60 Дистанционная подготовка по информатике]&lt;br /&gt;
*[http://rosettacode.org/wiki/Knapsack_Problem Код для нескольких задач семейства на всевозможных языках]&lt;br /&gt;
*[http://www.diku.dk/users/pisinger/95-1.pdf David Pisinger Knapsack problems. — 1995]&lt;br /&gt;
*Silvano Martello, Paolo Toth. Knapsack Problems: Algorithms and Computer Implementations — 1990 г. — ISBN 0-471-92420-2&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55473</id>
		<title>Задача о рюкзаке</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5&amp;diff=55473"/>
				<updated>2016-10-08T03:22:50Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Метод динамического программирования */ разве здесь не квадрат?&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Задача о рюкзаке'''(''англ. Knapsack problem'') — дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет имеет массу &amp;lt;tex&amp;gt; w_i &amp;gt; 0&amp;lt;/tex&amp;gt; и стоимость &amp;lt;tex&amp;gt; p_i &amp;gt; 0&amp;lt;/tex&amp;gt;. Необходимо выбрать из этих предметов такой набор, чтобы суммарная масса не превосходила заданной величины &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; (вместимость рюкзака), а суммарная стоимость была максимальна.&lt;br /&gt;
&lt;br /&gt;
== Формулировка задачи ==&lt;br /&gt;
&lt;br /&gt;
Дано &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов, &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt; - вместимость рюкзака, &amp;lt;tex&amp;gt;w=\{w_{1},w_{2},...,w_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых весов, &amp;lt;tex&amp;gt;p=\{p_{1},p_{2},...,p_{N}\}&amp;lt;/tex&amp;gt; — соответствующий ему набор положительных целых стоимостей. Нужно найти набор бинарных величин &amp;lt;tex&amp;gt;B=\{b_{1},b_{2},...,b_{N}\}&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;b_{i} = 1 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; включен в набор, &amp;lt;tex&amp;gt; b_{i} = 0 &amp;lt;/tex&amp;gt;, если предмет &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не включен, и такой что:&lt;br /&gt;
&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} w_{1}+ ... + b_{N} w_{N} \le W&amp;lt;/tex&amp;gt;&lt;br /&gt;
#&amp;lt;tex&amp;gt;b_{1} p_{1}+ ... + b_{N} p_{N} &amp;lt;/tex&amp;gt; максимальна.&lt;br /&gt;
&lt;br /&gt;
== Варианты решения ==&lt;br /&gt;
&lt;br /&gt;
Задачу о рюкзаке можно решить несколькими способами:&lt;br /&gt;
&lt;br /&gt;
* Перебирать все подмножества набора из N предметов. Сложность такого решения &amp;lt;tex&amp;gt;O({2^{N}})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Методом [[Meet-in-the-middle|Meet-in-the-middle]]. Сложность решения &amp;lt;tex&amp;gt; O({2^{N/2}}\times{N}) &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Метод динамического программирования. Сложность - &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Метод динамического программирования ==&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; есть максимальная стоимости предметов, которые можно уложить в рюкзак вместимости &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, если можно использовать только первые &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; предметов, то есть &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_k\}&amp;lt;/tex&amp;gt;, назовем этот набор допустимых предметов для &amp;lt;tex&amp;gt;A(k,s)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(k, 0) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;A(0, s) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Найдем &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt;. Возможны 2 варианта:&lt;br /&gt;
&lt;br /&gt;
#Если предмет &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; не попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k,s) = A(k-1, s)&amp;lt;/tex&amp;gt;&lt;br /&gt;
# Если &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; попал в рюкзак. Тогда &amp;lt;tex&amp;gt;A(k, s)&amp;lt;/tex&amp;gt; равно максимальной стоимости рюкзака, где вес &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; уменьшаем на вес &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; -ого предмета и набор допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{k-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(k-1, s-w_k) + p_k&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
То есть: &lt;br /&gt;
&amp;lt;tex&amp;gt;A(k,s) = max(A(k-1,s), A(k-1,s-w_{k}) + p_{k})&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стоимость искомого набора равна &amp;lt;tex&amp;gt;A(N,W)&amp;lt;/tex&amp;gt;, так как нужно найти максимальную стоимость рюкзака, где все предметы допустимы и вместимость рюкзака &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановим набор предметов, входящих в рюкзак'''&lt;br /&gt;
&lt;br /&gt;
Будем определять, входит ли &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; предмет в искомый набор. Начинаем с элемента &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;i = N&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;w = W&amp;lt;/tex&amp;gt;. Для этого сравниваем &amp;lt;tex&amp;gt;A(i,w)&amp;lt;/tex&amp;gt; со следующими значениями:&lt;br /&gt;
&lt;br /&gt;
#Максимальная стоимость рюкзака с такой же вместимостью и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;&lt;br /&gt;
#Максимальная стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt; меньше и набором допустимых предметов &amp;lt;tex&amp;gt;\{n_1,n_2,...,n_{i-1}\}&amp;lt;/tex&amp;gt; плюс стоимость &amp;lt;tex&amp;gt;p_i&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;A(i-1, w-w_i)+p_i&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Заметим, что при построении &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; мы выбирали максимум из этих значений и записывали в &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt;. Тогда будем сравнивать &amp;lt;tex&amp;gt;A(i, w)&amp;lt;/tex&amp;gt; c &amp;lt;tex&amp;gt;A(i-1, w)&amp;lt;/tex&amp;gt;, если равны, тогда &amp;lt;tex&amp;gt;n_i&amp;lt;/tex&amp;gt; не входит в искомый набор, иначе входит.&lt;br /&gt;
&lt;br /&gt;
== Реализация ==&lt;br /&gt;
Сначала генерируем &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
 for i = 0..W&lt;br /&gt;
   A[0][i] = 0;&lt;br /&gt;
 for i = 0..N&lt;br /&gt;
   A[i][0] = 0;   //Первые элементы приравниваем к 0&lt;br /&gt;
 for k = 1..N               &lt;br /&gt;
   for s = 1..W   //Перебираем для каждого k все вместимости &lt;br /&gt;
     if s &amp;gt;= w[k]    //Если текущий предмет вмещается в рюкзак&lt;br /&gt;
       A[k][s] = max(A[k-1][s], A[k-1][s-w[k]]+p[k]); //выбираем класть его или нет&lt;br /&gt;
     else &lt;br /&gt;
       A[k][s] = A[k-1][s];             //иначе, не кладем&lt;br /&gt;
&lt;br /&gt;
Затем найдем набор &amp;lt;tex&amp;gt;ans&amp;lt;/tex&amp;gt; предметов, входящих в рюкзак, рекурсивной функцией:&lt;br /&gt;
&lt;br /&gt;
 findAns(k, s)&lt;br /&gt;
   if A[k][s] == 0 &lt;br /&gt;
     return;&lt;br /&gt;
   if A[k-1][s] == A[k][s]&lt;br /&gt;
     findAns(k-1, s);&lt;br /&gt;
   else &lt;br /&gt;
     findAns(k-1, s - w[k]);&lt;br /&gt;
     ans.push(k);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(N \times W)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Пример ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;W = 13, N = 5&amp;lt;/tex&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{1} = 3, p_{1} = 1 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{2} = 4, p_{2} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{3} = 5, p_{3} = 4 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{4} = 8, p_{4} = 7 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;w_{5} = 9, p_{5} = 6 &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Knapsack problem0.png]]&lt;br /&gt;
&lt;br /&gt;
Числа от 0 до 13 в первой строчке обозначают вместимость рюкзака.&lt;br /&gt;
&lt;br /&gt;
В первой строке как только вместимость рюкзака &amp;lt;tex&amp;gt;n \ge 3&amp;lt;/tex&amp;gt;, добавляем в рюкзак 1 предмет.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим &amp;lt;tex&amp;gt;k = 3&amp;lt;/tex&amp;gt;, при каждом &amp;lt;tex&amp;gt;s \ge 5 (&amp;lt;/tex&amp;gt;так как &amp;lt;tex&amp;gt;w_3 = 5)&amp;lt;/tex&amp;gt; сравниваем &amp;lt;tex&amp;gt;A[k-1][s]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;A[k-1][s-w_3]+p_3&amp;lt;/tex&amp;gt; и записываем в &amp;lt;tex&amp;gt;A[k][s]&amp;lt;/tex&amp;gt; стоимость либо рюкзака без третьего предмета, но с таким же весом, либо с третьим предметом, тогда стоимость равна стоимости третьего предмета плюс стоимость рюкзака с вместимостью на &amp;lt;tex&amp;gt;w_3&amp;lt;/tex&amp;gt; меньше.     &lt;br /&gt;
&lt;br /&gt;
Максимальная стоимость рюкзака находится в &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Восстановление набора предметов, из которых состоит максимально дорогой рюкзак.'''&lt;br /&gt;
&lt;br /&gt;
Начиная с &amp;lt;tex&amp;gt;A(5, 13)&amp;lt;/tex&amp;gt; восстанавливаем ответ.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Задача о рюкзаке3.png]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, в набор входит &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt; предмет.&lt;br /&gt;
&lt;br /&gt;
Стоимость рюкзака &amp;lt;tex&amp;gt;= 6 + 7 = 13&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вес рюкзака &amp;lt;tex&amp;gt;= 4 + 8 = 12&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Другие задачи семейства=&lt;br /&gt;
==Ограниченный рюкзак ==&lt;br /&gt;
'''Ограниченный рюкзак''' (англ. ''Bounded Knapsack Problem'') - обобщение классической задачи, когда любой предмет может быть взят некоторое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит склад. Он может унести ограниченный вес, каждый товар на складе содержится в определенном ограниченном количестве. Нужно унести предметов на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран ограниченное &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; число раз.&lt;br /&gt;
Задача выбрать число &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
* максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
* выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \in (0,1,...,b_i)&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
При небольших &amp;lt;tex&amp;gt;b_i&amp;lt;/tex&amp;gt; решается сведением к классической задаче о рюкзаке. В иных случаях:&lt;br /&gt;
* Методом ветвей и границ.&lt;br /&gt;
* Методом динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого возможного числа предметов типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 1 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;d(i,c) = max(d(i - 1, c - lw_i) + lp_i) &amp;lt;/tex&amp;gt; по всем целым &amp;lt;tex&amp;gt; l &amp;lt;/tex&amp;gt; из промежутка &amp;lt;tex&amp;gt; 0 \le l \le min(b_i,\lfloor c/w_i \rfloor)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного.&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 for i = 0..W // база&lt;br /&gt;
   d[0][i] = 0;&lt;br /&gt;
 for i = 1..N             &lt;br /&gt;
   for c = 1..W   //Перебираем для каждого i, все вместимости &lt;br /&gt;
     d[i][c] = d[i - 1][c];&lt;br /&gt;
     for l = min(b[i], c / w[i]) .. 1 // ищем l для которого выполняется максимум&lt;br /&gt;
         d[i][c] =  max(d[i][c], d[i - 1][c - l * w[i]] + p[i] * l);&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Неограниченный рюкзак==&lt;br /&gt;
'''Неограниченный рюкзак''' (англ.''Unbounded Knapsack Problem'') - обобщение ограниченного рюкзака, в котором любой предмет может быть выбран любое количество раз.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Перекупщик закупается на оптовой базе. Он может увезти ограниченное количество товара, количество товара каждого типа на базе не ограниченно. Нужно увезти товар на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная стоимость любого количества вещей типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, суммарным весом до &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; включительно.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i, c - w_i) + p_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = max(d(c), d(c - w_i) + p_i)   &amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW^2)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Непрерывный рюкзак==&lt;br /&gt;
'''Непрерывный рюкзак''' (англ. ''Continuous knapsack problem'') - вариант задачи, в котором возможно брать любою дробную часть от предмета, при этом удельная стоимость сохраняется.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Вор грабит мясника. Суммарно он может унести ограниченный вес товаров. Вор может резать товары без ущерба к удельной стоимости. Нужно унести товара на максимальную сумму.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Задача выбрать часть &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; каждого предмета так, чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N p_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
где &amp;lt;tex&amp;gt; 0 \le x_i \le 1&amp;lt;/tex&amp;gt; дробное, для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Возможность брать любую часть от предмета сильно упрощает задачу. Жадный алгоритм дает оптимальное решение в данном случае.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 sort(); // сортируем в порядке убывания удельной стоимости.&lt;br /&gt;
 for i = 1..N // идем по предметам            &lt;br /&gt;
       if W &amp;gt;= w[i]    //если помещается - берем&lt;br /&gt;
        sum += p[i];&lt;br /&gt;
        W -= w[i];&lt;br /&gt;
    else&lt;br /&gt;
        sum += W / w[i] * p[i];// иначе берем сколько можно и выходим&lt;br /&gt;
        break;&lt;br /&gt;
&lt;br /&gt;
==Задача о суммах подмножеств==&lt;br /&gt;
'''Задача о суммах подмножеств''' (англ. ''Subset-sum problem, Value Independent Knapsack Problem'') - задача из семейства, в которой стоимость предмета совпадает с его весом.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' В машина может увезти определенное количество груза. Нужно увезти как можно больше крупного неделимого мусора за раз.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Нужно выбрать подмножество так, чтобы сумма ближе всего к &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, но не превысила его. Формально, нужно найти набор бинарных величин &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt;, так чтобы&lt;br /&gt;
&lt;br /&gt;
*максимизировать общую стоимость: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*выполнялось условие совместности: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i \le W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_j = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен рюкзаку, иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Для решения пригодны любые методы применяемые для классической задачи, однако специализированые алгоритмы обычно более оптимальны по параметрам. Используются:&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
* Гибридный метод на основе динамического программирования и поиска по дереву. В худшем случае, работает за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; максимальная сумма  &amp;lt;tex&amp;gt;\le c&amp;lt;/tex&amp;gt;, подмножества взятого из &amp;lt;tex&amp;gt; 1, ...,\ i&amp;lt;/tex&amp;gt; элементов.&lt;br /&gt;
&lt;br /&gt;
Заполним &amp;lt;tex&amp;gt;d(0,c)&amp;lt;/tex&amp;gt; нулями.&lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 max(d(i - 1, c), d(i - 1, c - w_i) + w_i) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная сумма подмножества, не превышающая заданное значение.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача о размене==&lt;br /&gt;
'''Задача о размене''' (англ. ''Change-Making problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; неисчерпаемых типов предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;.  Нужно наполнить рюкзак предметами с суммарным весом &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Часто задачу ставят как, дать сдачу наименьшим количеством монет.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Каждый предмет может быть выбран любое число раз.&lt;br /&gt;
Задача выбрать количество &amp;lt;tex&amp;gt;x_i&amp;lt;/tex&amp;gt; предметов каждого типа так, чтобы&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество взятых предметов: &amp;lt;tex&amp;gt;\sum_{i=1}^N x_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*сумма весов выбранных предметов равнялась вместимости рюкзака: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_i = W&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Где &amp;lt;tex&amp;gt; x_i \ge 0 &amp;lt;/tex&amp;gt; целое,  для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Самые распространенные методы точного решения это:&lt;br /&gt;
* Метод ветвей и границ.&lt;br /&gt;
* Метод динамического программирования.&lt;br /&gt;
&lt;br /&gt;
===Метод динамического программирования===&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt; минимальное число предметов, типов от 1 до &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, необходимое, чтобы заполнить рюкзак вместимостью &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;d(0,0) = 0&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;d(0,c) = \inf&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt;c &amp;gt; 0&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Тогда меняя i от 1 до &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;, рассчитаем на каждом шаге &amp;lt;tex&amp;gt;d(i,c)&amp;lt;/tex&amp;gt;, для &amp;lt;tex&amp;gt;c&amp;lt;/tex&amp;gt; от 0 до &amp;lt;tex&amp;gt;W&amp;lt;/tex&amp;gt;, по рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
d(i,c) =&lt;br /&gt;
\begin{cases}&lt;br /&gt;
 d(i - 1, c) &amp;amp; for\ c = 0, ..., w_i - 1; \\&lt;br /&gt;
 min(d(i - 1, c), d(i, c - w_i) + 1) &amp;amp; for\ c = w_i, ..., W; &lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После выполнения в &amp;lt;tex&amp;gt; d(N,W) &amp;lt;/tex&amp;gt; будет лежать максимальная стоимость предметов, помещающихся в рюкзак.&lt;br /&gt;
&lt;br /&gt;
Если не нужно восстанавливать ответ, то можно использовать одномерный массив &amp;lt;tex&amp;gt;d(c)&amp;lt;/tex&amp;gt; вместо двумерного и использовать формулу:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;  d(c) = min(d(c), d(c - w_i) + 1) \qquad  for\ c = w_i, ..., W&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Сложность алгоритма &amp;lt;tex&amp;gt;O(NW)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача об упаковке==&lt;br /&gt;
'''Задача об упаковке''' (англ. ''Bin Packing Problem'') - имеются &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt; рюкзаков вместимости &amp;lt;tex&amp;gt; W &amp;lt;/tex&amp;gt; и столько же предметов с весами &amp;lt;tex&amp;gt;w_i&amp;lt;/tex&amp;gt;. Нужно распределить все предметы, задействовав минимальное количество рюкзаков.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' Нужно вывезти из шахты все куски руды, используя наименьшее число вагонеток.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Математически задачу можно представить так:&lt;br /&gt;
&lt;br /&gt;
*минимизировать количество рюкзаков: &amp;lt;tex&amp;gt;\sum_{i=1}^N y_i&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
*так чтобы выполнялось условие на совместность: &amp;lt;tex&amp;gt;\sum_{i=1}^N w_ix_{ij} \le Wy_j \qquad j \in {1, ..., N}&amp;lt;/tex&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; y_i = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; i&amp;lt;/tex&amp;gt; рюкзак используется. Иначе &amp;lt;tex&amp;gt; y_i = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Обычно применяют аппроксимационные алгоритмы, либо используют метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Мультипликативный рюкзак==&lt;br /&gt;
'''Мультипликативный рюкзак''' (англ. ''Multiple Knapsack Problem'') - есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;. Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
&lt;br /&gt;
'''Пример:''' У транспортной компании есть парк машин разной грузоподъемности. Нужно перевезти товара на максимальную сумму с одного склада на другой единовременно.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
Максимизировать &amp;lt;tex&amp;gt;\sum_{i=1}^M \sum_{j=1}^{N} p_jx_{ij}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
так, чтобы &amp;lt;tex&amp;gt;\sum_{i=1}^N w_jx_{ij} \le W_i&amp;lt;/tex&amp;gt; выполнялось для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\sum_{j=1}^{M}x_{ij}=1&amp;lt;/tex&amp;gt; для всех &amp;lt;tex&amp;gt; i= 1,2,...,N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} = 1 &amp;lt;/tex&amp;gt; если &amp;lt;tex&amp;gt; j&amp;lt;/tex&amp;gt; предмет назначен  &amp;lt;tex&amp;gt;i &amp;lt;/tex&amp;gt; рюкзаку. Иначе &amp;lt;tex&amp;gt; x_{ij} = 0 &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования, для задач данного типа нецелесообразно. Используются вариации метода ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
==Задача о назначении==&lt;br /&gt;
'''Задача о назначении''' (англ. ''Generalized Assignment Problem'') - Наиболее общая задача семейства. Отличается от мультипликативного рюкзака тем, что каждый предмет имеет различные характеристики в зависимости от рюкзака, куда его помещают. Есть &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; предметов и &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; рюкзаков (&amp;lt;tex&amp;gt;M\le N&amp;lt;/tex&amp;gt;). У каждого рюкзака своя вместимость &amp;lt;tex&amp;gt;W_i&amp;lt;/tex&amp;gt;, у &amp;lt;tex&amp;gt; j &amp;lt;/tex&amp;gt; предмета &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; стоимость и вес, при помещении его в &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; рюкзак, равны &amp;lt;tex&amp;gt; p_{ij} &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; w_{ij} &amp;lt;/tex&amp;gt;  соответственно.  Задача: выбрать &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; не пересекающихся множеств, назначить соответствие рюкзакам так, чтобы суммарная стоимость была максимальна, а вес предметов в каждом рюкзаке не превышал его вместимость.&lt;br /&gt;
Весьма важная задача, так как она моделирует оптимальное распределение различных задач между вычислительными блоками.&lt;br /&gt;
===Формулировка Задачи===&lt;br /&gt;
&lt;br /&gt;
Максимизировать стоимость выбранных предметов &amp;lt;tex&amp;gt;\sum_{i=1}^M\sum_{j=1}^N p_{ij} x_{ij}&amp;lt;/tex&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
при выполнении условия совместности &amp;lt;tex&amp;gt;\sum_{j=1}^N w_{ij} x_{ij} \le W_i \qquad i=1, \ldots, M&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; \sum_{i=1}^M x_{ij} \leq 1 \qquad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt; x_{ij} \in \{0,1\} \qquad i=1, \ldots, N, \quad j=1, \ldots, N&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Варианты решения===&lt;br /&gt;
Применение динамического программирования нецелесообразно. Наиболее используем метод ветвей и границ.&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*[http://informatics.mccme.ru/moodle/mod/book/view.php?id=815&amp;amp;chapterid=60 Дистанционная подготовка по информатике]&lt;br /&gt;
*[http://rosettacode.org/wiki/Knapsack_Problem Код для нескольких задач семейства на всевозможных языках]&lt;br /&gt;
*[http://www.diku.dk/users/pisinger/95-1.pdf David Pisinger Knapsack problems. — 1995]&lt;br /&gt;
*Silvano Martello, Paolo Toth. Knapsack Problems: Algorithms and Computer Implementations — 1990 г. — ISBN 0-471-92420-2&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=Meet-in-the-middle&amp;diff=55471</id>
		<title>Meet-in-the-middle</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=Meet-in-the-middle&amp;diff=55471"/>
				<updated>2016-10-07T19:49:25Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Реализация */ опечатка&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
'''Meet-in-the-middle''' (Встреча в середине)  — это метод решения уравнения вида &amp;lt;tex&amp;gt; f({x}) = g({y}) &amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; x \in {X} &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; y \in {Y} &amp;lt;/tex&amp;gt;, который работает за время &amp;lt;tex&amp;gt; O(F(X) + Y \cdot G_X(y))&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt; F(X) &amp;lt;/tex&amp;gt; {{---}} время построения множества &amp;lt;tex&amp;gt; X &amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt; G_X(y) &amp;lt;/tex&amp;gt; {{---}} время поиска элемента &amp;lt;tex&amp;gt; x &amp;lt;/tex&amp;gt; в множестве &amp;lt;tex&amp;gt; X &amp;lt;/tex&amp;gt;, удовлетворяющее решению при заданном &amp;lt;tex&amp;gt; y &amp;lt;/tex&amp;gt;, или проверка, что такого &amp;lt;tex&amp;gt; x &amp;lt;/tex&amp;gt; не существует.&lt;br /&gt;
}}&lt;br /&gt;
'''Meet-in-the-middle''' разбивает задачу пополам и решает всю задачу через частичный расчет половинок. Он работает следующим образом: переберем все возможные значения &amp;lt;tex&amp;gt; {x} &amp;lt;/tex&amp;gt; и запишем пару значений &amp;lt;tex&amp;gt; ({x},{f({x})}) &amp;lt;/tex&amp;gt;  в множество. Затем будем перебирать всевозможные значения &amp;lt;tex&amp;gt; y &amp;lt;/tex&amp;gt;, для каждого из них будем вычислять &amp;lt;tex&amp;gt; g(y) &amp;lt;/tex&amp;gt;, которое мы будем искать в нашем множестве. Если в качестве множества использовать отсортированный массив, а в качестве функции поиска {{---}} [[Целочисленный двоичный поиск | бинарный поиск]], то время работы нашего алгоритма составляет &amp;lt;tex&amp;gt; {O(X\log{X})} &amp;lt;/tex&amp;gt; на сортировку, и &amp;lt;tex&amp;gt; {O(Y\log{X})} &amp;lt;/tex&amp;gt; на двоичный поиск, что дает в сумме &amp;lt;tex&amp;gt;{O((X + Y)\log{X}})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Задача о нахождении четырех чисел с суммой равной нулю ==&lt;br /&gt;
Дан массив целых чисел &amp;lt;tex&amp;gt;{A}&amp;lt;/tex&amp;gt;. Требуется найти любые &amp;lt;tex&amp;gt; 4 &amp;lt;/tex&amp;gt; числа, сумма которых равна &amp;lt;tex&amp;gt; 0 &amp;lt;/tex&amp;gt; (одинаковые элементы могут быть использованы несколько раз).&lt;br /&gt;
&lt;br /&gt;
Например : &amp;lt;tex&amp;gt; {A} = ({2,3,1,0,-4,-1}) &amp;lt;/tex&amp;gt;. Решением данной задачи является, например, четверка чисел &amp;lt;tex&amp;gt; 3 + 1 + 0 - 4 = 0&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt; 0 + 0 + 0 + 0 = 0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Наивный алгоритм заключается в переборе всевозможных комбинаций чисел. Это решение работает за &amp;lt;tex&amp;gt; {O(N^4)}&amp;lt;/tex&amp;gt;. Теперь, с помощью '''Meet-in-the-middle''' мы можем сократить время работы до &amp;lt;tex&amp;gt; {O(N^2\log{N}}) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для этого заметим, что сумму &amp;lt;tex&amp;gt; a + b + c + d = 0 &amp;lt;/tex&amp;gt; можно записать как &amp;lt;tex&amp;gt; a + b = -(c + d)&amp;lt;/tex&amp;gt;. Мы будем хранить все &amp;lt;tex&amp;gt; {N^2} &amp;lt;/tex&amp;gt; пар сумм &amp;lt;tex&amp;gt; a + b &amp;lt;/tex&amp;gt; в массиве &amp;lt;tex&amp;gt; sum &amp;lt;/tex&amp;gt;, который мы отсортируем. Далее перебираем все &amp;lt;tex&amp;gt; {N^2} &amp;lt;/tex&amp;gt; пар сумм &amp;lt;tex&amp;gt; c + d &amp;lt;/tex&amp;gt; и проверяем [[Целочисленный двоичный поиск|бинарным поиском]], есть ли сумма &amp;lt;tex&amp;gt; -(c + d) &amp;lt;/tex&amp;gt; в массиве &amp;lt;tex&amp;gt; sum &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
  // sum - массив сумм a + b, cnt - счетчик массива sum&lt;br /&gt;
  '''findsum'''():&lt;br /&gt;
    '''for''' a = 0..N - 1&lt;br /&gt;
      '''for''' b = 0..N - 1&lt;br /&gt;
        sum[cnt].res = A[a] + B[b]&lt;br /&gt;
        sum[cnt].a = a&lt;br /&gt;
        sum[cnt].b = b&lt;br /&gt;
        cnt++&lt;br /&gt;
    sort(sum, key = &amp;quot;res&amp;quot;) // сортируем sum по полю res&lt;br /&gt;
    '''for''' c = 0..N - 1&lt;br /&gt;
      '''for''' d = 0..N - 1&lt;br /&gt;
        '''if''' сумма -(A[c] + A[d]) есть в массив sum&lt;br /&gt;
           index = индекс суммы -(A[c] + A[d])&lt;br /&gt;
           '''return''' (sum[index].a, sum[index].b, A[c], A[d])&lt;br /&gt;
    '''return''' &amp;quot;No solution&amp;quot;&lt;br /&gt;
Итоговое время работы &amp;lt;tex&amp;gt; {O(N^2\log{N}}) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Если вместо отсортированного массива использовать [[Хеш-таблица | хэш-таблицу]], то задачу можно будет решить за время &amp;lt;tex&amp;gt; O(N^2) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Задача о рюкзаке ==&lt;br /&gt;
Классической задачей является задача о наиболее эффективной упаковке рюкзака. Каждый предмет характеризуется весом (&amp;lt;tex&amp;gt; {w_{i} \leqslant 10^{9}} &amp;lt;/tex&amp;gt; ) и ценностью (&amp;lt;tex&amp;gt;{cost_{i} \leqslant 10^{9}} &amp;lt;/tex&amp;gt;). В рюкзак, ограниченный по весу, необходимо набрать вещей с максимальной суммарной стоимостью. Для ее решения изначальное множество вещей N разбивается на два равных(или примерно равных) подмножества, для которых за приемлемое время можно перебрать все варианты и подсчитать суммарный вес и стоимость, а затем для каждого из них найти группу вещей из первого подмножества с максимальной стоимостью, укладывающуюся в ограничение по весу рюкзака. Сложность алгоритма &amp;lt;tex&amp;gt;O({2^{N/2}}\cdot{N})&amp;lt;/tex&amp;gt;. Память &amp;lt;tex&amp;gt; O({2^{N/2}})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
Разделим наше множество на две части. Подсчитаем все подмножества из первой части и будем хранить их в массиве &amp;lt;tex&amp;gt; first &amp;lt;/tex&amp;gt;. Отсортируем массив &amp;lt;tex&amp;gt; first &amp;lt;/tex&amp;gt; по весу. Далее пройдемся по этому массиву и оставим только те подмножества, для которых не существует другого подмножества с меньшим весом и большей стоимостью. Очевидно, что подмножества, для которых существует другое, более легкое и одновременно более ценное подмножество, можно удалять.&lt;br /&gt;
Таким образом в массиве &amp;lt;tex&amp;gt; first &amp;lt;/tex&amp;gt; мы имеем подмножества, отсортированные не только по весу, но и по стоимости. Тогда начнем перебирать все возможные комбинации вещей из второй половины и находить бинарным поиском удовлетворяющие нам подмножества из первой половины, хранящиеся в массиве &amp;lt;tex&amp;gt; first &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Реализуем данный алгоритм:&lt;br /&gt;
  // N - количество всех вещей, w[] - массив весов всех вещей, cost[] - массив стоимостей всех вещей, R - ограничение по весу рюкзака.&lt;br /&gt;
  '''knapsack'''():&lt;br /&gt;
    sn = N / 2&lt;br /&gt;
    fn = N - sn&lt;br /&gt;
    '''for''' mask = 0..2 ** sn  - 1&lt;br /&gt;
      '''for''' j = 0..sn&lt;br /&gt;
        '''if''' j-ый бит mask == 1&lt;br /&gt;
          first[i].w += w[j];&lt;br /&gt;
          first[i].c += cost[j]&lt;br /&gt;
    сортируем first по весу&lt;br /&gt;
    '''for''' i = 0..2 ** sn - 1&lt;br /&gt;
      '''if''' существует такое подмножество с индексом j, что first[j].w &amp;lt;tex&amp;gt; \leqslant &amp;lt;/tex&amp;gt; first[i].w '''and''' first[j].c &amp;lt;tex&amp;gt; \geqslant &amp;lt;/tex&amp;gt; first[i].c&lt;br /&gt;
        удалим множество с индексом i из массива first&lt;br /&gt;
  &lt;br /&gt;
    '''for''' mask = 0..2 ** fn - 1&lt;br /&gt;
      '''for''' j = 0..fn&lt;br /&gt;
        '''if''' j-ый бит mask == 1&lt;br /&gt;
          curw += w[j + sn]&lt;br /&gt;
          curcost += cost[j + sn]&lt;br /&gt;
    &lt;br /&gt;
      index = позиция, найденная бинарным поиском в массиве first, подмножества с максимальным весом, не превыщающим R - curv&lt;br /&gt;
      '''if''' first[index].w &amp;lt;tex&amp;gt; \leqslant &amp;lt;/tex&amp;gt; R - curw '''and''' first[index].c + curcost &amp;lt;tex&amp;gt; &amp;gt; &amp;lt;/tex&amp;gt; ans&lt;br /&gt;
        ans = first[index].c + curcost&lt;br /&gt;
    '''return''' ans&lt;br /&gt;
&lt;br /&gt;
Итоговое время работы &amp;lt;tex&amp;gt; {O({2^{N/2}}\cdot({N}+\log{2^{N/2}}))} = O({2^{N/2}}\cdot{N}) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Задача о нахождении кратчайшего расстояния между двумя вершинами в графе ==&lt;br /&gt;
[[Файл:bfs.png|600px|thumb|right|Нахождение кратчайшего расстояния между двумя вершинами]]&lt;br /&gt;
Еще одна задача, решаемая '''Meet-in-the-middle'''  —  это нахождение кратчайшего расстояния между двумя вершинами, зная начальное состояние, конечное состояние и то, что длина оптимального пути не превышает &amp;lt;tex&amp;gt; N &amp;lt;/tex&amp;gt;.&lt;br /&gt;
Стандартным подходом для решения данной задачи, является применение алгоритма [[Обход в ширину|обхода в ширину]]. Пусть из каждого состояния у нас есть &amp;lt;tex&amp;gt; K &amp;lt;/tex&amp;gt; переходов, тогда бы мы сгенерировали &amp;lt;tex&amp;gt; {K^{N}} &amp;lt;/tex&amp;gt; состояний. Асимптотика данного решения составила бы &amp;lt;tex&amp;gt; {O({K^{N}})} &amp;lt;/tex&amp;gt;. '''Meet-in-the-middle''' помогает снизить асимптотику до &amp;lt;tex&amp;gt; {O({K^{N/2}})} &amp;lt;/tex&amp;gt;. &amp;lt;br&amp;gt;&lt;br /&gt;
=== Алгоритм решения === &lt;br /&gt;
&lt;br /&gt;
1. Сгенерируем '''bfs'''-ом все состояния, доступные из начала и конца за &amp;lt;tex&amp;gt; {N/2} &amp;lt;/tex&amp;gt; или меньше ходов.&lt;br /&gt;
&lt;br /&gt;
2. Найдем состояния, которые достижимы из начала и из конца.&lt;br /&gt;
&lt;br /&gt;
3. Найдем среди них наилучшее по сумме длин путей. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, '''bfs-ом''' из двух концов, мы сгенерируем максимум &amp;lt;tex&amp;gt; {O({K^{N/2}})} &amp;lt;/tex&amp;gt; состояний.&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
* [[Обход в ширину]]&lt;br /&gt;
* [[Целочисленный двоичный поиск]]&lt;br /&gt;
&lt;br /&gt;
==Cсылки==&lt;br /&gt;
*[http://infoarena.ro/blog/meet-in-the-middle Meet-in-the-middle]&lt;br /&gt;
*[http://g6prog.narod.ru/dpl.ps Лекции по информатике (36 страница)]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Динамическое программирование ]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=55468</id>
		<title>Задача о редакционном расстоянии, алгоритм Вагнера-Фишера</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%BC_%D1%80%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B8,_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%92%D0%B0%D0%B3%D0%BD%D0%B5%D1%80%D0%B0-%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0&amp;diff=55468"/>
				<updated>2016-10-07T18:10:29Z</updated>
		
		<summary type="html">&lt;p&gt;37.57.17.214: /* Память */ опечатки&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|id=levenstain_dist&lt;br /&gt;
|definition=&lt;br /&gt;
'''Расстояние Левенштейна''' (англ. ''Levenshtein distance'') (также '''редакционное расстояние''' или '''дистанция редактирования''') между двумя строками в теории информации и компьютерной лингвистике — это минимальное количество операций вставки одного символа, удаления одного символа и замены одного символа на другой, необходимых для превращения одной строки в другую.}}&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
&lt;br /&gt;
Для расстояния Левенштейна справедливы следующие утверждения:&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \geqslant | |S_1| - |S_2| |&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) \leqslant max( |S_1| , |S_2| )&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) = 0 \Leftrightarrow S_1 = S_2&amp;lt;/tex&amp;gt;&lt;br /&gt;
где &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2)&amp;lt;/tex&amp;gt; — расстояние Левенштейна между строками &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;|S|&amp;lt;/tex&amp;gt; — длина строки &amp;lt;tex&amp;gt;S&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Расстояние Левенштейна является [[Метрическое пространство|метрикой]].&lt;br /&gt;
Для того чтобы доказать это достаточно доказать что выполняется неравенство треугольника:&lt;br /&gt;
* &amp;lt;tex&amp;gt;\rm{d}(S_1,S_3) \leqslant \rm{d}(S_1,S_2) + \rm{d}(S_2,S_3)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;\rm{d}(S_1,S_3) = x&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\rm{d}(S_1,S_2) = y&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\rm{d}(S_2,S_3) = z&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; — кратчайшее редакционное расстояние от &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;S_3&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; — кратчайшее редакционное расстояние от &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt; — кратчайшее редакционное расстояние от &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;S_3&amp;lt;/tex&amp;gt;. &amp;lt;tex&amp;gt;y + z&amp;lt;/tex&amp;gt; — какое-то расстояние от &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;S_3&amp;lt;/tex&amp;gt;. В других случаях &amp;lt;tex&amp;gt;\rm{d}(S_1,S_3) &amp;lt; \rm{d}(S_1,S_2) + \rm{d}(S_2,S_3)&amp;lt;/tex&amp;gt;. Следовательно, выполняется неравенство треугольника.&lt;br /&gt;
&lt;br /&gt;
== Разные цены операций ==&lt;br /&gt;
&lt;br /&gt;
Цены операций могут зависеть от вида операции (вставка, удаление, замена) и/или от участвующих в ней символов, отражая разную вероятность разных ошибок при вводе текста, и т. п. В общем случае:&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(a, b)&amp;lt;/tex&amp;gt; — цена замены символа &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; на символ &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(\varepsilon, b)&amp;lt;/tex&amp;gt; — цена вставки символа &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(a, \varepsilon)&amp;lt;/tex&amp;gt; — цена удаления символа &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для решения задачи о редакционном расстоянии, необходимо найти последовательность замен, минимизирующую суммарную цену. Расстояние Левенштейна является частным случаем этой задачи при&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(a, a) = 0&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(a, b) = 1&amp;lt;/tex&amp;gt; при &amp;lt;tex&amp;gt;a\ne b&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(\varepsilon, b) = 1&amp;lt;/tex&amp;gt;&lt;br /&gt;
* &amp;lt;tex&amp;gt;w(a, \varepsilon) = 1&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\varepsilon&amp;lt;/tex&amp;gt; — пустая последовательность.&lt;br /&gt;
&lt;br /&gt;
Как частный случай, так и задачу для произвольных &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt;, решает алгоритм Вагнера-Фишера, приведённый ниже. Здесь и ниже мы считаем, что все &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt; неотрицательны, и действует правило треугольника: если две последовательные операции можно заменить одной, это не ухудшает общую цену (например, заменить символ &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;, а потом с &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt; не лучше, чем сразу &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Формула ==&lt;br /&gt;
&lt;br /&gt;
Будем считать, что элементы строк нумеруются с первого, как принято в математике, а не нулевого.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; — две строки (длиной &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; соответственно) над некоторым алфавитом, тогда редакционное расстояние &amp;lt;tex&amp;gt;\rm{d}(S_1, S_2)&amp;lt;/tex&amp;gt; можно подсчитать по следующей рекуррентной формуле:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\ \rm{d}(S_1, S_2) = \rm{D}(M,N)&amp;lt;/tex&amp;gt; , где&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;&lt;br /&gt;
D(i, j) = \left\{\begin{array}{llcl}&lt;br /&gt;
0&amp;amp;;\ i = 0,\ j = 0\\&lt;br /&gt;
i&amp;amp;;\ j = 0,\ i &amp;gt; 0\\&lt;br /&gt;
j&amp;amp;;\ i = 0,\ j &amp;gt; 0\\&lt;br /&gt;
D(i - 1, j - 1)&amp;amp;;\ S_1[i] = S_2[j]\\&lt;br /&gt;
\min{(}\\&lt;br /&gt;
\begin{array}{llcl}&lt;br /&gt;
&amp;amp;D(i, j - 1) + insertCost\\&lt;br /&gt;
&amp;amp;D(i - 1, j) + deleteCost&amp;amp;&amp;amp;\\&lt;br /&gt;
&amp;amp;D(i - 1, j - 1) + replaceCost\\&lt;br /&gt;
\end{array}&amp;amp;;\ j &amp;gt; 0,\ i &amp;gt; 0,\ S_1[i] \ne S_2[j]\\&lt;br /&gt;
)&lt;br /&gt;
\end{array}\right.&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;\min(a, b, c)&amp;lt;/tex&amp;gt; возвращает наименьший из аргументов.&lt;br /&gt;
&lt;br /&gt;
=== Доказательство ===&lt;br /&gt;
&lt;br /&gt;
Рассмотрим формулу более подробно. Здесь &amp;lt;tex&amp;gt;D(i, j)&amp;lt;/tex&amp;gt; — расстояние между префиксами строк: первыми &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; символами строки &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и первыми &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; символами строки &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;. Для нашей динамики должен выполняться [[Динамическое программирование|принцип оптимальности на префиксе]]. Очевидно, что редакционное расстояние между двумя пустыми строками равно нулю. Так же очевидно то, что чтобы получить пустую строку из строки длиной &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, нужно совершить &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; операций удаления, а чтобы получить строку длиной &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; из пустой, нужно произвести &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; операций вставки. Осталось рассмотреть нетривиальный случай, когда обе строки непусты.&lt;br /&gt;
&lt;br /&gt;
Для начала заметим, что в оптимальной последовательности операций, их можно произвольно менять местами. В самом деле, рассмотрим две последовательные операции:&lt;br /&gt;
* Две замены одного и того же символа — неоптимально (если мы заменили &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt;, потом &amp;lt;tex&amp;gt;y&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt;, выгоднее было сразу заменить &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; на &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt;).&lt;br /&gt;
* Две замены разных символов можно менять местами&lt;br /&gt;
* Два стирания или две вставки можно менять местами&lt;br /&gt;
* Вставка символа с его последующим стиранием — неоптимально (можно их обе отменить)&lt;br /&gt;
* Стирание и вставку разных символов можно менять местами&lt;br /&gt;
* Вставка символа с его последующей заменой — неоптимально (излишняя замена)&lt;br /&gt;
* Вставка символа и замена другого символа меняются местами&lt;br /&gt;
* Замена символа с его последующим стиранием — неоптимально (излишняя замена)&lt;br /&gt;
* Стирание символа и замена другого символа меняются местами&lt;br /&gt;
&lt;br /&gt;
Пускай &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; кончается на символ &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; кончается на символ &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;. Есть три варианта:&lt;br /&gt;
# Символ «а», на который кончается &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, в какой-то момент был стёрт. Сделаем это стирание первой операцией. Тогда мы стёрли символ &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, после чего превратили первые &amp;lt;tex&amp;gt;i-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; (на что потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)&amp;lt;/tex&amp;gt; операций), значит, всего потребовалось &amp;lt;tex&amp;gt;D(i-1,\ j)+1&amp;lt;/tex&amp;gt; операций&lt;br /&gt;
# Символ &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;, на который кончается &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, в какой-то момент был добавлен. Сделаем это добавление последней операцией. Мы превратили &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; в первые &amp;lt;tex&amp;gt;j-1&amp;lt;/tex&amp;gt; символов &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, после чего добавили &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;. Аналогично предыдущему случаю, потребовалось &amp;lt;tex&amp;gt;D(i,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
# Оба предыдущих утверждения неверны. Если мы добавляли символы справа от финального &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt;, то чтобы сделать последним символом &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt;, мы должны были или в какой-то момент добавить его (но тогда утверждение 2 было бы верно), либо заменить на него один из этих добавленных символов (что тоже невозможно, потому что добавление символа с его последующей заменой неоптимально). Значит, символов справа от финального &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; мы не добавляли. Самого финального &amp;lt;tex&amp;gt;a&amp;lt;/tex&amp;gt; мы не стирали, поскольку утверждение 1 неверно. Значит, единственный способ изменения последнего символа — его замена. Заменять его 2 или больше раз неоптимально. Значит,&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a=b&amp;lt;/tex&amp;gt;, мы последний символ не меняли. Поскольку мы его также не стирали и не приписывали ничего справа от него, он не влиял на наши действия, и, значит, мы выполнили &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
## Если &amp;lt;tex&amp;gt;a\ne b&amp;lt;/tex&amp;gt;, мы последний символ меняли один раз. Сделаем эту замену первой. В дальнейшем, аналогично предыдущему случаю, мы должны выполнить &amp;lt;tex&amp;gt;D(i-1,\ j-1)&amp;lt;/tex&amp;gt; операций, значит, всего потребуется &amp;lt;tex&amp;gt;D(i-1,\ j-1)+1&amp;lt;/tex&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
== Алгоритм Вагнера — Фишера ==&lt;br /&gt;
&lt;br /&gt;
Для нахождения кратчайшего расстояния необходимо вычислить матрицу &amp;lt;tex&amp;gt;D&amp;lt;/tex&amp;gt;, используя [[#Формула|вышеприведённую формулу]]. Её можно вычислять как по строкам, так и по столбцам. &lt;br /&gt;
Псевдокод алгоритма, написанный при произвольных ценах замен, вставок и удалений (важно помнить, что элементы нумеруются с &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
Псевдокод ниже решает простой частный случай, когда вставка символа, удаление символа и замена одного символа на другой стоят одинаково для любых символов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''int''' '''levensteinInstruction'''('''String''' s1, '''String''' s2, '''int''' InsertCost, '''int''' DeleteCost, '''int''' ReplaceCost):&lt;br /&gt;
   D[0][0] = 0&lt;br /&gt;
   '''fo'''r j = 1 '''to''' N&lt;br /&gt;
     D[0][j] = D[0][j - 1] + InsertCost                  &lt;br /&gt;
   '''for''' i = 1 '''to''' M&lt;br /&gt;
     D[i][0] = D[i - 1][0] + DeleteCost                  &lt;br /&gt;
     '''for''' j = 1 '''to''' N&lt;br /&gt;
       '''if''' S1[i] != S2[j] &lt;br /&gt;
          D[i][j] = min(D[i - 1][j] + DeleteCost,        &lt;br /&gt;
          D[i][j - 1] + InsertCost,                      &lt;br /&gt;
          D[i - 1][j - 1] + ReplaceCost)                 &lt;br /&gt;
       '''else''' &lt;br /&gt;
          D[i][j] = D[i - 1][j - 1]&lt;br /&gt;
   '''return''' D[M][N]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Память ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм в виде, описанном выше, требует &amp;lt;tex&amp;gt;\Theta(M \cdot N)&amp;lt;/tex&amp;gt; операций и такую же память, однако, если требуется только расстояние, легко уменьшить требуемую память до &amp;lt;tex&amp;gt;\Theta(N)&amp;lt;/tex&amp;gt;. Заметим, что для вычисления &amp;lt;tex&amp;gt;D[i]&amp;lt;/tex&amp;gt; нам нужно только &amp;lt;tex&amp;gt;D[i - 1]&amp;lt;/tex&amp;gt;, поэтому будем вычислять &amp;lt;tex&amp;gt;D[i]&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;D[1]&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;D[i - 1]&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;D[0]&amp;lt;/tex&amp;gt;. Осталось только поменять местами &amp;lt;tex&amp;gt;D[1]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;D[0]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''int''' '''levensteinInstruction'''('''int[]''' D):&lt;br /&gt;
   '''for''' i = 0 '''to''' M&lt;br /&gt;
     '''for''' j = 0 '''to''' N&lt;br /&gt;
       вычислить D[1][j]&lt;br /&gt;
     swap(D[0], D[1])&lt;br /&gt;
   '''return''' D[0][N]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Рекурсивный алгоритм ==&lt;br /&gt;
Для того, чтобы обеспечить время &amp;lt;tex&amp;gt;\Theta(M \cdot N)&amp;lt;/tex&amp;gt; при памяти &amp;lt;tex&amp;gt;\Theta(\min(M,N))&amp;lt;/tex&amp;gt;, определим матрицу &amp;lt;tex&amp;gt; E &amp;lt;/tex&amp;gt; минимальных расстояний между ''суффиксами'' строк, то есть &amp;lt;tex&amp;gt; E(i, j) &amp;lt;/tex&amp;gt; {{---}} расстояние между последними &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; символами &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; и последними &amp;lt;tex&amp;gt; j &amp;lt;/tex&amp;gt; символами &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;. Очевидно, матрицу &amp;lt;tex&amp;gt; E &amp;lt;/tex&amp;gt; можно вычислить аналогично матрице &amp;lt;tex&amp;gt; D &amp;lt;/tex&amp;gt;, и так же быстро.&lt;br /&gt;
&lt;br /&gt;
Теперь опишем алгоритм, считая, что &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; — кратчайшая из двух строк.&lt;br /&gt;
&lt;br /&gt;
* Если длина одной из строк (или обеих) не больше &amp;lt;tex&amp;gt; 1 &amp;lt;/tex&amp;gt;, задача тривиальна. Если нет, выполним следующие шаги.&lt;br /&gt;
* Разделим строку &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; на две подстроки длиной &amp;lt;tex&amp;gt;M/2&amp;lt;/tex&amp;gt;. (Если &amp;lt;tex&amp;gt;M&amp;lt;/tex&amp;gt; нечётно, то длины подстрок будут &amp;lt;tex&amp;gt;(M-1)/2&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;(M+1)/2&amp;lt;/tex&amp;gt;.) Обозначим подстроки &amp;lt;tex&amp;gt;S_1^-&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_1^+&amp;lt;/tex&amp;gt;.&lt;br /&gt;
* Для вычислим последнюю строку матрицы &amp;lt;tex&amp;gt; D &amp;lt;/tex&amp;gt; для строк &amp;lt;tex&amp;gt;S_1^-&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;, последнюю строку матрицы &amp;lt;tex&amp;gt; E &amp;lt;/tex&amp;gt; для строк &amp;lt;tex&amp;gt;S_1^+&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;.&lt;br /&gt;
* Найдём &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; такое, что &amp;lt;tex&amp;gt;D(|S_1^-|, i) + E(|S_1^+|, N-i)&amp;lt;/tex&amp;gt; минимально. Здесь &amp;lt;tex&amp;gt; D &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; E &amp;lt;/tex&amp;gt; {{---}} матрицы из предыдущего шага, но мы используем только их последние строки. Таким образом, мы нашли разбиение &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; на две подстроки, минимизирующее сумму расстояния левой половины &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; до левой части &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; и расстояния правой половины &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt; до правой части &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt;. Следовательно, левая подстрока &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; соответствует левой половине &amp;lt;tex&amp;gt;S_1&amp;lt;/tex&amp;gt;, а правая {{---}} правой.&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;tex&amp;gt;S_1^-&amp;lt;/tex&amp;gt; в левую часть &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; (то есть в подстроку &amp;lt;tex&amp;gt;S_2[1...i]&amp;lt;/tex&amp;gt;)&lt;br /&gt;
* Рекурсивно ищем редакционное предписание, превращающее &amp;lt;tex&amp;gt;S_1^+&amp;lt;/tex&amp;gt; в правую часть &amp;lt;tex&amp;gt;S_2&amp;lt;/tex&amp;gt; (то есть в подстроку &amp;lt;tex&amp;gt;S_2[i+1...N]&amp;lt;/tex&amp;gt;).&lt;br /&gt;
* Объединяем оба редакционных предписания.&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 '''int''' '''levensteinInstruction'''('''String''' s1, '''String''' s2):&lt;br /&gt;
    '''if'''  s1.length &amp;lt;=  1 || s2.length &amp;lt;= 1  &lt;br /&gt;
        Решаем тривиально, возвращаем редакционное предписание&lt;br /&gt;
    '''else'''    &lt;br /&gt;
        String s1l, s1r, s2l, s2r&lt;br /&gt;
        '''if'''  s2.length &amp;lt; s1.length &lt;br /&gt;
            s1l = s1.substring(0, s1.length / 2)           &amp;lt;font color=darkgreen&amp;gt; // S1-&amp;lt;/font color=darkgreen&amp;gt; &lt;br /&gt;
            s1r = s1.substring(s1.length / 2, s1.length)   &amp;lt;font color=darkgreen&amp;gt; // S1+&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
                                                           &amp;lt;font color=darkgreen&amp;gt; // d, e - массивы&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            d = '''calcD'''(s1l, s2)                             &amp;lt;font color=darkgreen&amp;gt; // Вычисляем последнюю строку матрицы D для S1- и S2&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            e = '''calcE'''(s1r, s2)                             &amp;lt;font color=darkgreen&amp;gt; // Вычисляем последнюю строку матрицы E для S1+ и S2&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            k = 0&lt;br /&gt;
            '''for''' i = 1 '''to''' s2.length&lt;br /&gt;
                '''if''' d[i] + e[s2.length - i] &amp;lt; d[k] + e[s2.length - k]&lt;br /&gt;
                    k = i&lt;br /&gt;
            s2l = s2.substring(0, k)&lt;br /&gt;
            s2r = s2.substring(k, s2.length)&lt;br /&gt;
        '''else'''&lt;br /&gt;
                                                           &amp;lt;font color=darkgreen&amp;gt; // s1 - меньшая строка&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            s2l = s2.substring(0, s2.length / 2)           &amp;lt;font color=darkgreen&amp;gt; // S2-&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            s2r = s2.substring(s2.length / 2, s2.length)   &amp;lt;font color=darkgreen&amp;gt; // S2+&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            d = '''calcD'''(s2l, s1)                             &amp;lt;font color=darkgreen&amp;gt; // Вычисляем последнюю строку матрицы D для S2- и S1&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            e = '''calcE'''(s2r, s1)                             &amp;lt;font color=darkgreen&amp;gt; // Вычисляем последнюю строку матрицы E для S2+ и S1&amp;lt;/font color=darkgreen&amp;gt;&lt;br /&gt;
            k = 0&lt;br /&gt;
            '''for''' i = 1 '''to''' s1.length&lt;br /&gt;
                '''if''' d[i] + e[s1.length - i] &amp;lt; d[k] + e[s1.length - k]&lt;br /&gt;
                    k = i&lt;br /&gt;
            s1l = s1.substring(0, k)&lt;br /&gt;
            s1r = s1.substring(k, s1.length)&lt;br /&gt;
    '''return''' '''levensteinInstruction'''(s1l, s2l) + '''levensteinInstruction'''(s1r, s2r)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Время выполнения удовлетворяет (с точностью до умножения на константу) условию&lt;br /&gt;
: &amp;lt;tex&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N'),\ 0\leqslant N'\leqslant N&amp;lt;/tex&amp;gt;,&lt;br /&gt;
Докажем:&lt;br /&gt;
: &amp;lt;tex&amp;gt;T(M,N) \leqslant 2MN&amp;lt;/tex&amp;gt;&lt;br /&gt;
База индукции очевидна&lt;br /&gt;
: &amp;lt;tex&amp;gt;T(1,N) = N \leqslant 2N&amp;lt;/tex&amp;gt;&lt;br /&gt;
Пусть для всех &amp;lt;tex&amp;gt;M' &amp;lt; M&amp;lt;/tex&amp;gt; выполнено &amp;lt;tex&amp;gt;T(M',N') \leqslant 2M'N'&amp;lt;/tex&amp;gt;.  &lt;br /&gt;
Тогда учитывая &amp;lt;tex&amp;gt;T(M/2,N') \leqslant 2(M/2)N'&amp;lt;/tex&amp;gt;,  &amp;lt;tex&amp;gt;T(M/2,N-N') \leqslant 2(M/2)(N-N')&amp;lt;/tex&amp;gt;, получим:&lt;br /&gt;
: &amp;lt;tex&amp;gt;T(M,N)=MN+T(M/2,N')+T(M/2,N-N') \leqslant&amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt; MN+2(M/2)N'+2(M/2)(N-N')=2MN&amp;lt;/tex&amp;gt;&lt;br /&gt;
следовательно&lt;br /&gt;
: &amp;lt;tex&amp;gt;T(M,N) = \Theta(M \cdot N)&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для вычисления последних строк матриц &amp;lt;tex&amp;gt; D, ~E &amp;lt;/tex&amp;gt; можно использовать два глобальных двумерных массива размера &amp;lt;tex&amp;gt;2 \times (\min(M, N)+1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Т.к. мы вычисляем функцию рекурсивно, требуемый размер стека тоже следует учесть. На стек вызовов потребуется &amp;lt;tex&amp;gt;\Theta(\log(\max(M,N))&amp;lt;/tex&amp;gt; памяти, потому общая оценка использования памяти будет &amp;lt;tex&amp;gt; \Theta(\min(M,N)) &amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Редакционное предписание ==&lt;br /&gt;
&lt;br /&gt;
'''Редакционное предписание''' (англ. ''Editorial prescription'') — последовательность действий, необходимых для получения из первой строки второй кратчайшим образом. Обычно действия обозначаются так: '''D''' (англ. delete) — удалить, '''I''' (англ. insert) — вставить, '''R''' (англ. replace) — заменить, '''M''' (англ. match) — совпадение.&lt;br /&gt;
&lt;br /&gt;
Например, для 2-х строк «hell123» и «hello214» можно построить следующую таблицу преобразований:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!'''M''' ||'''M''' ||'''M''' ||'''M''' ||'''R''' ||'''M''' ||'''R''' ||'''I'''&lt;br /&gt;
|-&lt;br /&gt;
|h ||e ||l ||l ||1 ||2||3 ||&lt;br /&gt;
|-&lt;br /&gt;
|h ||e||l ||l ||o ||2 ||1 ||4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==См. также==&lt;br /&gt;
*[[Задача о расстоянии Дамерау-Левенштейна]]&lt;br /&gt;
&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
*[http://en.wikipedia.org/wiki/Levenshtein_distance Wikipedia {{---}} Levenshtein distance]&lt;br /&gt;
&lt;br /&gt;
*[http://pastie.org/5574488 Реализация рекурсивного алгоритма поиска редакционного предписания на языке Java]&lt;br /&gt;
&lt;br /&gt;
*Романовский И.В. &amp;quot;Дискретный анализ&amp;quot;. Третье издание. Стр. 103 - 105&lt;br /&gt;
&lt;br /&gt;
[[Категория:Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория:Динамическое программирование]]&lt;/div&gt;</summary>
		<author><name>37.57.17.214</name></author>	</entry>

	</feed>