<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://apmath.info/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=%D0%A1%D0%92</id>
	<title>Викиконспекты ПМ-ПУ - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://apmath.info/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=%D0%A1%D0%92"/>
	<link rel="alternate" type="text/html" href="https://apmath.info/wiki/%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/%D0%A1%D0%92"/>
	<updated>2026-11-05T12:58:35Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.36.2</generator>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%95%D0%B2%D0%BA%D0%BB%D0%B8%D0%B4%D0%BE%D0%B2%D1%8B,_%D0%BD%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5_%D0%B8_%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B0&amp;diff=973</id>
		<title>Евклидовы, нормированные и метрические пространства</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%95%D0%B2%D0%BA%D0%BB%D0%B8%D0%B4%D0%BE%D0%B2%D1%8B,_%D0%BD%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5_%D0%B8_%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B0&amp;diff=973"/>
		<updated>2026-11-04T20:51:41Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «{{Определение |definition= Линейное n-мерное пространство называется евклидовым векторным n-ме...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Линейное n-мерное пространство называется евклидовым векторным n-мерным пространством, если в нем задана функция, сопоставляющая двум любым элементам $a$ и $b$ этого пространства вещественное число, называемое скалярным произведением и обозначаемое $a\cdot b$, причем выполняются аксиомы:&lt;br /&gt;
&lt;br /&gt;
12. $a\cdot b=b\cdot a$;&lt;br /&gt;
&lt;br /&gt;
13. $\lambda (a\cdot b)=(\lambda a)\cdot b$;&lt;br /&gt;
&lt;br /&gt;
14. $(a+b)\cdot c=a\cdot c+b\cdot c$;&lt;br /&gt;
&lt;br /&gt;
15. $a\cdot a &amp;gt; 0$, если $a\ne 0$, $a\cdot a=0$, если $a=0$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Рассмотренные ранее линейные векторные пространства $V^{1}$, $V^{2}$, $V^{3}$ со скалярным произведением, определяемым по формуле &amp;lt;math&amp;gt;\vec{a}\cdot \vec{b}=|\vec{a}||\vec{b}|\cos \varphi&amp;lt;/math&amp;gt;, являются евклидовыми векторными пространствами соответственно размерностей 1, 2, 3.&lt;br /&gt;
&lt;br /&gt;
Векторное пространство $R^{n}$ будет евклидовым n-мерным пространством, если ввести в нем скалярное произведение для его любых двух элементов $a(\alpha_{1}, \alpha_{2}, ..., \alpha_{n} )$ и $b(\beta_{1}, \beta_{2}, ..., \beta_{n} )$ по формуле $a\cdot b=\sum_{i=1}^{n}\alpha_{i} \beta_{i} $.&lt;br /&gt;
&lt;br /&gt;
Евклидово пространство еще называют конечным гильбертовым пространством.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Два ненулевых элемента $a$ и $b$ евклидова пространства называются ортогональными, если равно нулю их скалярное произведение.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Заметим, что для геометрических векторов $\vec{a}\cdot \vec{b}=0$ тогда и только тогда, когда $\vec{a}\bot \vec{b}$, поэтому понятие ортогональности в пространствах $V^{2} $ и $V^{3} $ эквивалентно понятию перпендикулярности.&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Для любых двух элементов $a$ и $b$ произвольного евклидова пространства верно неравенство  &amp;lt;math&amp;gt;(a\cdot b)^{2} \le (a\cdot a)(b\cdot b), &amp;lt;/math&amp;gt; называемое неравенством Коши-Буняковского.&lt;br /&gt;
|proof=На основании аксиомы 15 имеем неравенство $(\lambda a-b)(\lambda a-b)\ge 0$, которое можно представить в виде&lt;br /&gt;
&amp;lt;math&amp;gt;\lambda ^{2} (a\cdot a)-2\lambda (a\cdot b)+(b\cdot b)\ge 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Так как $a\cdot a &amp;gt; 0$, то необходимым и достаточным условием неотрицательности квадратного трехчлена относительно $\lambda$ является неположительность его дискриминанта, то есть $(a\cdot b)^{2} \le (a\cdot a)(b\cdot b)$. Теорема доказана.&lt;br /&gt;
}}&lt;br /&gt;
Из неравенства Коши-Буняковского следует, что $-1\le \frac{(a\cdot b)}{\sqrt{a\cdot a} \cdot \sqrt{b\cdot b} } \le 1$. Следовательно, в любом евклидовом векторном пространстве можно определить &lt;br /&gt;
&amp;lt;math&amp;gt;\left|a\right|=\sqrt{a\cdot a}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\cos \varphi =\frac{a\cdot b}{\left|a\right|\cdot \left|b\right|}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\varphi =\arccos \frac{a\cdot b}{\left|a\right|\cdot \left|b\right|}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В евклидовом точечном пространстве (то есть в аффинном пространстве, в котором присоединенный линеал является евклидовым векторным пространством) можно находить расстояние между точками &amp;lt;math&amp;gt;d(A,B)=\left|\overrightarrow{AB} \right|=\sqrt{\overrightarrow{AB} \cdot \overrightarrow{AB} }&amp;lt;/math&amp;gt;.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Линейное n-мерное пространство $L$ называется нормированным, если имеется правило, посредством которого всякому элементу $x\in L$ ставится в соответствие вещественное число, называемое нормой указанного элемента и обозначаемое символом &amp;lt;math&amp;gt;\left\| x\right\|&amp;lt;/math&amp;gt;, причем должны выполняться соотношения:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;\left\| x\right\| \ge 0&amp;lt;/math&amp;gt;, если $x\ne 0$, &amp;lt;math&amp;gt;\left\| x\right\| =0&amp;lt;/math&amp;gt;, если $x=0$;&lt;br /&gt;
* &amp;lt;math&amp;gt;\left\| \lambda x\right\| =\left|\lambda \right|\cdot \left\| x\right\|&amp;lt;/math&amp;gt;  для любого $x\in L$ и произвольного вещественного числа $\lambda $;&lt;br /&gt;
* &amp;lt;math&amp;gt;\left\| x+y\right\| \le \left\| x\right\| +\left\| y\right\|&amp;lt;/math&amp;gt;  для любых $x,y\in L$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-17-02-triangle-inequality.svg|frame|none|800px|alt=Слева векторы x и y под углом друг к другу и их сумма: длина суммы меньше суммы длин. Справа сонаправленные векторы: длина суммы равна сумме длин.|Рис. 1. Неравенство &amp;lt;math&amp;gt;|x+y| \le |x|+|y|&amp;lt;/math&amp;gt;: при несонаправленных векторах оно строгое, при сонаправленных превращается в равенство. Доказательство опирается на неравенство Коши — Буняковского: &amp;lt;math&amp;gt;x\cdot y\le|x| |y|&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Всякое евклидово пространство будет нормированным, если норму в нем определить равенством&lt;br /&gt;
&amp;lt;math&amp;gt;\left\| x\right\| =\sqrt{x\cdot x}&amp;lt;/math&amp;gt;.&lt;br /&gt;
|proof=Справедливость для нормы первых двух условий вытекает из соответствующих аксиом для скалярного произведения. Опираясь на неравенство Коши-Буняковского, убедимся в справедливости последнего неравенства. Действительно, &amp;lt;math&amp;gt;\left|x\cdot y\right|\le \sqrt{x\cdot x} \cdot \sqrt{y\cdot y}&amp;lt;/math&amp;gt;. Тогда&lt;br /&gt;
&amp;lt;math&amp;gt;\left\| x+y\right\| =\sqrt{(x+y)\cdot (x+y)} =\sqrt{(x\cdot x)+2(x\cdot y)+(y\cdot y)} \le \sqrt{(x\cdot x)+2\sqrt{x\cdot x} \sqrt{y\cdot y} +(y\cdot y)} =\sqrt{(\sqrt{x\cdot x} +\sqrt{y\cdot y} )^{2} } =\left\| x\right\| +\left\| y\right\|&amp;lt;/math&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
Сосредоточив внимание на линейных операциях в множестве, мы пришли к понятию линейного (векторного) пространства, а потом аффинного (точечного) пространства. Введя скалярное произведение в ассоциированном векторном пространстве, мы пришли к евклидову точечному пространству. Обратим теперь внимание на одно лишь свойство точечного множества {{---}} на наличие расстояния в нем. Это приводит нас к понятию метрического пространства.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Множество называется метрическим пространством, если каждой паре его элементов сопоставляется по некоторому правилу неотрицательное вещественное число, называемое расстоянием, причем справедливы соотношения:&lt;br /&gt;
&lt;br /&gt;
* $\rho (x,y) &amp;gt; 0$, если $x\ne y$; $\rho (x,y)=0$, если $x=y$;&lt;br /&gt;
* $\rho (x,y)=\rho (y,x)$;&lt;br /&gt;
* $\rho (x,y)\le \rho (x,z)+\rho (z,y)$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Отметим, что нормированное пространство можно превратить в метрическое, если положить &amp;lt;math&amp;gt;\rho (x,y)=\left\| x - y \right\|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Линейное метрическое пространство легко превратить в нормированное, если положить &amp;lt;math&amp;gt;\left\| x \right\| =\rho (x,0)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-17-01-space-hierarchy.svg|frame|none|800px|alt=Пять блоков сверху вниз: линейное, аффинное, евклидово, нормированное и метрическое пространства, с указанием того, что добавляется или остаётся на каждом уровне.|Рис. 2. Пять уровней структуры: линейное пространство, аффинное, евклидово со скалярным произведением, нормированное с нормой &amp;lt;math&amp;gt;|x|=\sqrt{x\cdot x}&amp;lt;/math&amp;gt; и метрическое с расстоянием &amp;lt;math&amp;gt;\rho(x,y)=|x-y|&amp;lt;/math&amp;gt;. Сверху вниз структура только теряется, поэтому обратные переходы возможны не всегда.]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-17-01-space-hierarchy.svg&amp;diff=972</id>
		<title>Файл:P03-17-01-space-hierarchy.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-17-01-space-hierarchy.svg&amp;diff=972"/>
		<updated>2026-11-04T20:49:09Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-17-02-triangle-inequality.svg&amp;diff=971</id>
		<title>Файл:P03-17-02-triangle-inequality.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-17-02-triangle-inequality.svg&amp;diff=971"/>
		<updated>2026-11-04T20:48:55Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A1%D0%BA%D0%B0%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2&amp;diff=970</id>
		<title>Скалярное произведение векторов</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A1%D0%BA%D0%B0%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2&amp;diff=970"/>
		<updated>2026-11-04T19:02:13Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Углом между векторами $\vec{a}$ и $\vec{b}$ называется наименьший угол между векторами $\vec{a'}$ и $\vec{b'}$, равными соответственно $\vec{a}$ и $\vec{b}$ и имеющими общее начало. &lt;br /&gt;
}}&lt;br /&gt;
Если ненулевые векторы сонаправлены, то угол между ними полагают равным $0$, если ненулевые векторы противоположно направлены, то угол между ними считают равным $\pi$.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Скалярным произведением $\vec{a}\cdot \vec{b}$ двух векторов $\vec{a}$ и $\vec{b}$ называется произведение их модулей и косинуса угла между ними, то есть&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\vec{a}\cdot \vec{b}= |\vec{a}|\cdot |\vec{b}|\cos \varphi . &lt;br /&gt;
&amp;lt;/math&amp;gt; &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Непосредственно из определения следует, что &amp;lt;math&amp;gt;\vec{a}\cdot \vec{a}= |\vec{a}|^{2}&amp;lt;/math&amp;gt;, поэтому &amp;lt;math&amp;gt;\left|\vec{a}\right|=\sqrt{\vec{a}\cdot \vec{a} }&amp;lt;/math&amp;gt;, причем &amp;lt;math&amp;gt;\vec{a}\cdot \vec{a}&amp;gt;0&amp;lt;/math&amp;gt;, если &amp;lt;math&amp;gt;\vec{a} \ne \vec{0}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Число $\vec{a}\cdot \vec{a}=\vec{a}^{2} $ называется скалярным квадратом вектора $\vec{a}$.&lt;br /&gt;
&lt;br /&gt;
Пусть орт вектора $\vec{a}$ равен &amp;lt;math&amp;gt;\vec{g}=\frac{\vec{a} }{|\vec{a}|}&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;\vec{q}=\frac{\vec{b} }{|\vec{b}|}&amp;lt;/math&amp;gt; {{---}} орт вектора $\vec{b}$. Тогда в соответствии с теоремой имеем &amp;lt;math&amp;gt;Apr_{\vec{g} } \vec{b}= |\vec{b}|\cos \varphi&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Apr_{\vec{q} } \vec{a}= |\vec{a}|\cos \varphi&amp;lt;/math&amp;gt;, а в силу определения скалярного произведения получаем&lt;br /&gt;
&amp;lt;math&amp;gt; \vec{a}\cdot \vec{b}= |\vec{a}|Apr_{\vec{g} } \vec{b} = |\vec{b}| Apr_{\vec{q} } \vec{a} &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-16-01-dot-product-sign.svg|thumb|800px|alt=Три панели с векторами a и b из общего начала: острый угол — проекция положительна, прямой — равна нулю, тупой — отрицательна.|Рис. 1. Знак &amp;lt;math&amp;gt;\vec a\cdot\vec b&amp;lt;/math&amp;gt; определяется углом: при остром угле проекция &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; положительна, при прямом равна нулю, при тупом направлена против &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; и произведение отрицательно. Везде &amp;lt;math&amp;gt;\vec a\cdot\vec b=|\vec a| |\vec b|\cos\varphi&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Понятие скалярного произведения векторов впервые появились в механике. Если вектор $\vec{a}$ изображает силу, точка приложения которой перемещается из начала в конец вектора $\vec{b}$, то работа A указанной силы определяется равенством $A= |\vec{a}|\cdot |\vec{b}|\cos \varphi$.&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|about=свойства скалярного произведения&lt;br /&gt;
|statement=Для любых трех векторов $\vec{a}, \vec{b}, \vec{c}$ и произвольного числа $\lambda$ справедливы следующие свойства:&lt;br /&gt;
&lt;br /&gt;
* $\vec{a}\cdot \vec{b}=\vec{b}\cdot \vec{a}$ (коммутативность);&lt;br /&gt;
* $\lambda (\vec{a}\cdot \vec{b})=(\lambda \vec{a})\cdot \vec{b}$ (ассоциативность относительно числового множителя);&lt;br /&gt;
* $(\vec{a}+\vec{b})\cdot \vec{c}=\vec{a}\cdot \vec{c}+\vec{b}\cdot \vec{c}$ (дистрибутивность относительно суммы векторов);&lt;br /&gt;
* $\vec{a}\cdot \vec{a}&amp;gt;0$, если $\vec{a}$ {{---}} ненулевой вектор; $\vec{a}\cdot \vec{a}=0$, если $\vec{a}$ {{---}} нулевой вектор.&lt;br /&gt;
&lt;br /&gt;
|proof= Первое свойство непосредственно вытекает из определения скалярного произведения. Для второго и третьего свойств имеем&lt;br /&gt;
&amp;lt;math&amp;gt;(\lambda \vec{a})\cdot \vec{b}= |\vec{b}|Apr_{\vec{q} } (\lambda \vec{a})=\lambda |\vec{b}|Apr_{\vec{q} } (\vec{a})=\lambda (\vec{a}\cdot \vec{b}),&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;(\vec{a}+\vec{b})\cdot \vec{c}= |\vec{c}|Apr_{\vec{e} } (\vec{a}+\vec{b})= |\vec{c}|Apr_{\vec{e} } \vec{a}+ |\vec{c}|Apr_{\vec{e} } \vec{b}=\vec{a}\cdot \vec{c}+\vec{b}\cdot \vec{c},&amp;lt;/math&amp;gt;&lt;br /&gt;
где орт &amp;lt;math&amp;gt;\vec{e}=\frac{\vec{c} }{|\vec{c}|}&amp;lt;/math&amp;gt;. Наконец, четвертое свойство вытекает непосредственно из определения.&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Скалярное произведение двух векторов, заданных в произвольной аффинной системе координат $O\vec{e}_{1} \vec{e}_{2} \vec{e}_{3} $ своими координатами $\vec{a}(\alpha_{1}, \alpha_{2}, \alpha_{3} )$ и $\vec{b}(\beta_{1}, \beta_{2}, \beta_{3} )$, находится по формуле&lt;br /&gt;
&amp;lt;math&amp;gt;\vec{a}\cdot \vec{b}=\alpha_{1} \beta_{1} (\vec{e}_{1} \cdot \vec{e}_{1} )+(\alpha_{1} \beta_{2} +\alpha_{2} \beta_{1} )(\vec{e}_{1} \cdot \vec{e}_{2} )+\alpha_{2} \beta_{2} (\vec{e}_{2} \cdot \vec{e}_{2} )+(\alpha_{1} \beta_{3} +\alpha_{3} \beta_{1} )(\vec{e}_{1} \cdot \vec{e}_{3} )+(\alpha_{2} \beta_{3} +\alpha_{3} \beta_{2} )(\vec{e}_{2} \cdot \vec{e}_{3} )+\alpha_{3} \beta_{3} (\vec{e}_{3} \cdot \vec{e}_{3} ).&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|proof= Формула получается с помощью простых вычислений, если воспользоваться представлениями заданных векторов через базис $\vec{a}=\alpha_{1} \vec{e}_{1} +\alpha_{2} \vec{e}_{2} +\alpha_{3} \vec{e}_{3}$, $\vec{b}=\beta_{1} \vec{e}_{1} +\beta_{2} \vec{e}_{2} +\beta_{3} \vec{e}_{3} $ и свойствами скалярного произведения.&lt;br /&gt;
}}&lt;br /&gt;
Величины $g_{ij} $=$\vec{e}_{i} \cdot \vec{e}_{j} $ называются метрическими коэффициентами системы координат $O\vec{e}_{1} \vec{e}_{2} \vec{e}_{3} $. Таким образом, для вычисления скалярного произведения двух векторов в аффинной системе координат необходимо знать координаты этих векторов и метрические коэффициенты системы координат.&lt;br /&gt;
&lt;br /&gt;
Модуль произвольного вектора $\vec{a}(\alpha_{1}, \alpha_{2}, \alpha_{3} )$ в произвольной аффинной системе координат $O\vec{e}_{1} \vec{e}_{2} \vec{e}_{3} $ вычисляется по формуле&lt;br /&gt;
&amp;lt;math&amp;gt;|\vec{a}|=\sqrt{\vec{a}\cdot \vec{a} } =\sqrt{\alpha_{1}^{2} g_{11} +\alpha_{2}^{2} g_{22} +\alpha_{3}^{2} g_{33} +2\alpha_{1} \alpha_{2} g_{12} +2\alpha_{1} \alpha_{3} g_{13} +2\alpha_{2} \alpha_{3} g_{23} }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Угол $\varphi$ между двумя векторами $\vec{a}(\alpha_{1}, \alpha_{2}, \alpha_{3} )$ и $\vec{b}(\beta_{1}, \beta_{2}, \beta_{3} )$ в аффинной системе координат $O\vec{e}_{1} \vec{e}_{2} \vec{e}_{3} $ находится по формуле&lt;br /&gt;
&amp;lt;math&amp;gt;\varphi = \arccos \frac{\vec{a}\cdot \vec{b} }{|\vec{a}|\cdot |\vec{b}|}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для векторов, заданных своими координатами в ДПСК, справедливы формулы &lt;br /&gt;
&lt;br /&gt;
* $n=3$: $\vec{a}=\alpha_{x} \vec{i}+\alpha_{y} \vec{j}+\alpha_{z} \vec{k}$, $\vec{b}=\beta_{x} \vec{i}+\beta_{y} \vec{j}+\beta_{z} \vec{k}$, $\vec{a}\cdot \vec{b}=\alpha_{x} \beta_{x} +\alpha_{y} \beta_{y} +\alpha_{z} \beta_{z}$, &amp;lt;math&amp;gt;|\vec{a}|$=$\sqrt{\alpha_{x}^{2} +\alpha_{y}^{2} +\alpha_{z}^{2} }&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\varphi=\angle (\vec{a},\vec{b})=\arccos \frac{\alpha_{x} \beta_{x} +\alpha_{y} \beta_{y} +\alpha_{z} \beta_{z} }{\sqrt{\alpha_{x}^{2} +\alpha_{y}^{2} +\alpha_{z}^{2} } \cdot \sqrt{\beta_{x}^{2} +\beta_{y}^{2} +\beta_{z}^{2} } } &amp;lt;/math&amp;gt;;&lt;br /&gt;
* $n=2$: $\vec{a}=\alpha_{x} \vec{i}+\alpha_{y} \vec{j}$, $\vec{b}=\beta_{x} \vec{i}+\beta_{y} \vec{j}$, &lt;br /&gt;
$\vec{a}\cdot \vec{b}=\alpha_{x} \beta_{x} +\alpha_{y} \beta_{y}, |\vec{a}|=\sqrt{\alpha_{x}^{2} +\alpha_{y}^{2} }$,  &lt;br /&gt;
$\varphi =\angle (\vec{a},\vec{b})=\arccos \frac{\alpha_{x} \beta_{x} +\alpha_{y} \beta_{y} }{\sqrt{\alpha_{x}^{2} +\alpha_{y}^{2} } \cdot \sqrt{\beta_{x}^{2} +\beta_{y}^{2} } } $;&lt;br /&gt;
* $n=1$: $\vec{a}=\alpha_{x} \vec{i}, \vec{b}=\beta_{x} \vec{i},$ &amp;lt;math&amp;gt;\vec{a}\cdot \vec{b}=\alpha_{x} \beta_{x}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|\vec{a}| = |\alpha_{x} |, \varphi =\angle (\vec{a},\vec{b})=\arccos \frac{\alpha_{x} \beta_{x} }{|\alpha_{x} ||\beta_{x} |}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Поскольку расстояние между двумя точками $A(x_{A}, y_{A}, z_{A} )$ и $B(x_{B}, y_{B}, z_{B} )$ в трехмерном пространстве равно модулю вектора $\overrightarrow{AB} (x_{B} -x_{A}, y_{B} -y_{A}, z_{B} -z_{A} )$, то получаем следующую формулу для нахождения расстояния между названными точками в заданной ДПСК:&lt;br /&gt;
&amp;lt;math&amp;gt;d(A,B)=\sqrt{(x_{B} -x_{A} )^{2} +(y_{B} -y_{A} )^{2} +(z_{B} -z_{A} )^{2} }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Аналогично расстояние между двумя точками $A(x_{A}, y_{A} )$ и $B(x_{B}, y_{B})$ на плоскости находятся по формуле&lt;br /&gt;
&amp;lt;math&amp;gt;d(A,B)=\sqrt{(x_{B} -x_{A} )^{2} +(y_{B} -y_{A} )^{2} }&amp;lt;/math&amp;gt;,&lt;br /&gt;
а расстояние между двумя точками $A(x_{A} )$ и $B(x_{B} )$ на прямой соответственно по формуле&lt;br /&gt;
&amp;lt;math&amp;gt;d(A,B)=|x_{B} -x_{A} |&amp;lt;/math&amp;gt;.&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Необходимым и достаточным условием перпендикулярности двух ненулевых векторов в трехмерном пространстве $\vec{a}(\alpha_{x}, \alpha_{y}, \alpha_{z} )$ и $\vec{b}(\beta_{x}, \beta_{y}, \beta_{z} )$, заданных в ДПСК своими координатами, является равенство&lt;br /&gt;
&amp;lt;math&amp;gt;\label{GrindEQ__3_16_14_}&lt;br /&gt;
\alpha_{x} \beta_{x} +\alpha_{y} \beta_{y} +\alpha_{z} \beta_{z} = 0. &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Доказательство непосредственно следует из определения скалярного произведения.&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Декартовы прямоугольные координаты вектора $\vec{a}(\alpha_{x}, \alpha_{y}, \alpha_{z} )$ равны скалярным произведениям этого вектора на соответствующие базисные векторы, то есть&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\alpha_{x} =\vec{a}\cdot \vec{i}, \alpha_{y} =\vec{a}\cdot \vec{j}, \alpha_{z} =\vec{a}\cdot \vec{k}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
|proof=Пусть $\vec{i}$,$\vec{j}$,$\vec{k}$ {{---}} ортонормированный базис. Тогда $\vec{a}=\alpha_{x} \vec{i}+\alpha_{y} \vec{j}+\alpha_{z} \vec{k}$, причем&lt;br /&gt;
&amp;lt;math&amp;gt;\vec{a}\cdot \vec{i}=\alpha_{x} \vec{i}\cdot \vec{i}+\alpha_{y} \vec{j}\cdot \vec{i}+\alpha_{z} \vec{k}\cdot \vec{i}=\alpha_{x}&amp;lt;/math&amp;gt;;&lt;br /&gt;
&amp;lt;math&amp;gt;\vec{a}\cdot \vec{j}=\alpha_{x} \vec{i}\cdot \vec{j}+\alpha_{y} \vec{j}\cdot \vec{j}+\alpha_{z} \vec{k}\cdot \vec{j}=\alpha_{y}&amp;lt;/math&amp;gt;; &lt;br /&gt;
&amp;lt;math&amp;gt;\vec{a}\cdot \vec{k}=\alpha_{x} \vec{i}\cdot \vec{k}+\alpha_{y} \vec{j}\cdot \vec{k}+\alpha_{z} \vec{k}\cdot \vec{k}=\alpha_{z}&amp;lt;/math&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-16-02-dot-product-coordinates.svg|thumb|800px|alt=Вектор a в декартовой системе с координатным параллелепипедом: рёбра вдоль осей равны αx, αy, αz. Справа формулы для скалярного произведения, модуля и условия перпендикулярности.|Рис. 2. В ДПСК координаты вектора — это его проекции на орты, поэтому &amp;lt;math&amp;gt;\alpha_x=\vec a\cdot\vec i&amp;lt;/math&amp;gt; и т. д. Отсюда &amp;lt;math&amp;gt;\vec a\cdot\vec b=\alpha_x\beta_x+\alpha_y\beta_y+\alpha_z\beta_z&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|\vec a|=\sqrt{\vec a\cdot\vec a}&amp;lt;/math&amp;gt;, а перпендикулярность означает &amp;lt;math&amp;gt;\vec a\cdot\vec b=0&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим в ДПСК $O\vec{i}  \vec{j}\vec{k}$ вектор $\overrightarrow{OM}$, приведенный в начало $O$, и его орт $\vec{e}$. Пусть $\alpha =\angle (\vec{e},\vec{i}), \beta =\angle (\vec{e},\vec{j}), \gamma =\angle (\vec{e},\vec{k})$.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Косинусы $\cos \alpha$, $\cos \beta$, $\cos \gamma$ называются направляющими косинусами вектора $\overrightarrow{OM} $.&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Направляющие косинусы вектора являются декартовыми координатами орта данного вектора.&lt;br /&gt;
|proof= Пусть $\vec{e}$ {{---}} орт вектора $\overrightarrow{OM}$, причем $\vec{e}=e_{x} \vec{i}+e_{y} \vec{j}+e_{z} \vec{k}$. Тогда в силу теоремы 3.16.4 и определения скалярного произведения имеем &lt;br /&gt;
&amp;lt;math&amp;gt; e_{x} = \vec{e}\cdot \vec{i} = |\vec{e}||\vec{i}|\cos \alpha = \cos \alpha,&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; e_{y} = \vec{e}\cdot \vec{j} = |\vec{e}||\vec{j}|\cos \beta  = \cos \beta,&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; e_{z} = \vec{e}\cdot \vec{k} = |\vec{e}||\vec{k}|\cos \gamma = \cos \gamma,&amp;lt;/math&amp;gt;&lt;br /&gt;
что и требовалось доказать.&lt;br /&gt;
}}&lt;br /&gt;
Для направляющих косинусов справедливо соотношение&lt;br /&gt;
&amp;lt;math&amp;gt;\cos ^{2} \alpha +\cos ^{2} \beta +\cos ^{2} \gamma =1&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-16-01-dot-product-sign.svg&amp;diff=969</id>
		<title>Файл:P03-16-01-dot-product-sign.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-16-01-dot-product-sign.svg&amp;diff=969"/>
		<updated>2026-11-04T18:52:08Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-16-02-dot-product-coordinates.svg&amp;diff=968</id>
		<title>Файл:P03-16-02-dot-product-coordinates.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-16-02-dot-product-coordinates.svg&amp;diff=968"/>
		<updated>2026-11-04T18:51:59Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%93%D0%B5%D0%BE%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%BC%D1%8B%D1%81%D0%BB_%D0%B0%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D1%85_%D0%BA%D0%BE%D0%BE%D1%80%D0%B4%D0%B8%D0%BD%D0%B0%D1%82&amp;diff=967</id>
		<title>Геометрический смысл аффинных координат</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%93%D0%B5%D0%BE%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%BC%D1%8B%D1%81%D0%BB_%D0%B0%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D1%85_%D0%BA%D0%BE%D0%BE%D1%80%D0%B4%D0%B8%D0%BD%D0%B0%D1%82&amp;diff=967"/>
		<updated>2026-11-04T18:45:03Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «Рассмотрим прямую $O\vec{e}_{1} $ и вектор $\overrightarrow{OM} =\vec{a}$, коллинеарный вектору $\vec{e}_{1} $. Тогд...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Рассмотрим прямую $O\vec{e}_{1} $ и вектор $\overrightarrow{OM} =\vec{a}$, коллинеарный вектору $\vec{e}_{1} $. Тогда, $\vec{a}=\lambda \vec{e}_{1}$, причем $\lambda =\frac{|\vec{a}|}{|\vec{e}_{1} |}$, если $\vec{a}\uparrow \uparrow \vec{e}_{1}$, и $\lambda =-\frac{|\vec{a}|}{|\vec{e}_{1} |}$, если $\vec{a}\uparrow \downarrow \vec{e}_{1} $.&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Указанное число $\lambda$ называется алгебраической мерой вектора $\vec{a}$ на оси, определяемой вектором $\vec{e}_{1}$, и обозначается $\lambda =mes_{\vec{e}_{1} } \vec{a}$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-10-01-algebraic-measure.svg|thumb|800px|alt=Ось с единичным вектором e; над ней a = 2e, b = −1,5e и a + b = 0,5e, под ней шкала от −2 до 3|Рис. 1. Мера вектора на оси — коэффициент &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; в равенстве &amp;lt;math&amp;gt;a = \lambda e&amp;lt;/math&amp;gt;. Поэтому у &amp;lt;math&amp;gt;a = 2e&amp;lt;/math&amp;gt; мера равна 2, у &amp;lt;math&amp;gt;b = -1{,}5e&amp;lt;/math&amp;gt; равна −1,5, а у суммы &amp;lt;math&amp;gt;a + b = 0{,}5e&amp;lt;/math&amp;gt; — 0,5: мера линейна.]]&lt;br /&gt;
&lt;br /&gt;
С учетом введенного обозначения, очевидно, что $\vec{a}=\left(mes_{\vec{e}_{1} } \vec{a}\right)\vec{e}_{1} $.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=Для любых трех коллинеарных векторов $\vec{a}, \vec{b}, \vec{e}$ и произвольного числа $\lambda$ справедливы соотношения $mes_{\vec{e} } \left(\vec{a}+\vec{b}\right)=mes_{\vec{e} } \vec{a}+mes_{\vec{e} } \vec{b}, mes_{\vec{e} } \left(\lambda \vec{a}\right)=\lambda mes_{\vec{e} } \vec{a}$.&lt;br /&gt;
|proof= Пусть $\lambda \vec{a}\uparrow \uparrow \vec{e}$, тогда по определению алгебраической меры вектора&lt;br /&gt;
$ mes_{\vec{e} } \left(\lambda \vec{a}\right) = \frac{|\lambda \vec{a}|}{|\vec{e}|} = |\lambda |\frac{|\vec{a}|}{|\vec{e}|} =&lt;br /&gt;
 \left[ \begin{array}{l} &lt;br /&gt;
  {\lambda \frac{|\vec{a}|}{|\vec{e}|} =\lambda mes_{\vec{e} } \vec{a}, \lambda &amp;gt; 0;} \\ &lt;br /&gt;
  {-\lambda \frac{|\vec{a}|}{|\vec{e}|} =\lambda \left(-\frac{|\vec{a}|}{|\vec{e}|} \right) = \lambda mes_{\vec{e} } \vec{a}, \lambda &amp;lt; 0.} &lt;br /&gt;
 \end{array} \right. &lt;br /&gt;
$&lt;br /&gt;
&lt;br /&gt;
Если $\lambda \vec{a}\uparrow \downarrow \vec{e}$, то &lt;br /&gt;
$mes_{\vec{e} } \left(\lambda \vec{a}\right)=-\frac{|\lambda \vec{a}|}{|\vec{e}|} = - |\lambda |\frac{|\vec{a}|}{|\vec{e}|} =\left[\begin{array}{l} {-\lambda \frac{|\vec{a}|}{|\vec{e}|} =\lambda \left(-\frac{|\vec{a}|}{|\vec{e}|} \right)=\lambda mes_{\vec{e} } \vec{a},  \lambda &amp;gt; 0;} \\ {\lambda \frac{|\vec{a}|}{|\vec{e}|} =\lambda mes_{\vec{e} } \vec{a}, \lambda &amp;lt; 0.} \end{array}\right. $&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь алгебраическую меру суммы векторов. Так как по условию векторы $\vec{a}, \vec{b}$ являются коллинеарными, то будем считать, что $\vec{b}=\mu \vec{a}$. Тогда&lt;br /&gt;
$mes_{\vec{e} } \left(\vec{a}+\vec{b}\right)=mes_{\vec{e} } \left(\vec{a}+\mu \vec{a}\right)=mes_{\vec{e} } \left(1+\mu \right)\vec{a}=\left(1+\mu \right)mes_{\vec{e} } \vec{a}=mes_{\vec{e} } \vec{a}+\mu mes_{\vec{e} } \vec{a} = mes_{\vec{e} } \vec{a}+mes_{\vec{e} } \vec{b}$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Таким образом, можно утверждать, что аффинная координата произвольной точки $M$ на координатной оси равна алгебраической мере ее радиус-вектора $\overrightarrow{OM} $.&lt;br /&gt;
&lt;br /&gt;
Пусть задана аффинная система координат $O\vec{e}_{1} \vec{e}_{2} $ и произвольный вектор $\vec{a}= \overrightarrow{NM} $. Спроектируем начало и конец данного вектора на координатные оси, получаем соответственно $\overrightarrow{N_{1} M_{1} } $ и $\overrightarrow{N_{2} M_{2} } $.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Вектор $\overrightarrow{N_{1} M_{1} }$ ($\overrightarrow{N_{2} M_{2} }$) называется геометрической проекцией вектора $\vec{a}$ на координатную ось $O\vec{e}_{1} $ ($O\vec{e}_{2} $).&lt;br /&gt;
}}&lt;br /&gt;
Для геометрической проекции вектора на координатные оси будем использовать обозначение $\overrightarrow{N_{1} M_{1} } =\mbox{Proj}_{\vec{e}_{1} } \vec{a}$, $\overrightarrow{N_{2} M_{2} } =\mbox{Proj}_{\vec{e}_{2} } \vec{a}$.&lt;br /&gt;
&lt;br /&gt;
В соответствии с правилом параллелограмма $\overrightarrow{NM} =\overrightarrow{N_{1} M_{1} } +\overrightarrow{N_{2} M_{2} }$, то есть &lt;br /&gt;
$\vec{a}=\mbox{Proj}_{\vec{e}_{1} } \vec{a}+\mbox{Proj}_{\vec{e}_{2} } \vec{a}$.&lt;br /&gt;
&lt;br /&gt;
С другой стороны, векторы $\overrightarrow{N_{1} M_{1} } || \vec{e}_{1}$, $\overrightarrow{N_{2} M_{2} } || \vec{e}_{2}$, поэтому $\overrightarrow{N_{1} M_{1} } =\left(mes_{\vec{e}_{1} } \overrightarrow{N_{1} M_{1} } \right) \vec{e}_{1}$, $\overrightarrow{N_{2} M_{2} } =\left(mes_{\vec{e}_{2} } \overrightarrow{N_{2} M_{2} } \right) \vec{e}_{2}$, следовательно,&lt;br /&gt;
$\vec{a}=\left(mes_{\vec{e}_{1} } (\mbox{Proj}_{\vec{e}_{1} } \vec{a})\right) \vec{e}_{1} +\left(mes_{\vec{e}_{2} } (\mbox{Proj}_{\vec{e}_{2} } \vec{a})\right) \vec{e}_{2}$.&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Алгебраическая мера геометрической проекции вектора на координатную ось называется алгебраической проекцией вектора.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-10-02-projections.svg|thumb|800px|alt=Вектор a = NM и его проекции N1M1 на ось Oe1 и N2M2 на ось Oe2, полученные параллельным проектированием|Рис. 2. Точки &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; спроецированы на оси: получаются &amp;lt;math&amp;gt;N_1M_1 = \operatorname{Proj}_{e_1} a&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;N_2M_2 = \operatorname{Proj}_{e_2} a&amp;lt;/math&amp;gt;. Алгебраические проекции — меры этих векторов, они и служат координатами: &amp;lt;math&amp;gt;a = (\operatorname{Apr}_{e_1} a)e_1 + (\operatorname{Apr}_{e_2} a)e_2&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Алгебраическая проекция вектора на ось $O\vec{e}_{i} $ обозначается $Apr_{\vec{e}_{i} } \vec{a}=mes_{\vec{e}_{i} } (\mbox{Proj}_{\vec{e}_{i} } \vec{a})$. Тогда с учетом введенного обозначения вектор $\vec{a}=(Apr_{\vec{e}_{i} } \vec{a})\vec{e}_{1} +(Apr_{\vec{e}_{2} } \vec{a})\vec{e}_{2}$, то есть аффинными координатами произвольного вектора являются его алгебраические проекции на координатные оси, а аффинными координатами произвольной точки {{---}} алгебраические проекции ее радиус-вектора на соответствующие координатные оси.&lt;br /&gt;
&lt;br /&gt;
Допустим теперь, что в системе координат $O\vec{e}_{1} \vec{e}_{2} \vec{e}_{3} $ задан произвольный вектор $\vec{a}=$$\overrightarrow{NM} $. Спроектируем точки $N$ и $M$ на координатные оси. Тогда точки $N_1$ и $M_1$ определяют вектор $\overrightarrow{N_{1} M_{1} } =\mbox{Proj}_{\vec{e}_{1} } \vec{a}$, точки $N_2$ и $M_2$ {{---}} вектор $\overrightarrow{N_{2} M_{2} } =\mbox{Proj}_{\vec{e}_{2} } \vec{a}$, а точки $N_3$ и $M_3$ {{---}} соответственно вектор $\overrightarrow{N_{3} M_{3} } =\mbox{Proj}_{\vec{e}_{3} } \vec{a}$. В соответствии с правилом сложения векторов $\overrightarrow{NM} =\overrightarrow{N_{1} M_{1} } +\overrightarrow{N_{2} M_{2} } +\overrightarrow{N_{3} M_{3} }$, поэтому &lt;br /&gt;
$\vec{a}=\mbox{Proj}_{\vec{e}_{1} } \vec{a}+\mbox{Proj}_{\vec{e}_{2} } \vec{a}+\mbox{Proj}_{\vec{e}_{3} } \vec{a}=(Apr_{\vec{e}_{1} } \vec{a})\vec{e}_{1} +(Apr_{\vec{e}_{2} } \vec{a})\vec{e}_{2} +(Apr_{\vec{e}_{3} } \vec{a})\vec{e}_{3}$.&lt;br /&gt;
&lt;br /&gt;
Следовательно, аффинные координаты произвольного вектора есть его алгебраические проекции на координатные оси, а аффинные координаты произвольной точки {{---}} алгебраические проекции ее радиус-вектора на соответствующие координатные оси.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-10-02-projections.svg&amp;diff=966</id>
		<title>Файл:P03-10-02-projections.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-10-02-projections.svg&amp;diff=966"/>
		<updated>2026-11-04T18:44:12Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-10-01-algebraic-measure.svg&amp;diff=965</id>
		<title>Файл:P03-10-01-algebraic-measure.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-10-01-algebraic-measure.svg&amp;diff=965"/>
		<updated>2026-11-04T18:44:01Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%90%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B_%D0%BA%D0%BE%D0%BE%D1%80%D0%B4%D0%B8%D0%BD%D0%B0%D1%82a&amp;diff=964</id>
		<title>Аффинные системы координатa</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%90%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B_%D0%BA%D0%BE%D0%BE%D1%80%D0%B4%D0%B8%D0%BD%D0%B0%D1%82a&amp;diff=964"/>
		<updated>2026-11-04T18:39:20Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «Аффинная система координат &amp;lt;math&amp;gt;O e_{1} e_{2} ... e_{n}&amp;lt;/math&amp;gt; в аффинном пространстве $A^n$ есть совокуп...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Аффинная система координат &amp;lt;math&amp;gt;O e_{1} e_{2} ... e_{n}&amp;lt;/math&amp;gt; в аффинном пространстве $A^n$ есть совокупность, состоящая из произвольной точки $O$, называемой началом координат, и базиса $e_{1}, e_{2}, ..., e_{n}$ из присоединенного линеала $L^{n}$.&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-09-01-affine-frame.svg|thumb|600px|alt=Слева система O, e1, e2 и точка A(2; 1) с радиус-вектором; справа точки A(1; 2), B(2; 1) и вектор AB = (1; −1)|Рис. 1. Слева система &amp;lt;math&amp;gt;O, e_1, e_2&amp;lt;/math&amp;gt;: точка &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; имеет координаты &amp;lt;math&amp;gt;(2; 1)&amp;lt;/math&amp;gt;, потому что &amp;lt;math&amp;gt;OA = 2e_1 + e_2&amp;lt;/math&amp;gt;. Справа &amp;lt;math&amp;gt;A(1; 2)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;B(2; 1)&amp;lt;/math&amp;gt; и вектор &amp;lt;math&amp;gt;AB = (1; -1)&amp;lt;/math&amp;gt;: координаты вектора — разности координат точек.]]&lt;br /&gt;
&lt;br /&gt;
Подчеркнем, что аффинная система координат задается двумя разнородными объектами {{---}} точкой $O$ из аффинного пространства и базисом $e_{1}, e_{2}, ..., e_{n} $ из присоединенного линеала.&lt;br /&gt;
&lt;br /&gt;
Пусть $A$ {{---}} произвольная точка аффинного пространства $A^n$. Вместе с началом координат $O$ точка $A$ определяет элемент (радиус-вектор) $r_{A} =OA\in L^{n}$, координаты которого определяются из разложения по базису $e_{1}, e_{2}, ..., e_{n}$: $OA=x_{1} e_{1} + x_{2} e_{2} +...+ x_{n} e_{n} $.&lt;br /&gt;
&lt;br /&gt;
Аффинными координатами произвольной точки $A\in A^n$ называют координаты соответствующего радиус-вектора $r_{A} =OA\in L^{n} $ и обозначают символом $A(x_{1}, x_{2}, ..., x_{n} )$.&lt;br /&gt;
&lt;br /&gt;
Ясно, что координаты точки $А$ определяются однозначно в силу единственности разложения вектора по заданному базису.&lt;br /&gt;
&lt;br /&gt;
Произвольный вектор $AB\in L^{n} $ можно представить как разность соответствующих радиус-векторов $AB=r_{B} -r_{A} $.&lt;br /&gt;
&lt;br /&gt;
Если вектор $AB$ задан координатами своего начала $A(x_{1}, x_{2}, ..., x_{n} )$ и конца $B(y_{1}, y_{2}, ..., y_{n} )$ относительно базиса $e_{1}, e_{2}, ..., e_{n}$, то его координаты относительно того же базиса определяются из соотношения как коэффициенты разложения по указанному базису в виде &lt;br /&gt;
&amp;lt;math&amp;gt;AB=(y_{1} - x_{1} )e_{1} +(y_{2} - x_{2} )e_{2} +...+(y_{n} - x_{n} )e_{n}&amp;lt;/math&amp;gt; &lt;br /&gt;
и обычно используют обозначение $AB(y_{1} -x_{1}, y_{n} -x_{2}, ..., y_{n} -x_{n} )$.&lt;br /&gt;
&lt;br /&gt;
Прямой в аффинном пространстве $A^n$, проходящей через точку $C\in A^n$ в направлении ненулевого вектора $u\in L^{n}$, называется множество всех точек $P\in A^n$, для которых $CP=\lambda u$, где $\lambda \in (-\infty, +\infty )$ {{---}} вещественное число. Указанный вектор $u$ называется направляющим вектором прямой.&lt;br /&gt;
&lt;br /&gt;
При рассмотрении аффинных систем координат на прямой, плоскости и трехмерном пространстве, базисные векторы соответствующих линеалов $V^{1}$, $V^{2}$, $V^{3} $ приводятся в общее начало.&lt;br /&gt;
&lt;br /&gt;
Прямые в рассматриваемых пространствах, определяемые началом координат $O$ и соответствующими базисными векторами, называются осями координат ($O\vec{e}_{1}$, $O\vec{e}_{2}$, $O\vec{e}_{3}$); ($O\vec{e}_{1}$, $O\vec{e}_{2} $); $O\vec{e}_{1} $.&lt;br /&gt;
&lt;br /&gt;
В трехмерном аффинном пространстве рассматривают также координатные плоскости, определяемые соответствующими парами координатных осей ($O\vec{e}_{1} \vec{e}_{2}$, $O\vec{e}_{1} \vec{e}_{3}$, $O\vec{e}_{2} \vec{e}_{3} $).&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Тройка базисных векторов $\vec{e}_{1}$, $\vec{e}_{2}$, $\vec{e}_{3} $ называется правой (левой), если после приведения их к общему началу по этим векторам можно направить соответственно большой, несогнутый указательный и средний пальцы правой (левой) руки.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-09-02-orientation.svg|thumb|600px|alt=Слева правая пара e1, e2 с поворотом против часовой стрелки; справа правая тройка e1, e2, e3 и круговые перестановки|Рис. 2. Слева пара &amp;lt;math&amp;gt;e_1, e_2&amp;lt;/math&amp;gt; правая: кратчайший поворот от первого вектора ко второму идёт против часовой стрелки. Справа правая тройка: большой, указательный и средний пальцы правой руки направлены вдоль &amp;lt;math&amp;gt;e_1, e_2, e_3&amp;lt;/math&amp;gt;; круговая замена порядка ориентацию не меняет.]]&lt;br /&gt;
&lt;br /&gt;
Заметим, что тройка будет правой, если, находясь внутри телесного угла, образованного этими векторами, кратчайший поворот от первого из них ко второму, а затем от второго к третьему осуществляется против хода часовой стрелки.&lt;br /&gt;
&lt;br /&gt;
Очевидно, что при круговой замене ориентация тройки не изменяется, то есть если $\vec{e}_{1}$, $\vec{e}_{2}$, $\vec{e}_{3} $ правая тройка векторов, то $\vec{e}_{3}$, $\vec{e}_{1}$, $\vec{e}_{2} $ и $\vec{e}_{2}$, $\vec{e}_{3}$, $\vec{e}_{1} $ также будут правыми.&lt;br /&gt;
&lt;br /&gt;
Упорядоченная пара базисных векторов $\vec{e}_{1}$, $\vec{e}_{2} $ называется правой (левой), если кратчайший поворот от $\vec{e}_{1} $ к $\vec{e}_{2} $ происходит против (по) часовой стрелки.&lt;br /&gt;
&lt;br /&gt;
Аффинная система координат называется правой (левой), если ее базис правый (левый). В дальнейшем ради определенности будем рассматривать только правые системы координат.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-09-02-orientation.svg&amp;diff=963</id>
		<title>Файл:P03-09-02-orientation.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-09-02-orientation.svg&amp;diff=963"/>
		<updated>2026-11-04T18:38:35Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-09-01-affine-frame.svg&amp;diff=962</id>
		<title>Файл:P03-09-01-affine-frame.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-09-01-affine-frame.svg&amp;diff=962"/>
		<updated>2026-11-04T18:38:27Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%90%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D0%B5_%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B0&amp;diff=961</id>
		<title>Аффинные пространства</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%90%D1%84%D1%84%D0%B8%D0%BD%D0%BD%D1%8B%D0%B5_%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%B0&amp;diff=961"/>
		<updated>2026-11-04T18:35:54Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «{{Определение |definition= Аффинное пространство {{---}} это множество $A$ элементов произвольной...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Аффинное пространство {{---}} это множество $A$ элементов произвольной природы, называемых точками, которому сопоставлены:&lt;br /&gt;
&lt;br /&gt;
1) линеал $L$, называемый присоединенным к $A$;&lt;br /&gt;
&lt;br /&gt;
2) соответствие, по которому любым двум точкам $A, B  \in A$ отвечает некоторый элемент $AB \in L$ с началом в $A$ и с концом в $B$;&lt;br /&gt;
&lt;br /&gt;
При этом выполняются следующие две аксиомы:&lt;br /&gt;
&lt;br /&gt;
10. Для произвольной точки $A \in A$ и любого вектора ${\rm {\bf u} } \in L$ существует единственная точка $B \in A$ такая, что $AB = {\rm {\bf u} }$.&lt;br /&gt;
&lt;br /&gt;
11. Для произвольных трех точек $А, В, С$ имеет место равенство $AB + BC = AC$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-08-01-affine-axioms.svg|frame|none|800px|alt=Слева по точке A и вектору u строится единственная точка B; справа для точек A, B, C выполнено AB + BC = AC|Рис. 1. Аксиома 10: по точке &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и вектору &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; точка &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; с &amp;lt;math&amp;gt;AB = u&amp;lt;/math&amp;gt; находится однозначно. Аксиома 11: для любых точек &amp;lt;math&amp;gt;A, B, C&amp;lt;/math&amp;gt; выполняется &amp;lt;math&amp;gt;AB + BC = AC&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Размерность аффинного пространства $A$ совпадает с размерностью присоединенного с $A$ линеала $L$ и обозначается символом $dim(A)$. Если $dim(A) = n$, то аффинное пространство называют $n$-мерным и используют обозначение $A^{ n}$.&lt;br /&gt;
&lt;br /&gt;
Приведем примеры аффинных пространств.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим трехмерное линейное пространство $V^3$. Фиксируем некоторую точку $О$ и будем рассматривать всевозможные радиус-векторы с началом в точке $О$. Под точками аффинного пространства $A^{ 3}$ с присоединенным линеалом $V^3$, будем понимать концы соответствующих радиус-векторов, причем двум точкам $А$ и $В$ сопоставляется вектор $\mathop {AB}\limits^ \to = \vec{r}_B - \vec{r}_A \in V^3$. Заметим, что при такой формализации мы отождествляем точку $А$ с радиусом-вектором.&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-08-02-radius-vectors.svg|frame|none|800px|alt=Точка O, радиус-векторы rA и rB и вектор AB = rB − rA|Рис. 2. С фиксированным началом &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt; каждая точка получает радиус-вектор: &amp;lt;math&amp;gt;A \mapsto r_A = OA&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;B \mapsto r_B = OB&amp;lt;/math&amp;gt;. Тогда &amp;lt;math&amp;gt;AB = r_B - r_A&amp;lt;/math&amp;gt;: разность двух точек — вектор.]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-08-01-affine-axioms.svg&amp;diff=960</id>
		<title>Файл:P03-08-01-affine-axioms.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-08-01-affine-axioms.svg&amp;diff=960"/>
		<updated>2026-11-04T18:35:20Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-08-02-radius-vectors.svg&amp;diff=959</id>
		<title>Файл:P03-08-02-radius-vectors.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-08-02-radius-vectors.svg&amp;diff=959"/>
		<updated>2026-11-04T18:35:05Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%98%D0%B7%D0%BE%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%BE%D0%B2&amp;diff=958</id>
		<title>Изоморфизм линеалов</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%98%D0%B7%D0%BE%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%BE%D0%B2&amp;diff=958"/>
		<updated>2026-11-04T18:33:18Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Соответствие между элементами двух линеалов $L$ и $M$ называется взаимно однозначным, если каждому элементу из $L$ отвечает единственный элемент из $M$, причем каждый элемент из $M$ отвечает одному лишь элементу из $L$.&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Два линеала $L$ и $M$ называются изоморфными (обозначение $L \approx M$), если между элементами этих линеалов можно установить такое взаимно однозначное соответствие&lt;br /&gt;
&amp;lt;math&amp;gt;\varphi :x \in L\buildrel \varphi \over \longrightarrow \varphi (x) \in M,&amp;lt;/math&amp;gt;&lt;br /&gt;
что $\varphi (\lambda x) = \lambda \varphi (x)$, $\varphi (x + y) = \varphi (x) + \varphi (y)$, где $x,y \in L$; $\lambda $ {{---}} любое вещественное число.&lt;br /&gt;
&lt;br /&gt;
Указанное отображение $\varphi :L \to M$ называют линейным изоморфизмом.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-07-01-coordinate-isomorphism.svg|thumb|800px|alt=Слева вектор x = 2e1 + 3e2 в линеале L, справа точка (2; 3) в R²; между ними отображение φ|Рис. 1. Вектор &amp;lt;math&amp;gt;x = 2e_1 + 3e_2&amp;lt;/math&amp;gt; линеала &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переходит в набор своих координат &amp;lt;math&amp;gt;(2; 3) \in R^2&amp;lt;/math&amp;gt;. Отображение &amp;lt;math&amp;gt;\varphi&amp;lt;/math&amp;gt; сохраняет сложение и умножение на число, поэтому является изоморфизмом.]]&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Все линеалы одной и той же размерности изоморфны.&lt;br /&gt;
|proof=Пусть линеалы $L$ и $M$ являются $n$-мерными. Выберем в $L$ некоторый базис ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $, а в $M$ {{---}} базис ${\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_n .$ Сопоставим элементу $x = \sum\limits_{i = 1}^n {x_i } e_i \in L$ элемент &lt;br /&gt;
&amp;lt;math&amp;gt;\varphi (x) = \varphi \left( {\sum\limits_{i = 1}^n {x_i } {\rm {\bf e} }_i } \right) = \sum\limits_{i = 1}^n {x_i } {\rm {\bf {e}'} }_i \in M,&amp;lt;/math&amp;gt;&lt;br /&gt;
т. е. вектор, имеющий те же координаты, что и вектор $x$, но относительно базиса ${\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_n .$&lt;br /&gt;
&lt;br /&gt;
В силу единственности разложения элемента по базису установленное соответствие $\varphi $ является взаимно однозначным. Кроме того,&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\varphi (x + y) = \varphi \left( {\sum\limits_{i = 1}^n {(x_i + y_i ){\bf e}_i } } \right) = \sum\limits_{i = 1}^n {(x_i + y_i ){\rm {\bf {e}'} }_i } = \sum\limits_{i = 1}^n {x_i {\rm {\bf {e}'} }_i } + \sum\limits_{i = 1}^n {y_i {\rm {\bf {e}'} }_i } = \varphi ({\rm {\bf x} }) + \varphi (y),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\varphi (\alpha x) = \varphi \left( {\sum\limits_{i = 1}^n {(\alpha x_i ){\bf e}_i } } \right) = \sum\limits_{i = 1}^n {(\alpha x_i ){\rm {\bf {e}'} }_i } = \alpha \sum\limits_{i = 1}^n {x_i {\rm {\bf {e}'} }_i } = \alpha \varphi (x).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, линеалы $L$ и $M$ являются изоморфными, а построенное отображение называется координатным изоморфизмом.&lt;br /&gt;
}}&lt;br /&gt;
Заметим, что координатный изоморфизм зависит от выбора базисов в $L$ и $M$.&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Любое $n$-мерное линейное пространство $L$ изоморфно координатному пространству $R^n$.&lt;br /&gt;
}}&lt;br /&gt;
Отметим, что при координатном изоморфизме любому элементу $x \in L$, представимому через базис ${\bf e}_1, {\bf e}_2, ..., {\rm {\bf e} }_n $ в виде $x = \sum\limits_{i = 1}^n {x_i } {\rm {\bf e} }_i $, сопоставляется упорядоченный набор чисел $(x_1, x_2, ..., x_n ) \in R^n$, компоненты которого суть координаты элемента $x \in L$ относительно фиксированного базиса.&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Изоморфные линеалы имеют одну и ту же размерность.&lt;br /&gt;
|proof=Пусть $n$-мерный линеал $L$ изоморфен линейному пространству $M $при линейном изоморфизме $\varphi $. Убедимся, что нулевому элементу ${\rm {\bf 0} } \in L$ соответствует нулевой элемент $\varphi ({\bf 0}) \in M.$&lt;br /&gt;
&lt;br /&gt;
Действительно, для любого элемента $x \in L$ имеем равенство $x + {\bf 0} = x$ и потому $\varphi ({\rm {\bf x} } + {\bf 0}) = \varphi (x)$. В силу свойств линейного изоморфизма $\varphi (x + {\bf 0}) = \varphi ({\rm {\bf x} }) + \varphi ({\bf 0})$. Сравнивая последние равенства, получаем $\varphi (x) + \varphi ({\bf 0}) = \varphi (x)$, откуда следует, что $\varphi $(\textbf{0}) есть нулевой элемент в $M$.&lt;br /&gt;
&lt;br /&gt;
Пусть ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $ произвольный базис в$ L$, а $\varphi ({\bf e}_1 ), \varphi ({\rm {\bf e} }_2 ),..., \varphi ({\bf e}_n )$ {{---}} образы базисных векторов в $M$ при линейном изоморфизме $\varphi $. Составим в $M$ линейную комбинацию&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\sum\limits_{i = 1}^n {\alpha _i \varphi (e_i )} = \varphi ({\bf 0}).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В силу свойств линейного изоморфизма $\varphi $ и взаимно однозначного соответствия нулевых элементов линеалов $L$ и $M $имеем соотношение $\sum\limits_{i = 1}^n {\alpha _i } e_i = {\bf 0}$. Откуда, вследствие линейной независимости элементов ${\bf e}_1, {\bf e}_2, ..., {\rm {\bf e} }_n $ получаем $\alpha _i = 0, i = \overline {1,n} $. Но тогда следует, что элементы $\varphi ({\bf e}_i )$, $i = \overline {1,n} $ из $M $линейно независимы.&lt;br /&gt;
&lt;br /&gt;
Таким образом, размерность $M$ не менее $n$. Если предположить, что размерность $\dim (M) &amp;gt; n$, то при обратном взаимно однозначном соответствии $\varphi ^{-1}: M \to L$ окажется, что $\dim (L) &amp;gt; n$. Полученное противоречие показывает, что $\dim (M) = n$.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:p03-07-02-dimension-invariant.svg|frame|none|800px|alt=Четыре класса линеалов по размерности 1, 2, 3 и ∞; внутри класса V ≈ R, между классами изоморфизма нет|Рис. 2. Внутри каждого класса одинаковой размерности все линеалы изоморфны: &amp;lt;math&amp;gt;V^1 \approx R^1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V^2 \approx R^2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V^3 \approx R^3&amp;lt;/math&amp;gt;. Бесконечномерные пространства образуют отдельный класс. Поэтому &amp;lt;math&amp;gt;L \approx M \iff \dim L = \dim M&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Конечномерные линеалы разных размерностей неизоморфны.&lt;br /&gt;
}}&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Бесконечномерный линеал неизоморфен никакому конечномерному линеалу.&lt;br /&gt;
}}&lt;br /&gt;
Итак, существенной характеристикой конечномерного линеала является его размерность. Линеалы, имеющие одинаковую размерность, изоморфны. Свойства линеалов, выраженные в терминах алгебраических операций сложения и умножения на вещественное число, у изоморфных линеалов одинаковые. Поэтому с алгебраической точки зрения изоморфные линеалы тождественны. На этом факте основывается, в сущности, и метод координат, на котором базируется аналитическая геометрия.&lt;br /&gt;
&lt;br /&gt;
Вследствие теоремы все $n$-мерные линейные пространства изоморфны координатному линеалу $R^n$. Таким образом, в теории $n$-мерных линейных пространств, не теряя общности, можно ограничиться изучением линеала $R^n$, поскольку, очевидно, что с помощью обратного изоморфизма $\varphi ^{-1}:R^n\buildrel {\varphi ^{ - 1} } \over \longrightarrow L$ полученные в $R^n$ утверждения переносятся в исходный линеал $L$.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%98%D0%B7%D0%BE%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%BE%D0%B2&amp;diff=957</id>
		<title>Изоморфизм линеалов</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%98%D0%B7%D0%BE%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%BE%D0%B2&amp;diff=957"/>
		<updated>2026-11-04T18:32:47Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «{{Определение |definition= Соответствие между элементами двух линеалов $L$ и $M$ называется взаи...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Соответствие между элементами двух линеалов $L$ и $M$ называется взаимно однозначным, если каждому элементу из $L$ отвечает единственный элемент из $M$, причем каждый элемент из $M$ отвечает одному лишь элементу из $L$.&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Два линеала $L$ и $M$ называются изоморфными (обозначение $L \approx M$), если между элементами этих линеалов можно установить такое взаимно однозначное соответствие&lt;br /&gt;
&amp;lt;math&amp;gt;\varphi :x \in L\buildrel \varphi \over \longrightarrow \varphi (x) \in M,&amp;lt;/math&amp;gt;&lt;br /&gt;
что $\varphi (\lambda x) = \lambda \varphi (x)$, $\varphi (x + y) = \varphi (x) + \varphi (y)$, где $x,y \in L$; $\lambda $ {{---}} любое вещественное число.&lt;br /&gt;
&lt;br /&gt;
Указанное отображение $\varphi :L \to M$ называют линейным изоморфизмом.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-07-01-coordinate-isomorphism.svg|thumb|800px|alt=Слева вектор x = 2e1 + 3e2 в линеале L, справа точка (2; 3) в R²; между ними отображение φ|Рис. 1. Вектор &amp;lt;math&amp;gt;x = 2e_1 + 3e_2&amp;lt;/math&amp;gt; линеала &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переходит в набор своих координат &amp;lt;math&amp;gt;(2; 3) \in R^2&amp;lt;/math&amp;gt;. Отображение &amp;lt;math&amp;gt;\varphi&amp;lt;/math&amp;gt; сохраняет сложение и умножение на число, поэтому является изоморфизмом.]]&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Все линеалы одной и той же размерности изоморфны.&lt;br /&gt;
|proof=Пусть линеалы $L$ и $M$ являются $n$-мерными. Выберем в $L$ некоторый базис ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $, а в $M$ {{---}} базис ${\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_n .$ Сопоставим элементу $x = \sum\limits_{i = 1}^n {x_i } e_i \in L$ элемент &lt;br /&gt;
&amp;lt;math&amp;gt;\varphi (x) = \varphi \left( {\sum\limits_{i = 1}^n {x_i } {\rm {\bf e} }_i } \right) = \sum\limits_{i = 1}^n {x_i } {\rm {\bf {e}'} }_i \in M,&amp;lt;/math&amp;gt;&lt;br /&gt;
т. е. вектор, имеющий те же координаты, что и вектор $x$, но относительно базиса ${\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_n .$&lt;br /&gt;
&lt;br /&gt;
В силу единственности разложения элемента по базису установленное соответствие $\varphi $ является взаимно однозначным. Кроме того,&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\varphi (x + y) = \varphi \left( {\sum\limits_{i = 1}^n {(x_i + y_i ){\bf e}_i } } \right) = \sum\limits_{i = 1}^n {(x_i + y_i ){\rm {\bf {e}'} }_i } = \sum\limits_{i = 1}^n {x_i {\rm {\bf {e}'} }_i } + \sum\limits_{i = 1}^n {y_i {\rm {\bf {e}'} }_i } = \varphi ({\rm {\bf x} }) + \varphi (y),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\varphi (\alpha x) = \varphi \left( {\sum\limits_{i = 1}^n {(\alpha x_i ){\bf e}_i } } \right) = \sum\limits_{i = 1}^n {(\alpha x_i ){\rm {\bf {e}'} }_i } = \alpha \sum\limits_{i = 1}^n {x_i {\rm {\bf {e}'} }_i } = \alpha \varphi (x).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом, линеалы $L$ и $M$ являются изоморфными, а построенное отображение называется координатным изоморфизмом.&lt;br /&gt;
}}&lt;br /&gt;
Заметим, что координатный изоморфизм зависит от выбора базисов в $L$ и $M$.&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Любое $n$-мерное линейное пространство $L$ изоморфно координатному пространству $R^n$.&lt;br /&gt;
}}&lt;br /&gt;
Отметим, что при координатном изоморфизме любому элементу $x \in L$, представимому через базис ${\bf e}_1, {\bf e}_2, ..., {\rm {\bf e} }_n $ в виде $x = \sum\limits_{i = 1}^n {x_i } {\rm {\bf e} }_i $, сопоставляется упорядоченный набор чисел $(x_1, x_2, ..., x_n ) \in R^n$, компоненты которого суть координаты элемента $x \in L$ относительно фиксированного базиса.&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Изоморфные линеалы имеют одну и ту же размерность.&lt;br /&gt;
|proof=Пусть $n$-мерный линеал $L$ изоморфен линейному пространству $M $при линейном изоморфизме $\varphi $. Убедимся, что нулевому элементу ${\rm {\bf 0} } \in L$ соответствует нулевой элемент $\varphi ({\bf 0}) \in M.$&lt;br /&gt;
&lt;br /&gt;
Действительно, для любого элемента $x \in L$ имеем равенство $x + {\bf 0} = x$ и потому $\varphi ({\rm {\bf x} } + {\bf 0}) = \varphi (x)$. В силу свойств линейного изоморфизма $\varphi (x + {\bf 0}) = \varphi ({\rm {\bf x} }) + \varphi ({\bf 0})$. Сравнивая последние равенства, получаем $\varphi (x) + \varphi ({\bf 0}) = \varphi (x)$, откуда следует, что $\varphi $(\textbf{0}) есть нулевой элемент в $M$.&lt;br /&gt;
&lt;br /&gt;
Пусть ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $ произвольный базис в$ L$, а $\varphi ({\bf e}_1 ), \varphi ({\rm {\bf e} }_2 ),..., \varphi ({\bf e}_n )$ {{---}} образы базисных векторов в $M$ при линейном изоморфизме $\varphi $. Составим в $M$ линейную комбинацию&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\sum\limits_{i = 1}^n {\alpha _i \varphi (e_i )} = \varphi ({\bf 0}).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В силу свойств линейного изоморфизма $\varphi $ и взаимно однозначного соответствия нулевых элементов линеалов $L$ и $M $имеем соотношение $\sum\limits_{i = 1}^n {\alpha _i } e_i = {\bf 0}$. Откуда, вследствие линейной независимости элементов ${\bf e}_1, {\bf e}_2, ..., {\rm {\bf e} }_n $ получаем $\alpha _i = 0, i = \overline {1,n} $. Но тогда следует, что элементы $\varphi ({\bf e}_i )$, $i = \overline {1,n} $ из $M $линейно независимы.&lt;br /&gt;
&lt;br /&gt;
Таким образом, размерность $M$ не менее $n$. Если предположить, что размерность $\dim (M) &amp;gt; n$, то при обратном взаимно однозначном соответствии $\varphi ^{-1}: M \to L$ окажется, что $\dim (L) &amp;gt; n$. Полученное противоречие показывает, что $\dim (M) = n$.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:p03-07-02-dimension-invariant.svg|thumb|800px|alt=Четыре класса линеалов по размерности 1, 2, 3 и ∞; внутри класса V ≈ R, между классами изоморфизма нет|Рис. 2. Внутри каждого класса одинаковой размерности все линеалы изоморфны: &amp;lt;math&amp;gt;V^1 \approx R^1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V^2 \approx R^2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V^3 \approx R^3&amp;lt;/math&amp;gt;. Бесконечномерные пространства образуют отдельный класс. Поэтому &amp;lt;math&amp;gt;L \approx M \iff \dim L = \dim M&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Конечномерные линеалы разных размерностей неизоморфны.&lt;br /&gt;
}}&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Бесконечномерный линеал неизоморфен никакому конечномерному линеалу.&lt;br /&gt;
}}&lt;br /&gt;
Итак, существенной характеристикой конечномерного линеала является его размерность. Линеалы, имеющие одинаковую размерность, изоморфны. Свойства линеалов, выраженные в терминах алгебраических операций сложения и умножения на вещественное число, у изоморфных линеалов одинаковые. Поэтому с алгебраической точки зрения изоморфные линеалы тождественны. На этом факте основывается, в сущности, и метод координат, на котором базируется аналитическая геометрия.&lt;br /&gt;
&lt;br /&gt;
Вследствие теоремы все $n$-мерные линейные пространства изоморфны координатному линеалу $R^n$. Таким образом, в теории $n$-мерных линейных пространств, не теряя общности, можно ограничиться изучением линеала $R^n$, поскольку, очевидно, что с помощью обратного изоморфизма $\varphi ^{-1}:R^n\buildrel {\varphi ^{ - 1} } \over \longrightarrow L$ полученные в $R^n$ утверждения переносятся в исходный линеал $L$.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-07-02-dimension-invariant.svg&amp;diff=956</id>
		<title>Файл:P03-07-02-dimension-invariant.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-07-02-dimension-invariant.svg&amp;diff=956"/>
		<updated>2026-11-04T18:32:09Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-07-01-coordinate-isomorphism.svg&amp;diff=955</id>
		<title>Файл:P03-07-01-coordinate-isomorphism.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-07-01-coordinate-isomorphism.svg&amp;diff=955"/>
		<updated>2026-11-04T18:31:59Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A0%D0%B0%D0%BD%D0%B3_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B&amp;diff=954</id>
		<title>Ранг матрицы</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A0%D0%B0%D0%BD%D0%B3_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B&amp;diff=954"/>
		<updated>2026-11-04T18:28:31Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «Пусть ${\bf a}_i \in R^n$ {{---}} произвольная $i-$я строка матрицы ${\rm {\bf S} } = [{\bf a}_1, {\bf a}_2, ..., {\bf a}_p ] = [p\...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Пусть ${\bf a}_i \in R^n$ {{---}} произвольная $i-$я строка матрицы ${\rm {\bf S} } = [{\bf a}_1, {\bf a}_2, ..., {\bf a}_p ] = [p\times n]$. Каждая строка является элементом линеала $R^n$, поэтому если число строк в матрице $p &amp;gt; n$, то строки ${\bf a}_1, {\bf a}_2,..., {\bf a}_p $ линейно зависимы. С другой стороны, очевидно, что среди ${\bf a}_1, {\bf a}_2, ..., {\bf a}_p $ существует ровно $k \le n$ строк, являющихся линейно независимыми, по которым можно разложить остальные $(n - k)$ строк.&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Указанная совокупность $k$ строк называется '''строчечным базисом''', а число $k$ {{---}} строчечным рангом матрицы.&lt;br /&gt;
}}&lt;br /&gt;
Аналогично, если матрица ${\rm {\bf S} } = [{\bf a}^1, {\rm {\bf a} }^2, ..., {\bf a}^n]$, где ${\bf a}^j \in R^p$, то в ней существует не более $p$ линейно независимых столбцов. Среди столбцов ${\bf a}^1, {\bf a}^2, ..., {\bf a}^n$ имеется ровно $m \le p$ линейно независимых, по которым можно разложить любой из остальных $p - m$ столбцов.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Указанная совокупность $m$ столбцов называется '''столбцовым базисом''', а число $m$ {{---}} '''столбцовым рангом''' матрицы $S$.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:p03-06-01-row-column-rank.svg|thumb|800px|alt=Матрица 3 × 4: третья строка равна сумме первых двух; первые два столбца образуют базис, c3 и c4 выражены через них|Рис. 1. Одна матрица &amp;lt;math&amp;gt;3 \times 4&amp;lt;/math&amp;gt;: третья строка равна сумме первых двух, поэтому строчечный ранг &amp;lt;math&amp;gt;k = 2&amp;lt;/math&amp;gt;. По столбцам &amp;lt;math&amp;gt;c_3 = -2c_1 + c_2&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;c_4 = -3c_1 + 2c_2&amp;lt;/math&amp;gt;, поэтому и столбцовый ранг равен 2.]]&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Строчечный (столбцовый) ранг любой матрицы не зависит от выбора строчечного (столбцового) базиса.&lt;br /&gt;
|proof=Проведем доказательство для строчечного базиса. Пусть ${\bf a}_1, {\bf a}_2, ..., {\bf a}_k $ и ${\rm {\bf c} }_1, {\rm {\bf c} }_2, ..., {\rm {\bf c} }_r $ два разных строчечных базиса одной и той же матрицы. Предположим, что $r &amp;gt; k$. Поскольку ${\bf a}_1, {\bf a}_2, ..., {\bf a}_k $ базис, то строки ${\rm {\bf c} }_j $, $j = \overline {1,r} $ можно разложить по этому базису, т. е. ${\rm {\bf c} }_j = \sum\limits_{i = 1}^k {\alpha _{ij} } {\bf a}_i $, $j = \overline {1,r} $. Так как ${\bf a}_1, {\bf a}_2, ..., {\rm {\bf a} }_k $ линейно независимы, то в соответствии с теоремой 3.5.3 строки ${\rm {\bf c} }_1, {\rm {\bf c} }_2, ..., {\rm {\bf c} }_r $ линейно зависимы. Полученное противоречие доказывает, что $k \ge r$. Аналогично показывается, что $k $не может быть больше$ r$, поэтому $k = r$.&lt;br /&gt;
}}&lt;br /&gt;
Пусть дана прямоугольная матрица ${\bf a} = [{\bf a}_1, {\bf a}_2, ...., {\bf a}_m ] = [{\bf a}^1, {\bf a}^2,..., {\bf a}^n]$, ${\bf a}_i \in R^n, {\bf a}^j \in R^m$. Допустим, что строчечный ранг этой матрицы равен $k$. Без ограничения общности можно считать, что первые $k$ строк образуют базис, а любая другая строка матрицы ${\bf a}$ представима в виде их линейной комбинации, т. е.&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\bf a}_s = \sum\limits_{i = 1}^k {\gamma _{si} {\bf a}_i }, &lt;br /&gt;
\quad&lt;br /&gt;
s = \overline {k + 1,m}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Из первых $k$ строк матрицы ${\bf a}$ построим вспомогательную матрицу ${\rm {\bf \tilde {A} } } = [{\rm {\bf \tilde {a} } }_1, {\rm {\bf \tilde {a} } }_2, ..., {\rm {\bf \tilde {a} } }_k ]$, где ${\rm {\bf \tilde {a} } }_i = {\bf a}_i $, $i = \overline {1,k} $. Пусть ${\rm {\bf \tilde {a} } }^j$ есть $j-$й столбец матрицы ${\rm {\bf \tilde {A} } } = [{\rm {\bf \tilde {a} } }^1,{\rm {\bf \tilde {a} } }^2,..., {\rm {\bf \tilde {a} } }^n]$.&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=Если для некоторых чисел $\beta _1, \beta _2, ..., \beta _n $ справедливо равенство $\sum\limits_{i = 1}^n {\beta _i {\rm {\bf \tilde {a} } }^i = 0}$, то $\sum\limits_{i = 1}^n {\beta _i {\bf a}^{\rm {\bf i} } = 0}$.&lt;br /&gt;
|proof=Если ввести обозначение &lt;br /&gt;
&amp;lt;math&amp;gt;{\rm {\bf b} } = \left( {{\begin{array}{c}&lt;br /&gt;
 {\beta _1 } \\&lt;br /&gt;
 {\beta _2 } \\&lt;br /&gt;
 \vdots  \\&lt;br /&gt;
 {\beta _n } \\&lt;br /&gt;
\end{array} } } \right)&amp;lt;/math&amp;gt;, то произведение ${\bf a}_j {\rm {\bf b} }$ определяет $j-$ю компоненту столбца $\sum\limits_{i = 1}^n {\beta _i {\rm {\bf a} }^i} $. &lt;br /&gt;
Из равенства $\sum\limits_{i = 1}^n {\beta _i {\rm {\bf \tilde {a} } }^i = 0} $ следует, что первые $k$ компонент указанного столбца $\sum\limits_{i = 1}^n {\beta _i {\bf a}^i} $ по построению матрицы &lt;br /&gt;
${\rm {\bf \tilde {A} } }$ равны нулю, т. е. ${\bf a}_i {\rm {\bf b} } = 0$, $i = \overline {1,k} $. Тогда для любого $s = \overline {k + 1,m} $ и имеем&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\bf a}_s {\rm {\bf b} } = \left( {\sum\limits_{i = 1}^k {\gamma _{si} {\bf a}_i } } \right){\rm {\bf b} } = \sum\limits_{i = 1}^k {\gamma _{si} ({\bf a}_i {\rm {\bf b} }) = 0} .&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Строчечный ранг любой матрицы равен ее столбцовому рангу.&lt;br /&gt;
|proof=Допустим, что столбцовый базис матрицы ${\rm {\bf \tilde {A} } } = [{\rm {\bf \tilde {a} } }_1, {\rm {\bf \tilde {a} } }_2, ..., {\rm {\bf \tilde {a} } }_k ]$ состоит из $r$ первых столбцов ${\rm {\bf \tilde {a} } }^1,{\rm {\bf \tilde {a } } }^2,..., {\rm {\bf \tilde {a} } }^r$. Так как ${\rm {\bf \tilde {a} } }^i \in R^k$, то $r \le k$. Столбцы ${\rm {\bf \tilde {a} } }^1,{\rm {\bf \tilde {a} } }^2,..., {\rm {\bf \tilde {a} } }^r$ образуют базис, поэтому для любого $s = \overline {r + 1,n} $ имеем ${\rm {\bf \tilde {a} } }^s = \sum\limits_{i = 1}^r {\beta _{si} {\rm {\bf \tilde {a} } }^i} $. Следовательно, справедливо равенство $\sum\limits_{i = 1}^r {\beta _{si} {\rm {\bf \tilde {a} } }^i} - {\rm {\bf \tilde {a} } }^s = 0$. Тогда в силу леммы 3.6.1 имеем $\sum\limits_{i = 1}^r {\beta _{si} {\rm {\bf a} }^i} - {\bf a}^s = 0$, отсюда следует, что ${\bf a}^1, {\rm {\bf a} }^2, ..., {\bf a}^r$ есть столбцовый базис матрицы ${\rm {\bf A} }$. Поскольку имеем неравенство $r \le k$, то столбцовый ранг не превосходит строчечный. Аналогичные рассуждения относительно строчечного базиса приводят к выводу, что $r \ge k$. Следовательно, строчечный ранг матрицы совпадает со столбцовым рангом.&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Общее значение столбцового и строчечного ранга называется рангом матрицы ${\bf a}$ и обозначается $rank{\rm {\bf A} }$.&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Определитель матрицы ${\bf a} = [n\times n]$ равен нулю тогда и только тогда, когда $rank{\bf a} &amp;lt; n$.&lt;br /&gt;
|proof='''Достаточность.''' Пусть $rank{\bf a} &amp;lt; n$, следовательно, строки матрицы ${\bf a}$ линейно зависимы. Поэтому в соответствии с теоремой из параграфа 3 главы 3, по крайней мере, одну из них можно разложить по оставшимся строкам. Без ограничения общности будем считать, что ${\bf a}_n = \sum\limits_{i = 1}^{n - 1} {\gamma _{ni} {\bf a}_i } $. Определитель $\det {\bf a}$ не изменяется, если к любой из его строк прибавить другую строку, умноженную на заданное число. Следовательно,&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\det {\bf a} = \det [{\bf a}_1, {\bf a}_2, ..., {\rm {\bf a} }_n ] = \det [{\bf a}_1, {\bf a}_2, ..., {\bf a}_n - \gamma _1 {\bf a}_1 - \cdots - \gamma _{n - 1} {\bf a}_{n - 1} ] = \det [{\bf a}_1, {\bf a}_2, ..., {\bf a}_{n - 1}, 0] = 0.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Необходимость.''' Доказательство проведем методом математической индукции по числу $n$. При $n = 1$ имеем $\det {\bf a} = \alpha _{11} = 0$, т. е. ${\bf a}_1 = 0$ {{---}} линейно зависимая строка. Допустим, что теорема доказана для $n = k - 1$, докажем ее для случая $n = k$.&lt;br /&gt;
&lt;br /&gt;
Пусть ${\bf a} = [{\bf a}_1, {\bf a}_2, ...., {\rm {\bf a} }_k ] = \left\{ {\alpha _{ij} } \right\}_{i,j = 1}^{k,k} $. Построим вспомогательные строки, считая $\alpha _{11} \ne 0$:&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\rm {\bf b} }_2 = {\bf a}_2 - \frac{\alpha _{21} }{\alpha _{11} }{\rm {\bf a} }_1 = (0,{\alpha }'_{22}, {\alpha }'_{23}, ..., {\alpha }'_{2k} ), ..., {\rm {\bf b} }_k = {\bf a}_k - \frac{\alpha _{k1} }{\alpha _{11} }{\rm {\bf a} }_1 = (0,{\alpha }'_{k2}, {\alpha }'_{k3}, ..., {\alpha }'_{kk} ).&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\det {\bf a} = \det [{\bf a}_1, {\rm {\bf b} }_2,  ..., {\rm {\bf b} }_k ] = \alpha _{11} \left| {{\begin{array}{*{20}c}&lt;br /&gt;
 {{\alpha }'_{22} }   &amp;amp; {{\alpha }'_{23}  ...}   &amp;amp; {{\alpha }'_{2k} }   \\&lt;br /&gt;
 \vdots   &amp;amp; {  \vdots    ...}   &amp;amp; \vdots   \\&lt;br /&gt;
 {{\alpha }'_{k2} }   &amp;amp; {{\alpha }'_{k3}  ...}   &amp;amp; {{\alpha }'_{kk} }   \\&lt;br /&gt;
\end{array} } } \right| = 0,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
следовательно,&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\det {\rm {\bf \bar {A} } } = \left| {{\begin{array}{*{20}c}&lt;br /&gt;
 {{\alpha }'_{22} }   &amp;amp; {{\alpha }'_{23}  ...}   &amp;amp; {{\alpha }'_{2k} }   \\&lt;br /&gt;
 \vdots   &amp;amp; {  \vdots    ...}   &amp;amp; \vdots   \\&lt;br /&gt;
 {{\alpha }'_{k2} }   &amp;amp; {{\alpha }'_{k3}  ...}   &amp;amp; {{\alpha }'_{kk} }   \\&lt;br /&gt;
\end{array} } } \right| = 0.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По индукционному предположению $rank{\rm {\bf \bar {A} } } &amp;lt; k - 1$, т. е. строки $({\alpha }'_{22}, {\alpha }'_{23}, ..., {\alpha }'_{2k} )$, ..., $({\alpha }'_{k2}, {\alpha }'_{k3}, ..., {\alpha }'_{kk} )$ линейно зависимы. &lt;br /&gt;
Но тогда линейно зависимыми являются ${\rm {\bf b} }_2, {\rm {\bf b} }_3, ..., {\rm {\bf b} }_k $, следовательно, существуют числа $\gamma _2, \gamma _3, ..., \gamma _k $ не равные нулю одновременно, что $\gamma _2 {\rm {\bf b} }_2 + \gamma _3 {\rm {\bf b} }_3 + ... + \gamma _k {\rm {\bf b} }_k = 0$. &lt;br /&gt;
Поэтому справедливо соотношение&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\left( { - \gamma _2 \frac{\alpha _{21} }{\alpha _{11} } - \cdots - \gamma _k \frac{\alpha _{k1} }{\alpha _{11} } } \right){\bf a}_1 + \gamma _2 {\bf a}_2 + \gamma _3 {\bf a}_3 + ... + \gamma _k {\rm {\bf a} }_k = 0,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
отсюда следует линейная зависимость ${\bf a}_1, {\bf a}_2, ..., {\bf a}_k $, т. е. $rank[{\bf a}_1, {\bf a}_2,..., {\bf a}_k ] &amp;lt; k$.&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Ранг матрицы равен наибольшему порядку миноров, отличных от нуля.&lt;br /&gt;
|proof=Допустим, что $rank{\bf a} = k$. Тогда в матрице ${\bf a}$ существует ровно $k$ линейно независимых строк, образующих строчечный базис, и $k$ линейно независимых столбцов, составляющих столбцовый базис. Поэтому минор $k-$го порядка, построенный на указанных строках и столбцах, очевидно, не равен нулю. При этом все миноры порядка $k + 1$ и выше (если таковые имеются) будут равны нулю, как содержащие линейно зависимые строки и линейно зависимые столбцы.&lt;br /&gt;
}}&lt;br /&gt;
[[Файл:p03-06-02-rank-minor.svg|thumb|800px|alt=Матрица с выделенным минором 2 × 2, равным 1, и карточки: det A ≠ 0 при rank A = n, det A = 0 при rank A &amp;lt; n|Рис. 2. Минор второго порядка &amp;lt;math&amp;gt;M_2 = 1 \neq 0&amp;lt;/math&amp;gt; даёт &amp;lt;math&amp;gt;\operatorname{rank} A \ge 2&amp;lt;/math&amp;gt;, а все миноры третьего порядка равны нулю, так как содержат зависимые строки. Справа: для квадратной матрицы &amp;lt;math&amp;gt;\det A \neq 0 \iff \operatorname{rank} A = n&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;\det A = 0 \iff \operatorname{rank} A &amp;lt; n&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=Ранг произведения двух матриц не превосходит ранг каждого из сомножителей.&lt;br /&gt;
|proof=Пусть даны произвольные матрицы ${\bf a} = [{\bf a}_1, ..., {\bf a}_m ] = [{\bf a}^1, ..., {\rm {\bf a} }^n] = \{\alpha _{ij} \}_{i,j = 1}^{m,n} $, ${\rm {\bf B} } = [{\rm {\bf b} }_1, ..., {\rm {\bf b} }_n ] = [{\rm {\bf b} }^1,..., {\rm {\bf b} }^r] = \{\beta _{jk} \}_{j,k = 1}^{n,r} $. Тогда произведение&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\rm {\bf AB} } = [{\rm {\bf Ab} }^1,..., {\rm {\bf Ab} }^r] = \left[ {\sum\limits_{i = 1}^n {\beta _{i1} {\bf a}^i,..., } \sum\limits_{i = 1}^n {\beta _{ir} {\bf a}^i} } \right],&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
следовательно, каждый столбец произведения есть линейная комбинация столбцов матрицы ${\bf a}$, поэтому $rank({\rm {\bf AB} }) \le rank{\rm {\bf A} }$. Аналогично, имеем&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\rm {\bf AB} } = [{\bf a}_1 {\rm {\bf B} },..., {\bf a}_m {\rm {\bf B} }] = \left[ {\sum\limits_{j = 1}^n {\alpha _{1j} {\rm {\bf b} }_j, ..., \sum\limits_{j = 1}^n {\alpha _{mj} {\rm {\bf b} }_j } } } \right],&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
отсюда каждая строка произведения равна линейной комбинации строк матрицы ${\rm {\bf B} }$, т. е. $rank({\rm {\bf AB} }) \le rank{\rm {\bf B} }$.&lt;br /&gt;
}}&lt;br /&gt;
{{Лемма&lt;br /&gt;
|statement=Для произвольной невырожденной матрицы ${\rm {\bf B} }$ и любой матрицы ${\bf a}$ справедливо равенство $rank({\rm {\bf AB} }) = rank{\bf a}$.&lt;br /&gt;
|proof=Если матрица ${\rm {\bf C} } = {\rm {\bf AB} }$, то в силу леммы 3.6.2 $rank{\rm {\bf C} } \le rank{\bf a}$. Так как матрица ${\rm {\bf B} }$ по условию является невырожденной, то существует обратная матрица ${\rm {\bf B} }^{ - 1}$. Умножим матрицу ${\rm {\bf C} }$ справа на ${\rm {\bf B} }^{ - 1}$, получаем ${\rm {\bf CB} }^{ - 1} = {\rm {\bf ABB} }^{ - 1} = {\rm {\bf AE} } = {\bf a}$, следовательно, $rank{\rm {\bf C} } \ge rank{\bf a}$. Таким образом, справедливо равенство $rank{\rm {\bf C} } = rank{\bf a}$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-06-02-rank-minor.svg&amp;diff=953</id>
		<title>Файл:P03-06-02-rank-minor.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-06-02-rank-minor.svg&amp;diff=953"/>
		<updated>2026-11-04T18:27:43Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-06-01-row-column-rank.svg&amp;diff=952</id>
		<title>Файл:P03-06-01-row-column-rank.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-06-01-row-column-rank.svg&amp;diff=952"/>
		<updated>2026-11-04T18:27:36Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/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%B9_%D1%81%D1%82%D0%B5%D0%BA&amp;diff=951</id>
		<title>Персистентный стек</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/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%B9_%D1%81%D1%82%D0%B5%D0%BA&amp;diff=951"/>
		<updated>2026-11-04T18:07:13Z</updated>

		<summary type="html">&lt;p&gt;СВ: /* Источники */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентный стек''' (persistent stack) — стек, хранящий '''все свои предыдущие версии''': каждая операция не изменяет структуру, а создаёт новую версию, оставляя старые доступными. Реализуется как '''односвязный список''': узел хранит значение и ссылку &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; на предыдущую версию, а «текущая версия» — это просто ссылка на верхний узел. &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт один новый узел за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает значение и ссылку на предыдущую версию — тоже &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и '''без новых ячеек'''. &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создают ровно &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; новых узлов, а вместе с корнем в структуре &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел, потому что все версии '''разделяют общий хвост''' (sharing): память &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, а не &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;. Все узлы неизменяемы, поэтому персистентный стек — это '''функциональная''' персистентность; он служит строительным блоком для персистентной очереди ([[Очередь на шести стеках]]), версионирования и неизменяемых списков.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Свойство !! Значение&lt;br /&gt;
|-&lt;br /&gt;
| Класс || Стек (LIFO) с полной персистентностью; функциональная структура данных&lt;br /&gt;
|-&lt;br /&gt;
| Версии || Хранятся все; доступ к версии по ссылке за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, материализация — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; времени, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; времени, 0 новых ячеек (список) / 1 ячейка (массив)&lt;br /&gt;
|-&lt;br /&gt;
| Память || &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; под &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; узлов; &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; → &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; новых узлов (вместе с корнем &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| Изменяемость || нет; узлы инициализируются один раз и не меняются&lt;br /&gt;
|-&lt;br /&gt;
| Представления || односвязный список (&amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;) или массив запросов&lt;br /&gt;
|-&lt;br /&gt;
| Разделение || версии делят общие хвосты; ветвление даёт дерево версий&lt;br /&gt;
|-&lt;br /&gt;
| Персистентность || функциональная (сильнейшая форма)&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается || функциональные языки (списки), персистентная очередь, undo/redo, версионирование&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных''' — структура, сохраняющая доступ ко всем предшествующим версиям при выполнении операций. Различают три уровня:&lt;br /&gt;
&lt;br /&gt;
* '''частичная персистентность''' (partial): можно изменять только самую новую версию, но читать — любую;&lt;br /&gt;
* '''полная персистентность''' (full): изменять и читать можно любую версию;&lt;br /&gt;
* '''функциональная персистентность''' (functional): каждая ячейка памяти инициализируется один раз и больше не изменяется, поэтому версии разделяют неизменяемые данные.&lt;br /&gt;
&lt;br /&gt;
'''Версия стека''' — конкретное состояние стека (упорядоченная последовательность элементов). В персистентном стеке версия отождествляется с узлом-вершиной.&lt;br /&gt;
&lt;br /&gt;
'''Узел (node)''' — запись &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;, где &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; — значение на вершине, а &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; — ссылка на предыдущую версию (узел ниже) или &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; для пустой версии. Узел неизменяем.&lt;br /&gt;
&lt;br /&gt;
'''Текущая вершина''' (&amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;) — ссылка на узел, представляющий актуальную версию. Изменение стека не переписывает узлы, а лишь заменяет &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Разделение хвоста (sharing)''' — ситуация, когда несколько версий ссылаются на один и тот же узел (и, следовательно, на общий остаток стека). Безопасно только при неизменяемости узлов.&lt;br /&gt;
&lt;br /&gt;
'''Две реализации.''' ''Списковая'': узлы связаны ссылками. ''Массивная'' (массив запросов): &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет ячейку &amp;lt;code&amp;gt;{x, i}&amp;lt;/code&amp;gt;, а &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — копию версии &amp;lt;code&amp;gt;s[i].prev&amp;lt;/code&amp;gt; (той, что лежит под вершиной); номер новой версии равен &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — число элементов/операций; &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; — число версий; &amp;lt;code&amp;gt;s[i]&amp;lt;/code&amp;gt; — ячейка массива запросов с номером &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный.&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Обычный стек из [[Стек]] хранит '''одно''' состояние: &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; записывает элемент в ячейку, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; её очищает. Прошлые состояния теряются. Если нужно вернуться к предыдущему состоянию — приходится заранее копировать стек целиком, а это &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; времени и памяти на каждую версию и &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; суммарно. См. рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-01-idea.svg|thumb|700px|Рис. 1. Мутирующий стек хранит единственное состояние: &amp;lt;code&amp;gt;push(d)&amp;lt;/code&amp;gt; теряет прежнюю версию; персистентный добавляет новый узел, сохраняя старые версии.]]&lt;br /&gt;
&lt;br /&gt;
Списковая структура стека подсказывает решение. Стек — это цепочка «верх → … → дно». Добавление элемента можно представить не как запись в общую память, а как '''новую голову цепочки''': новый узел хранит добавленное значение и ссылку на прежнюю голову. Тогда старая цепочка никуда не девается — на неё просто никто не указывает как на текущую. Аналогично &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не удаляет узел, а возвращает ссылку на следующий узел цепочки. Такой стек никогда ничего не перезаписывает.&lt;br /&gt;
&lt;br /&gt;
Ключевая идея в одном абзаце: '''вершина — это всего лишь указатель на узел, а узел неизменяем'''; поэтому «изменение» сводится к замене указателя, а вся история сохраняется как цепочка (точнее — дерево) узлов, делящих общие хвосты. Отсюда &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на операцию и &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарной памяти.&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Персистентный стек состоит из узлов &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt; и ссылки &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt; на текущую вершину (см. рис. 2). Пустая версия — это специальный корневой узел без значения; в массиве его роль играет sentinel &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-02-versions.svg|thumb|700px|Рис. 2. Версия = узел; &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет узел перед текущим, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает предыдущий узел.]]&lt;br /&gt;
&lt;br /&gt;
Версии образуют связную структуру: цепочка &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;-ссылок из любого узла ведёт вниз, к корню. Несколько версий могут указывать на один узел — это '''разделение хвостов'''. Ветвление (например, два &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; от одной и той же версии) превращает линию версий в '''дерево версий'''; именно так устроены история в редакторах и снапшоты в базах данных.&lt;br /&gt;
&lt;br /&gt;
Есть два эквивалентных представления (см. рис. 3):&lt;br /&gt;
&lt;br /&gt;
* '''Список.''' Узел хранит &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;; ссылка на узел — это версия. &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт узел за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает пару &amp;lt;code&amp;gt;(value, prev)&amp;lt;/code&amp;gt; и не создаёт узлов.&lt;br /&gt;
* '''Массив запросов.''' Ячейка &amp;lt;code&amp;gt;s[i]&amp;lt;/code&amp;gt; хранит &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt; — значение вершины версии &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; и индекс её предыдущей версии. &amp;lt;code&amp;gt;push(i, x)&amp;lt;/code&amp;gt; добавляет ячейку &amp;lt;code&amp;gt;{x, i}&amp;lt;/code&amp;gt; и возвращает её индекс &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;pop(i)&amp;lt;/code&amp;gt; возвращает значение &amp;lt;code&amp;gt;s[i].value&amp;lt;/code&amp;gt; и создаёт новую версию — копию версии &amp;lt;code&amp;gt;s[i].prev&amp;lt;/code&amp;gt;, лежащей под вершиной.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-03-array.svg|thumb|700px|Рис. 3. Массив запросов: каждая ячейка = {value, prev}; операция добавляет ровно одну ячейку.]]&lt;br /&gt;
&lt;br /&gt;
Массивное представление удобно, когда версии нумеруются подряд и нужен быстрый доступ по индексу; списковое — когда версии передаются как ссылки, а структура используется как неизменяемое значение (например, список в функциональном языке).&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
=== Операция: Push ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Создать новый узел со значением &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и ссылкой на текущую вершину; объявить его новой вершиной. Прежняя версия сохраняется без изменений.&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание (список):'''&lt;br /&gt;
&lt;br /&gt;
# Создать узел &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; со значением &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и ссылкой &amp;lt;code&amp;gt;prev ← top&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Присвоить &amp;lt;code&amp;gt;top ← k&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Вернуть новую вершину (новую версию).&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Цепочка &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;-ссылок из &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt; перечисляет элементы стека сверху вниз; все ранее созданные узлы сохраняют свои поля.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push(s, x)&lt;br /&gt;
Вход:  персистентный стек s (ссылка top на вершину); x — элемент&lt;br /&gt;
Выход: новая версия стека (ссылка на новый узел)&lt;br /&gt;
Инвариант: элементы версии = цепочка top → prev → … → корень&lt;br /&gt;
Сложность: O(1) времени, O(1) новых ячеек&lt;br /&gt;
&lt;br /&gt;
1  k ← новый узел&lt;br /&gt;
2  k.value ← x&lt;br /&gt;
3  k.prev ← s.top&lt;br /&gt;
4  s.top ← k&lt;br /&gt;
5  return k&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Массивная версия:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push-Array(s, i, x)&lt;br /&gt;
Вход:  массив s запросов; i — номер версии, от которой добавляем; x — элемент&lt;br /&gt;
Выход: номер новой версии&lt;br /&gt;
Сложность: O(1)&lt;br /&gt;
&lt;br /&gt;
1  s.top ← s.top + 1&lt;br /&gt;
2  s[s.top].value ← x&lt;br /&gt;
3  s[s.top].prev ← i&lt;br /&gt;
4  return s.top&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Стек, представленный версией 2 (&amp;lt;code&amp;gt;value = 3, prev = 1&amp;lt;/code&amp;gt;), при &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt; получает ячейку 3 (&amp;lt;code&amp;gt;value = 5, prev = 2&amp;lt;/code&amp;gt;). Версия 2 не изменилась: &amp;lt;code&amp;gt;s[2]&amp;lt;/code&amp;gt; по-прежнему &amp;lt;code&amp;gt;{3, 1}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и на списке, и на массиве: одна запись, одна ссылка.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; в пустую версию даёт узел со ссылкой на корень. Если требуется добавить элемент в ''прошлую'' версию, создаётся ветвь — текущая линия не затрагивается.&lt;br /&gt;
&lt;br /&gt;
См. рис. 1 и рис. 2.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Pop ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Вернуть значение текущей вершины и «перейти» на предыдущую версию. В списковой реализации это просто возврат ссылки &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;; в массивной — запись новой ячейки, повторяющей предыдущую версию (см. рис. 4).&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание (список):'''&lt;br /&gt;
&lt;br /&gt;
# Если стек пуст — ошибка &amp;lt;code&amp;gt;underflow&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Запомнить значение &amp;lt;code&amp;gt;x ← top.value&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Перейти к предыдущей версии: &amp;lt;code&amp;gt;top ← top.prev&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Вернуть пару &amp;lt;code&amp;gt;(x, top)&amp;lt;/code&amp;gt; — значение и новую версию.&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Новая версия содержит все элементы исходной, кроме верхнего; исходная версия остаётся доступной по прежней ссылке.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop(s)&lt;br /&gt;
Вход:  персистентный стек s (top ≠ корень)&lt;br /&gt;
Выход: пара (x, s) — верхний элемент и новая версия s&lt;br /&gt;
Сложность: O(1) времени, 0 новых ячеек&lt;br /&gt;
&lt;br /&gt;
1  if s.top = корень&lt;br /&gt;
2      error &amp;quot;попытка pop из пустого стека&amp;quot;&lt;br /&gt;
3  x ← s.top.value&lt;br /&gt;
4  s.top ← s.top.prev&lt;br /&gt;
5  return (x, s)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Массивная версия (копирует предыдущую ячейку):'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop-Array(s, i)&lt;br /&gt;
Вход:  массив s; i — номер версии (i ≠ корень)&lt;br /&gt;
Выход: (значение, номер новой версии)&lt;br /&gt;
Сложность: O(1)&lt;br /&gt;
&lt;br /&gt;
1  if s[i].prev = null&lt;br /&gt;
2      error &amp;quot;pop из пустого стека&amp;quot;&lt;br /&gt;
3  x ← s[i].value&lt;br /&gt;
4  k ← s[s[i].prev]                 # узел под вершиной&lt;br /&gt;
5  Push-Array(s, k.prev, k.value)   # новая ячейка = копия версии под i&lt;br /&gt;
6  return (x, s.top)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; — sentinel пустой версии (в C++-реализации ниже он кодируется индексом 0). Поэтому &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; от версии из одного элемента создаёт новую пустую ячейку с &amp;lt;code&amp;gt;prev = null&amp;lt;/code&amp;gt; (в трассировке — ячейка 7).&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Для &amp;lt;code&amp;gt;s = [a, b, c]&amp;lt;/code&amp;gt; (вершина &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt;) операция &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; вернёт &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; и версию &amp;lt;code&amp;gt;[a, b]&amp;lt;/code&amp;gt;; версия &amp;lt;code&amp;gt;[a, b, c]&amp;lt;/code&amp;gt; сохраняется. В массивной версии &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt; для ячейки 3 вернул &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt; и завёл ячейку 4, повторяющую версию 2.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; в списке ноль новых ячеек, в массиве — ровно одна.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из пустой версии — ошибка. Если &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; применяется к «прошлой» версии, новые ячейки пишутся в конец массива, а прежние ветви остаются нетронутыми.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-04-pop.svg|thumb|700px|Рис. 4. &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не удаляет данные: он лишь указывает на предыдущую версию.]]&lt;br /&gt;
&lt;br /&gt;
=== Операция: Top и Empty ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Вершина и признак пустоты — чтение за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Top(s)&lt;br /&gt;
Вход:  персистентный стек s&lt;br /&gt;
Выход: значение верхнего элемента&lt;br /&gt;
Инвариант: s.top — вершина текущей версии&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  if s.top = корень&lt;br /&gt;
2      error &amp;quot;вершина пустого стека не определена&amp;quot;&lt;br /&gt;
3  return s.top.value&lt;br /&gt;
&lt;br /&gt;
Операция Empty(s)&lt;br /&gt;
Вход:  персистентный стек s&lt;br /&gt;
Выход: true, если текущая версия пуста&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  return (s.top = корень)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; пустой версии — ошибка; &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; для корня возвращает &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Доступ к произвольной версии ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Версия — это ссылка (или индекс), поэтому «переход» к ней бесплатен. Материализация (выписывание всех элементов версии) стоит &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция To-List(v)                 # материализация версии v&lt;br /&gt;
Вход:  вершина v (версия стека)&lt;br /&gt;
Выход: список значений сверху вниз&lt;br /&gt;
Инвариант: v — ещё не пройденная часть версии&lt;br /&gt;
Сложность: O(k) времени и O(k) памяти для k элементов&lt;br /&gt;
&lt;br /&gt;
1  out ← пустой список&lt;br /&gt;
2  while v ≠ корень&lt;br /&gt;
3      out.append(v.value)&lt;br /&gt;
4      v ← v.prev&lt;br /&gt;
5  return out&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов; при этом версии не мешают друг другу.&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Время !! Новых ячеек !! Память&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || +1 узел&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 (список) / 1 (массив) || 0 (список) / 1 ячейка (массив)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| Доступ к версии || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| Материализация версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарно || — || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; узлов&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Оговорка: в списковой и массивной моделях '''амортизация не нужна''' — каждая операция уже &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. В C++-реализации ниже используется &amp;lt;code&amp;gt;std::vector&amp;lt;/code&amp;gt;: его &amp;lt;code&amp;gt;push_back&amp;lt;/code&amp;gt; даёт амортизированный &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, поэтому для строгого худшего случая нужен заранее выделенный пул с резервом. Суммарная память пропорциональна '''числу созданных версий''', а не их размерам, потому что версии делят общие хвосты.&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Повторим последовательность из источника для массивной реализации: &amp;lt;code&amp;gt;push(1, 3)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(3, 6)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(5, 1)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(5)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(7, 9)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! index !! value !! prev !! Операция&lt;br /&gt;
|-&lt;br /&gt;
| 1 || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || начальная пустая версия&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 3 || 1 || &amp;lt;code&amp;gt;push(1, 3)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 5 || 2 || &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 3 || 1 || &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt;: вернул 5, скопировал версию 2&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 6 || 3 || &amp;lt;code&amp;gt;push(3, 6)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 6 || 1 || 5 || &amp;lt;code&amp;gt;push(5, 1)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 7 || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt;: вернул 3, скопировал версию 1 (пустую)&lt;br /&gt;
|-&lt;br /&gt;
| 8 || 5 || 2 || &amp;lt;code&amp;gt;pop(5)&amp;lt;/code&amp;gt;: вернул 6, скопировал версию 3&lt;br /&gt;
|-&lt;br /&gt;
| 9 || 9 || 7 || &amp;lt;code&amp;gt;push(7, 9)&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Разберём строку 4: &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt; читает ячейку 3 (&amp;lt;code&amp;gt;value = 5, prev = 2&amp;lt;/code&amp;gt;), возвращает &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;, а новой версией делает копию ячейки под ней — ячейка 4 повторяет версию 2 (&amp;lt;code&amp;gt;value = 3, prev = 1&amp;lt;/code&amp;gt;). Строка 7: &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt; возвращает &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;, а новой версией становится пустая (ячейка 7 повторяет корень). Все прошлые ячейки (1–6) остаются доступными: в массиве запросов ничего не перезаписывается.&lt;br /&gt;
&lt;br /&gt;
== Корректность и оценка сложности ==&lt;br /&gt;
&lt;br /&gt;
=== Инвариант ===&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Для любого узла &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; цепочка &amp;lt;math&amp;gt;v, \mathrm{prev}(v), \mathrm{prev}^2(v), \dots&amp;lt;/math&amp;gt; заканчивается корнем, и значения узлов вдоль цепочки образуют содержимое версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; сверху вниз.&lt;br /&gt;
&lt;br /&gt;
'''База.''' Корень — пустая версия, цепочка длины 0.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;.''' Новый узел &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; указывает на прежнюю вершину &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, поэтому цепочка из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; есть &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; + цепочка из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;; значения — &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; + содержимое &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, что и требуется. Ни один старый узел не изменён.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;.''' Новая вершина — &amp;lt;math&amp;gt;\mathrm{prev}(v)&amp;lt;/math&amp;gt;; её цепочка есть содержимое версии без верхнего элемента. Исходная ссылка &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; сохраняет старый стек.&lt;br /&gt;
&lt;br /&gt;
Отсюда &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; возвращает верхний элемент, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; корректно распознаёт корень, а любая версия читается за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по ссылке (переход к прошлому состоянию — операция над указателем).&lt;br /&gt;
&lt;br /&gt;
=== Оценка памяти: разделение хвостов ===&lt;br /&gt;
&lt;br /&gt;
Каждый &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт '''ровно один''' новый узел; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в списковой реализации не создаёт узлов вовсе. Значит, после &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; в структуре &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел (включая корень), то есть &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; памяти — независимо от того, сколько версий сохранено и каковы их размеры (см. рис. 5). Если бы каждая версия хранилась отдельным массивом, суммарная память была бы &amp;lt;math&amp;gt;1 + 2 + \dots + n = \Theta(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-05-sharing.svg|thumb|700px|Рис. 5. Ветви делят общий хвост: &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; дают &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел, а не &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; памяти.]]&lt;br /&gt;
&lt;br /&gt;
=== Время ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; выполняют постоянное число операций над указателями и записями — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. Материализация версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов требует прохода по &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; узлам — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;; это неизбежно, поскольку выписываются &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; значений.&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Структура !! &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; !! &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; !! Доступ к версиям !! Память под &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версий&lt;br /&gt;
|-&lt;br /&gt;
| Обычный стек ([[Стек]]) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || нет (одно состояние) || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Обычный стек + полное копирование || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; + &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; копия || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || да || &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| '''Персистентный стек (список)''' || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 узел || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 0 узлов || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Персистентный стек (массив запросов) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 ячейка || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 ячейка || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Персистентная очередь ([[Очередь на шести стеках]]) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарно (&amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых на операцию)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Вердикт.''' Нужны прошлые состояния стека — только персистентная реализация; копирование неприемлемо из-за &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt;. Между списком и массивом выбор по стилю: список удобнее для функционального кода и передачи версий как значений, массив — для нумерованных версий и индексного доступа. Персистентная очередь — следующий шаг: стек — её элементарный кирпич.&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки и неизменяемые списки.''' Односвязный список в LISP, Haskell, OCaml, Standard ML — это персистентный стек: добавление в голову за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, хвосты разделяются. Почему: скрытая мутация в чистом функциональном языке невозможна, а неизменяемый список её и не требует [2][3].&lt;br /&gt;
&lt;br /&gt;
# '''Персистентная очередь и другие персистентные структуры.''' Делая стек персистентным, мы получаем строительный блок для очереди ([[Очередь на шести стеках]]), дека и приоритетной очереди. Почему: многие структуры сводятся к комбинации стеков/списков [1][2].&lt;br /&gt;
&lt;br /&gt;
# '''Undo/redo и версионирование.''' Редакторы, СУБД с MVCC, системы контроля версий и транзакционные снапшоты хранят историю как версии структуры; откат — переход по ссылке без копирования данных [3][4].&lt;br /&gt;
&lt;br /&gt;
# '''Обход и разбор с ветвлением.''' В backtracking, разборе выражений и DFS путь можно хранить как персистентный стек: каждое ветвление — новая версия, а возврат в точку выбора — переход по сохранённой ссылке. Почему: состояние стека восстанавливается за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; [2].&lt;br /&gt;
&lt;br /&gt;
# '''Параллельные и распределённые вычисления.''' Неизменяемые данные естественно разделяются между потоками и репликами без блокировок: структура, разделяемая несколькими потребителями, не может быть испорчена мутацией [3][4].&lt;br /&gt;
&lt;br /&gt;
Обзор основных областей — на рис. 6.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-06-apply.svg|frame|none|700px|Рис. 6. Персистентная очередь, версионирование и undo, функциональные списки, история обхода и разбора, параллельные вычисления — пять областей, где персистентный стек служит фундаментом.]]&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Идея функциональных списков-стеков восходит к языку '''LISP''' (Джон Маккарти, 1958–1960): операции &amp;lt;code&amp;gt;cons&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;car&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cdr&amp;lt;/code&amp;gt; строят разделяемые неизменяемые цепочки — фактически персистентный стек [2]. Термин «персистентные структуры данных» и систематическое изучение методов (путь-копирование, fat nodes, node-copying) ввели '''Дрисколл, Сарнак, Слитор и Тарьян''' в работе ''Making Data Structures Persistent'' (STOC 1986; журнальная версия — JCSS, 1989) [4]. Формальный амортизированный анализ, которым описывают стоимость операций, развил '''Роберт Тарьян''' («Amortized Computational Complexity», 1985) [5].&lt;br /&gt;
&lt;br /&gt;
Учебное изложение персистентного стека (список и массив запросов) — конспект кафедры компьютерных технологий ИТМО на «Викиконспектах» [1]; оттуда же распространён разбор персистентной очереди. Понятие «функциональная персистентность» и коллекция персистентных структур систематизированы '''Крисом Окасаки''' в книге ''Purely Functional Data Structures'' (Cambridge University Press, 1998) [3].&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Путать персистентность со снапшотом-копией.''' Персистентный стек не копирует содержимое при каждой версии: узлы общие, копируется только «верх» (один узел).&lt;br /&gt;
* '''Мутировать узел.''' Изменение &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; существующего узла немедленно испортит все версии, которые через него проходят. Узлы обязаны быть неизменяемыми.&lt;br /&gt;
* '''Освобождать память «оптимизацией».''' Удаление узлов из-под старых версий ломает персистентность; сборка мусора нужна, но только когда на узел не ссылается ни одна версия.&lt;br /&gt;
* '''Считать память пропорциональной числу версий, умноженному на их размер.''' Правильная оценка — &amp;lt;math&amp;gt;O(\text{число операций})&amp;lt;/math&amp;gt; благодаря разделению хвостов.&lt;br /&gt;
* '''Ожидать &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; от &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в массивной реализации без учёта новой ячейки.''' В массиве &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; добавляет запись; это всё ещё &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но память растёт на каждую модификацию.&lt;br /&gt;
* '''Переполнение/уход индексов в массиве запросов.''' Версии нумеруются монотонно; смешение «номера версии» и «индекса ячейки» при ветвлении приводит к ошибкам.&lt;br /&gt;
* '''Смешивать ''current'' и ''версию''.''' Возвращая из операции узел, а не пару «результат + новая версия», легко потерять возможность продолжать от прежней версии.&lt;br /&gt;
* '''Забыть про корень.''' Пустая версия должна быть корректным состоянием: без корня &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; до конца и &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; работают неверно.&lt;br /&gt;
* '''Ожидать, что &amp;lt;code&amp;gt;To-List&amp;lt;/code&amp;gt; бесплатен.''' Материализация версии — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;; путать её с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; доступом к самой версии нельзя.&lt;br /&gt;
* '''Полагать, что персистентность автоматически даёт быстрый произвольный &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;.''' Для очереди на двух стеках этого мало — требуется дробление переноса (см. [[Очередь на шести стеках]]).&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Базовые операции.''' Реализуйте персистентный стек на списке (&amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;) с &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt;. Ограничение: узлы неизменяемы. ''Подсказка:'' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает пару «значение + новая версия».&lt;br /&gt;
&lt;br /&gt;
# '''Ветвление.''' В списковой реализации от версии &amp;lt;code&amp;gt;[a, b, c]&amp;lt;/code&amp;gt; примените &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, затем &amp;lt;code&amp;gt;push(d)&amp;lt;/code&amp;gt;, затем ещё раз &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Сколько версий существует и какие у них содержимые? ''Подсказка:'' постройте дерево версий.&lt;br /&gt;
&lt;br /&gt;
# '''Память.''' Для списковой реализации докажите, что &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и любые &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; порождают не более &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узлов. ''Подсказка:'' каждый &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет ровно один узел, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — ноль.&lt;br /&gt;
&lt;br /&gt;
# '''Массив запросов.''' Реализуйте массивную версию и воспроизведите трассировку из раздела «Трассировка примера». Сверьте значения &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Материализация.''' Напишите &amp;lt;code&amp;gt;To-List(v)&amp;lt;/code&amp;gt; и оцените время; добейтесь &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов. ''Подсказка:'' идти по &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; от вершины к корню.&lt;br /&gt;
&lt;br /&gt;
# '''Общий хвост.''' Для двух версий найдите длину общего хвоста, используя только поля &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;; добейтесь времени &amp;lt;math&amp;gt;O(\ell_1 + \ell_2)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\ell_i&amp;lt;/math&amp;gt; — длины версий. ''Дополнительно:'' добавьте &amp;lt;code&amp;gt;depth&amp;lt;/code&amp;gt; и двоичные подъёмы (binary lifting); оцените preprocessing и время запроса LCA. ''Подсказка:'' без глубины сначала найдите глубины, затем выровняйте вершины и идите вверх вместе.&lt;br /&gt;
&lt;br /&gt;
# '''Персистентная очередь.''' Используя персистентный стек, постройте персистентную очередь и оцените худшее время &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. ''Подсказка:'' два стека; обсудите, почему нужен &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае.&lt;br /&gt;
&lt;br /&gt;
# '''Сравнение.''' Сравните сумму памяти для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версий при (а) полном копировании, (б) списковой персистентности, (в) массивной. Приведите асимптотики и пример численно при &amp;lt;math&amp;gt;n = 1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Стек]] — базовый LIFO-стек; мутирующая реализация.&lt;br /&gt;
* [[Персистентная очередь]] — персистентность очереди целиком.&lt;br /&gt;
* [[Очередь на шести стеках]] — очередь реального времени на шести стеках.&lt;br /&gt;
* [[Асимптотическая сложность]] — оценки &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, амортизация, худший случай.&lt;br /&gt;
* [[Персистентные структуры данных]] — общая теория: путь-копирование, fat nodes.&lt;br /&gt;
* [[Декартово дерево]] — персистентный treap; ещё один пример персистентности.&lt;br /&gt;
* [[Двоичное дерево поиска]] — персистентные структуры на деревьях.&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Викиконспекты ИТМО: Персистентный стек (реализации на массиве и на списке, пример). URL: https://neerc.ifmo.ru/wiki/index.php?title=Персистентный_стек.&lt;br /&gt;
# McCarthy J. Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I // Communications of the ACM. — 1960. — Vol. 3, № 4 (основы LISP и списков).&lt;br /&gt;
# Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998.&lt;br /&gt;
# Driscoll J. R., Sarnak N., Sleator D. D., Tarjan R. E. Making Data Structures Persistent // STOC 1986; журнальная версия: Journal of Computer and System Sciences. — 1989. — Vol. 38.&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Cormen T. H., Leiserson C. E., Rivest R. L., Stein C. ''Introduction to Algorithms'' (CLRS). 3rd ed. — MIT Press, 2009. — Гл. 10 (элементарные структуры: стек, список).&lt;br /&gt;
# Хабрахабр. Персистентные структуры, часть 1: персистентный стек (пост 113585). URL: https://habr.com/ru/post/113585/.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Алгоритмы и структуры данных]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C_%D0%BD%D0%B0_%D1%88%D0%B5%D1%81%D1%82%D0%B8_%D1%81%D1%82%D0%B5%D0%BA%D0%B0%D1%85&amp;diff=950</id>
		<title>Очередь на шести стеках</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C_%D0%BD%D0%B0_%D1%88%D0%B5%D1%81%D1%82%D0%B8_%D1%81%D1%82%D0%B5%D0%BA%D0%B0%D1%85&amp;diff=950"/>
		<updated>2026-11-04T18:07:04Z</updated>

		<summary type="html">&lt;p&gt;СВ: /* Источники */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
Обычная очередь на '''двух стеках''' работает амортизированно за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но отдельная операция может стоить &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;: когда выходной стек пуст, приходится разом перевернуть весь входной. '''Очередь на шести стеках''' (real-time queue) распределяет этот перенос на маленькие порции — не более '''3 действий за операцию''' — и тем самым даёт '''&amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае''' на каждую операцию, то есть работает в реальном времени. Стеки &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt; и переменные &amp;lt;math&amp;gt;\mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}&amp;lt;/math&amp;gt; следят за фоновым перекопированием, а копии &amp;lt;math&amp;gt;R_c, R_c'&amp;lt;/math&amp;gt; обслуживают &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, пока выход перестраивается. Из этой конструкции одной заменой &amp;lt;math&amp;gt;(R_c, R_c') \to R'&amp;lt;/math&amp;gt; получается '''персистентная очередь на пяти стеках''': каждая версия занимает &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек, а операции остаются &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае.&lt;br /&gt;
&lt;br /&gt;
== Инфобокс ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Свойство !! Значение&lt;br /&gt;
|-&lt;br /&gt;
| Класс || Очередь (FIFO) в реальном времени; на стеках&lt;br /&gt;
|-&lt;br /&gt;
| Порядок доступа || FIFO&lt;br /&gt;
|-&lt;br /&gt;
| Худший случай || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Амортизированный || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (совпадает с худшим — амортизация не нужна)&lt;br /&gt;
|-&lt;br /&gt;
| Память || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; под элементы; &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; переноса на операцию (не более 6 стеков)&lt;br /&gt;
|-&lt;br /&gt;
| Стеки || &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Переменные || &amp;lt;math&amp;gt;\mathtt{recopy}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{copied}&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Режимы || обычный и перекопирования&lt;br /&gt;
|-&lt;br /&gt;
| Сравнение || 2 стека — амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, худший &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; 6 стеков — худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; 5 стеков — ещё и персистентность&lt;br /&gt;
|-&lt;br /&gt;
| Авторы идеи || Hood, Melville (1980); персистентный вариант — учебная традиция ИТМО, Okasaki&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается || функциональные языки, персистентные структуры, системы реального времени, версионирование&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Очередь''' (см. [[Очередь]] (план)) — коллекция с порядком '''FIFO''': &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет в конец, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; забирает из начала. Реализуется кольцевым буфером, списком или двумя стеками.&lt;br /&gt;
&lt;br /&gt;
'''Реализация на двух стеках.''' Входной стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; принимает &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, выходной &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; обслуживает &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Когда &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; пуст, все элементы &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переворачиваются в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; разбирается сверху, каждый элемент кладётся в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;. Разовый перенос стоит &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;, поэтому &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; здесь — '''амортизированный''', а не худший.&lt;br /&gt;
&lt;br /&gt;
'''Очередь в реальном времени (real-time queue).''' Очередь, у которой каждая операция выполняется за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; '''в худшем случае''', без «долгих» вызовов. Синоним: очередь с ограниченным (постоянным) временем отклика.&lt;br /&gt;
&lt;br /&gt;
'''Перекопирование (recopy mode).''' Фоновый режим, в котором часть работы переноса &amp;lt;math&amp;gt;(L \to R)&amp;lt;/math&amp;gt; выполняется маленькими порциями параллельно с обычными операциями. Начинается, когда &amp;lt;math&amp;gt;L.size &amp;gt; R.size&amp;lt;/math&amp;gt;, и заканчивается, когда служебный стек &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; опустеет.&lt;br /&gt;
&lt;br /&gt;
'''Шесть стеков.''' Основные: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — вход обычного режима, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — выход обычного режима. Вспомогательные: &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; — вход во время перекопирования; &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; — буфер двойного переворота; &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копия текущего &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (из неё идут &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; во время перекопирования); &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; — копия строящегося &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Переменные состояния.''' &amp;lt;math&amp;gt;\mathtt{recopy}&amp;lt;/math&amp;gt; — булев флаг режима; &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; — сколько элементов в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; ещё «корректны» (принадлежат очереди); &amp;lt;math&amp;gt;\mathtt{copied}&amp;lt;/math&amp;gt; — начат ли перенос &amp;lt;math&amp;gt;L \to R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных.''' Структура, к которой доступны все прошлые версии. '''Функциональная персистентность''' — сильнейшая форма: ячейка памяти инициализируется один раз и больше не меняется, поэтому версии разделяют неизменяемые данные. См. [[Персистентные структуры данных]].&lt;br /&gt;
&lt;br /&gt;
'''Персистентная очередь на пяти стеках.''' Вариант, где пара &amp;lt;math&amp;gt;(R_c, R_c')&amp;lt;/math&amp;gt; заменена одной неизменяемой версией &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;, а версия очереди описывается записью &amp;lt;math&amp;gt;Q = \langle L, L', R, R', S, \mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}\rangle&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — число элементов очереди; &amp;lt;math&amp;gt;|X|&amp;lt;/math&amp;gt; — размер стека &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный. Стеки трактуются как последовательности «дно … верх».&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Очередь на двух стеках красива, но у неё есть скрытый дефект: один &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; на пустом &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; тянет за собой перенос всех элементов — &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; работы и, что важнее для персистентности, &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; '''новых ячеек памяти''' в одной операции. Для систем реального времени (планировщики, обработка сигналов, сетевые буферы) задержка должна быть ограничена сверху; один вызов, пропорциональный размеру структуры, недопустим. См. рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-01-two-stacks.svg|thumb|700px|Рис. 1. Перенос &amp;lt;math&amp;gt;L \to R&amp;lt;/math&amp;gt; происходит разом и даёт пик &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; задача — размазать его по операциям так, чтобы каждая стоила &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Ключевая идея: '''дробить дорогую работу'''. Пока очередь обслуживает обычные &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, она потихоньку готовит новый выходной стек, выполняя ровно '''3 действия''' за операцию. Если начать перекопирование заранее — когда &amp;lt;math&amp;gt;|L|&amp;lt;/math&amp;gt; лишь на единицу превысил &amp;lt;math&amp;gt;|R|&amp;lt;/math&amp;gt;, — то на перенос останется ровно столько операций, сколько нужно, и «долгих» вызовов не будет вовсе.&lt;br /&gt;
&lt;br /&gt;
Почему именно шесть стеков? Во время переноса стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; разбирается, поэтому новые &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; надо куда-то девать — так появляется &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt;. Пока строится новый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, старый выход ещё должен отвечать на &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — так появляется копия &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;. У копии тоже есть версия «на будущее» — &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Наконец, старый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; может не успеть опустеть (если операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; было много), поэтому его содержимое временно уводят в буфер &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, чтобы через двойной переворот вернуть уже над новыми элементами &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;. Итого &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt; — шесть стеков.&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Очередь хранит шесть стеков и три переменные; вместе они реализуют FIFO с постоянным временем отклика (см. рис. 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-02-roles.svg|frame|none|700px|Рис. 2. &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — вход обычного режима, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — выход; &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; принимает вход во время перекопирования, &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; — копии выхода, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; — буфер двойного переворота.]]&lt;br /&gt;
&lt;br /&gt;
Работа идёт в '''двух режимах''' (см. рис. 3):&lt;br /&gt;
&lt;br /&gt;
* '''Обычный режим.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; кладёт в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (и синхронно из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;); &amp;lt;math&amp;gt;\mathtt{empty} = (R = \varnothing)&amp;lt;/math&amp;gt;. После каждой операции проверка &amp;lt;math&amp;gt;\mathtt{recopy} \leftarrow (|L| &amp;gt; |R|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* '''Режим перекопирования.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; кладёт в &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\mathtt{empty} = \text{false}&amp;lt;/math&amp;gt;; каждая операция делает не более 3 переносов. Режим длится, пока &amp;lt;math&amp;gt;S \neq \varnothing&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-03-modes.svg|thumb|700px|Рис. 3. Переключение режимов: вход в перекопирование по &amp;lt;math&amp;gt;\lvert L\rvert &amp;gt; \lvert R\rvert&amp;lt;/math&amp;gt;, выход — по пустому &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Перекопирование не должно затянуться дольше, чем простоит старый выход, — иначе не хватит свободного стека. Гарантию дают '''инварианты''' (см. рис. 4). В обычном режиме: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — половины очереди, &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копия &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; подчищены так, что &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|L'| = |S| = 0&amp;lt;/math&amp;gt;. В режиме перекопирования: &amp;lt;math&amp;gt;|R_c| \ge \mathtt{toCopy}&amp;lt;/math&amp;gt; (равенство, пока из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; не переносили корректных элементов), и если &amp;lt;math&amp;gt;|L| = 0&amp;lt;/math&amp;gt;, то либо первые &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; элементов &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; корректны, либо (при &amp;lt;math&amp;gt;\mathtt{toCopy} \le 0&amp;lt;/math&amp;gt;) стек &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; уже содержит весь правый кусок очереди в правильном порядке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-04-invariants.svg|thumb|700px|Рис. 4. Инвариант &amp;lt;math&amp;gt;\lvert R_c'\rvert \le \lvert R\rvert - \lvert L\rvert&amp;lt;/math&amp;gt; гарантирует, что остаток &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; рассосётся по элементу за операцию до следующего перекопирования.]]&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
Ниже — операции очереди. Обозначим &amp;lt;math&amp;gt;|X| = \mathtt{X.size}&amp;lt;/math&amp;gt;; стек как объект со стандартными &amp;lt;math&amp;gt;\mathtt{push}, \mathtt{pop}&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\mathtt{swap}&amp;lt;/math&amp;gt; меняет местами переменные-стеки.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Empty ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме очередь пуста тогда и только тогда, когда пуст &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; в режиме&lt;br /&gt;
перекопирования очередь заведомо непуста (см. лемму о корректности &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; ниже).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Empty(Q)&lt;br /&gt;
Вход:  очередь Q&lt;br /&gt;
Выход: true, если элементов нет&lt;br /&gt;
Инвариант: R = ∅ ⟺ Q = ∅ (в обычном режиме)&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  return (not Q.recopy) and (|R| = 0)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Во время перекопирования &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; всегда &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, даже если все «старые» элементы уже извлечены: за время переноса выполняется не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; извлечений, а в очереди было &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; элементов, поэтому неперекопированный остаток лежит в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Push ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме элемент идёт в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;; попутно подчищаем &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; и проверяем, не пора ли начать перекопирование. В режиме перекопирования элемент идёт в &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; (стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; сейчас разбирается), и продолжаем фоновую работу.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push(Q, x)&lt;br /&gt;
Вход:  очередь Q; x — элемент&lt;br /&gt;
Выход: x добавлен в конец очереди&lt;br /&gt;
Инвариант: в обычном режиме Q = R (сверху вниз) ++ reverse(L)&lt;br /&gt;
Сложность: O(1) в худшем случае (не более 3 переносов)&lt;br /&gt;
&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      push(L, x)&lt;br /&gt;
3      if |Rc'| &amp;gt; 0&lt;br /&gt;
4          pop(Rc')                       # рассасываем остаток копии&lt;br /&gt;
5      Check-Recopy(Q)&lt;br /&gt;
6  else&lt;br /&gt;
7      push(L', x)&lt;br /&gt;
8      Check-Normal(Q)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если очередь была пуста, &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; запускает перекопирование (так как &amp;lt;math&amp;gt;|L| = 1 &amp;gt; 0 = |R|&amp;lt;/math&amp;gt;), но завершает его сразу же за один вызов, попадая в обычный режим.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Pop ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме берём верх &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (это самый старый элемент) и синхронно чистим &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;. В режиме перекопирования элемент берётся из копии &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;; если &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt;, он «оплачен» корректным элементом из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, иначе берём и из перестраиваемого &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop(Q)&lt;br /&gt;
Вход:  очередь Q&lt;br /&gt;
Выход: самый старый элемент; error, если очередь пуста&lt;br /&gt;
Инвариант: возвращаемый элемент — минимальный по времени поступления&lt;br /&gt;
Сложность: O(1) в худшем случае (не более 3 переносов)&lt;br /&gt;
&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      x ← pop(R)&lt;br /&gt;
3      pop(Rc)&lt;br /&gt;
4      if |Rc'| &amp;gt; 0&lt;br /&gt;
5          pop(Rc')&lt;br /&gt;
6      Check-Recopy(Q)&lt;br /&gt;
7      return x&lt;br /&gt;
8  else&lt;br /&gt;
9      x ← pop(Rc)&lt;br /&gt;
10     if Q.toCopy &amp;gt; 0&lt;br /&gt;
11         Q.toCopy ← Q.toCopy − 1&lt;br /&gt;
12     else&lt;br /&gt;
13         pop(R)&lt;br /&gt;
14         pop(Rc')&lt;br /&gt;
15     Check-Normal(Q)&lt;br /&gt;
16     return x&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если в режиме перекопирования &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; дошёл до нуля, значит, оставшиеся старые элементы уже продублированы в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; тогда &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; синхронно снимает элемент из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Обращение к пустой очереди — ошибка (&amp;lt;code&amp;gt;underflow&amp;lt;/code&amp;gt;), как и в любой очереди.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Check-Recopy ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' После операции в обычном режиме проверить, не перерос ли вход выход; если да — переключиться в режим перекопирования и сделать первый пакет переносов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Check-Recopy(Q)&lt;br /&gt;
1  Q.recopy ← (|L| &amp;gt; |R|)&lt;br /&gt;
2  if Q.recopy&lt;br /&gt;
3      Q.toCopy ← |R|                    # столько старых элементов ещё «в деле»&lt;br /&gt;
4      Q.copied ← false                 # перенос L → R ещё не начат&lt;br /&gt;
5      Check-Normal(Q)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Операция: Check-Normal ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Доделать пакет переносов; режим перекопирования продолжается, пока непуст &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Check-Normal(Q)&lt;br /&gt;
1  Additional-Operations(Q)&lt;br /&gt;
2  Q.recopy ← (|S| ≠ 0)                 # S пуст — перекопирование завершено&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Операция: Additional-Operations ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' За один вызов выполнить не более '''3''' действий над вспомогательными стеками (см. рис. 5). Четыре фазы:&lt;br /&gt;
&lt;br /&gt;
# '''&amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt;''' — пока &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; не пуст и &amp;lt;math&amp;gt;\mathtt{copied} = \text{false}&amp;lt;/math&amp;gt;, перекладывать &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; (временный переворот выхода).&lt;br /&gt;
# '''&amp;lt;math&amp;gt;L \to R, R_c'&amp;lt;/math&amp;gt;''' — как только начали, ставим &amp;lt;math&amp;gt;\mathtt{copied} \leftarrow \text{true}&amp;lt;/math&amp;gt;; каждый элемент &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; идёт и в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, и в &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;.&lt;br /&gt;
# '''&amp;lt;math&amp;gt;S \to R, R_c'&amp;lt;/math&amp;gt;''' — первые &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; элементов &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; возвращаются в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; (двойной переворот восстанавливает порядок &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и кладёт его '''над''' элементами &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;); остальные (уже логически извлечённые) отбрасываются.&lt;br /&gt;
# '''Смена ролей''' — если &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; пуст: &amp;lt;math&amp;gt;\mathtt{swap}(L, L')&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{swap}(R_c, R_c')&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Additional-Operations(Q)&lt;br /&gt;
Вход:  очередь Q в режиме перекопирования&lt;br /&gt;
Выход: сделано не более 3 переносов&lt;br /&gt;
Инвариант: фазы 1–4 выполняются строго по порядку&lt;br /&gt;
Сложность: O(1) (счётчик toDo = 3)&lt;br /&gt;
&lt;br /&gt;
1  toDo ← 3&lt;br /&gt;
2  while (not Q.copied) and toDo &amp;gt; 0 and |R| &amp;gt; 0&lt;br /&gt;
3      push(S, pop(R))                   # фаза 1: R → S&lt;br /&gt;
4      toDo ← toDo − 1&lt;br /&gt;
5  while toDo &amp;gt; 0 and |L| &amp;gt; 0&lt;br /&gt;
6      Q.copied ← true&lt;br /&gt;
7      x ← pop(L)&lt;br /&gt;
8      push(R, x)                        # фаза 2: L → R&lt;br /&gt;
9      push(Rc', x)                      #         и L → Rc'&lt;br /&gt;
10     toDo ← toDo − 1&lt;br /&gt;
11 while toDo &amp;gt; 0 and |S| &amp;gt; 0&lt;br /&gt;
12     x ← pop(S)                        # фаза 3: S → R, Rc'&lt;br /&gt;
13     if Q.toCopy &amp;gt; 0&lt;br /&gt;
14         push(R, x)&lt;br /&gt;
15         push(Rc', x)&lt;br /&gt;
16         Q.toCopy ← Q.toCopy − 1&lt;br /&gt;
17     toDo ← toDo − 1&lt;br /&gt;
18 if |S| = 0&lt;br /&gt;
19     swap(L, L')                       # фаза 4: смена ролей&lt;br /&gt;
20     swap(Rc, Rc')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Точные состояния стеков на примере &amp;lt;code&amp;gt;push(1..7)&amp;lt;/code&amp;gt; и последующих &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; приведены в разделе «Трассировка примера». Ключевой момент фазы 3 — двойной переворот &amp;lt;math&amp;gt;R \to S \to R&amp;lt;/math&amp;gt;: он возвращает относительный порядок старого выхода неизменным, но ставит его поверх элементов &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;; поскольку старые элементы и есть «перед» очереди, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из нового &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; выдаёт FIFO.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; уже пуст, фаза 1 пропускается и работа сразу начинается с фазы 2. Если &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; пуст, фаза 2 пропускается. Если &amp;lt;math&amp;gt;\mathtt{toCopy} = 0&amp;lt;/math&amp;gt;, элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; просто извлекаются и выбрасываются — они уже были отданы через &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-05-recopy-steps.svg|frame|none|700px|Рис. 5. За вызов &amp;lt;code&amp;gt;additionalOperations&amp;lt;/code&amp;gt; — не более 3 переносов; фазы идут строго по порядку.]]&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Худший случай !! Амортизированный !! Переносов за вызов !! Память&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Вся очередь || — || — || — || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; под элементы&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Здесь '''худший и амортизированный случаи совпадают''': амортизация не нужна, потому что никакая операция не выполняет больше трёх переносов. Для сравнения, очередь на двух стеках имеет амортизированный &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но худший &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;. В режиме перекопирования каждая «обычная» операция дополнительно тратит до 3 переносов — это уже учтено в оценке &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Проследим &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt; … &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt;, затем семь &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Стеки записаны в виде «дно … верх»; в столбце «режим»: &amp;lt;code&amp;gt;обыч.&amp;lt;/code&amp;gt; — обычный, &amp;lt;code&amp;gt;перекоп.&amp;lt;/code&amp;gt; — перекопирование. Значения получены прогоном реализации из раздела «Служебное».&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Режим !! &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; !! &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt; || обыч.→перекоп.→обыч. || — || — || 1 || 1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(2)&amp;lt;/code&amp;gt; || обыч. || 2 || — || 1 || 1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt; || обыч.→перекоп. || — || — || 3,2 || 1 || 3,2 || 1 || 1&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(4)&amp;lt;/code&amp;gt; || перекоп.→обыч. || 4 || — || 3,2,1 || 3,2,1 || 1 || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(5)&amp;lt;/code&amp;gt; || обыч. || 4,5 || — || 3,2,1 || 3,2,1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(6)&amp;lt;/code&amp;gt; || обыч. || 4,5,6 || — || 3,2,1 || 3,2,1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt; || обыч.→перекоп. || 4,5,6,7 || — || — || 3,2,1 || — || 1,2,3 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 1 || перекоп. || 4 || — || 7,6,5 || 3,2 || 7,6,5 || 1,2,3 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 2 || перекоп. || — || — || 7,6,5,4,3 || 3 || 7,6,5,4,3 || 1 || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 3 || перекоп.→обыч. || — || — || 7,6,5,4 || 7,6,5,4 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 4 || обыч. || — || — || 7,6,5 || 7,6,5 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 5 || обыч. || — || — || 7,6 || 7,6 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 6 || обыч. || — || — || 7 || 7 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 7 || обыч. || — || — || — || — || — || — || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Разберём два важных момента. Во-первых, &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt; запускает перекопирование сразу, но успевает сделать только 3 переноса (старый &amp;lt;math&amp;gt;R = \{1\}&amp;lt;/math&amp;gt; уходит в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, два элемента &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;); завершается перенос на &amp;lt;code&amp;gt;push(4)&amp;lt;/code&amp;gt;, после чего пары &amp;lt;math&amp;gt;L \leftrightarrow L'&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c \leftrightarrow R_c'&amp;lt;/math&amp;gt; меняются ролями. Во-вторых, при &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt; вход (&amp;lt;math&amp;gt;|L| = 4&amp;lt;/math&amp;gt;) перерос выход (&amp;lt;math&amp;gt;|R| = 3&amp;lt;/math&amp;gt;), начался новый перенос: &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; целиком ушёл в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;toCopy = 3&amp;lt;/code&amp;gt;. Следующие &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берут элементы из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; (копия старого &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;), пока в фоне &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переливается в новый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; когда &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt; доходит до нуля, корректная часть &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; переносится в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а остальное отбрасывается, и перекопирование заканчивается. Порядок выдачи — &amp;lt;math&amp;gt;1,2,\dots,7&amp;lt;/math&amp;gt; — ровно FIFO, и ни один &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не сделал больше трёх внутренних переносов.&lt;br /&gt;
&lt;br /&gt;
== Корректность и оценка сложности ==&lt;br /&gt;
&lt;br /&gt;
=== Инвариант и FIFO ===&lt;br /&gt;
&lt;br /&gt;
Будем считать, что в обычном режиме элементы очереди равны &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (сверху вниз) &amp;lt;math&amp;gt;\mathbin{+\!\!+}&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\mathrm{reverse}(L)&amp;lt;/math&amp;gt;. Тогда верх &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — самый старый элемент, и &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; возвращает его.&lt;br /&gt;
&lt;br /&gt;
'''Лемма (сохранение порядка при перекопировании).''' После завершения перекопирования стек &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; содержит все элементы очереди в правильном порядке для извлечения.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' Пусть до перекопирования очередь равна &amp;lt;math&amp;gt;R_0 \mathbin{+\!\!+} L_0&amp;lt;/math&amp;gt; (в указанном смысле). Фаза 1 переносит &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, разворачивая: &amp;lt;math&amp;gt;S = \mathrm{reverse}(R_0)&amp;lt;/math&amp;gt;. Фаза 2 переносит &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, разворачивая: &amp;lt;math&amp;gt;R = \mathrm{reverse}(L_0)&amp;lt;/math&amp;gt;. Фаза 3 переносит &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; (в порядке &amp;lt;math&amp;gt;\mathrm{reverse}(R_0)&amp;lt;/math&amp;gt;) обратно в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, что даёт &amp;lt;math&amp;gt;R = \mathrm{reverse}(L_0) \mathbin{+\!\!+} R_0&amp;lt;/math&amp;gt; в смысле стека, то есть сверху оказывается &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; в исходном порядке, а под ним — &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt; в исходном порядке. Поскольку элементы &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; старее элементов &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt;, это и есть FIFO-порядок. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Лемма (обслуживание во время перекопирования).''' Пока перекопирование не завершено, &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; возвращает корректный самый старый элемент, не дожидаясь конца переноса.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' В режиме перекопирования &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; читает &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копию старого &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt;; в варианте на шести стеках она синхронно уменьшается вместе с &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а в персистентном заменена неизменяемой версией &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;. Элементы &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; старее всех элементов &amp;lt;math&amp;gt;L_0 \cup L'&amp;lt;/math&amp;gt;, поэтому выдача из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; сохраняет FIFO. Счётчик &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; уменьшается и при извлечении из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, и при переносе корректного элемента из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;; поэтому &amp;lt;math&amp;gt;|R_c| \ge \mathtt{toCopy}&amp;lt;/math&amp;gt;. Пока &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt;, верх &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — ещё не перенесённый корректный элемент; когда счётчик доходит до нуля, старый правый кусок уже распределён между &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, и &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; синхронно снимает элемент из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Инвариант &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; не даёт прочитать «мусорные» элементы в дне &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Лемма (корректность &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt;).''' В обычном режиме &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;; в режиме перекопирования очередь непуста.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' В обычном режиме инвариант &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt; и представление очереди как &amp;lt;math&amp;gt;R \mathbin{+\!\!+} \mathrm{reverse}(L)&amp;lt;/math&amp;gt; дают эквивалентность. В режиме перекопирования в момент старта (после запускающей операции) в очереди &amp;lt;math&amp;gt;n + (n + 1) = 2n + 1&amp;lt;/math&amp;gt; элементов. Перенос занимает &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операций, включая запускающую; после неё остаётся не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций, поэтому извлечь можно не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; элементов. Значит, хотя бы один элемент остаётся: при &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt; он лежит в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, а при &amp;lt;math&amp;gt;\mathtt{toCopy} = 0&amp;lt;/math&amp;gt; весь правый кусок уже находится в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;. Поэтому &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; возвращает &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; корректно. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Оценка &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае ===&lt;br /&gt;
&lt;br /&gt;
Все переносы выполняются только в &amp;lt;code&amp;gt;Additional-Operations&amp;lt;/code&amp;gt;, где счётчик &amp;lt;code&amp;gt;toDo&amp;lt;/code&amp;gt; ограничен тройкой, и в фазах 1–3 каждый шаг — один перенос (pop и один-два push). Значит, одна операция очереди делает не более 3 переносов, то есть &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае (см. рис. 6).&lt;br /&gt;
&lt;br /&gt;
Покажем, что перекопирование '''успевает''' завершиться. В момент старта &amp;lt;math&amp;gt;|R| = n&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|L| = n + 1&amp;lt;/math&amp;gt;. Требуется: &amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; действий; &amp;lt;math&amp;gt;L \to R, R_c'&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;S \to R, R_c'&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;; две смены ролей — 2. Итого &amp;lt;math&amp;gt;3n + 3&amp;lt;/math&amp;gt; действий. В распоряжении — &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операций (сама запускающая перекопирование плюс следующие за ней до завершения переноса), по 3 действия каждая, то есть ровно &amp;lt;math&amp;gt;3(n + 1) = 3n + 3&amp;lt;/math&amp;gt;. Расчёт сходится: перенос гарантированно завершается.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-06-timeline.svg|thumb|700px|Рис. 6. &amp;lt;math&amp;gt;3(n+1)&amp;lt;/math&amp;gt; действий на &amp;lt;math&amp;gt;n+1&amp;lt;/math&amp;gt; операций — ровно 3 действия на операцию, без запаздывания.]]&lt;br /&gt;
&lt;br /&gt;
'''После перекопирования.''' Пусть за &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций после старта произошло &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; извлечений и &amp;lt;math&amp;gt;n - x&amp;lt;/math&amp;gt; добавлений. Тогда в новых стеках останется &amp;lt;math&amp;gt;n - x&amp;lt;/math&amp;gt; элементов в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;2n + 1 - x = (n - x) + (n + 1)&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, значит, до следующего перекопирования не менее &amp;lt;math&amp;gt;n + 2&amp;lt;/math&amp;gt; операций. За это время остаток &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; (не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; элементов) успевает рассосаться: он уменьшается на 1 при каждой операции в обычном режиме. Это и есть содержание инварианта &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Персистентная очередь на пяти стеках ===&lt;br /&gt;
&lt;br /&gt;
Сделаем все стеки '''персистентными''' (неизменяемыми): операция возвращает новую версию, старые остаются доступны. Тогда шесть стеков превращаются в пять: пара &amp;lt;math&amp;gt;(R_c, R_c')&amp;lt;/math&amp;gt; избыточна — достаточно одной версии &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;, записанной в момент старта перекопирования; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; обращается к ней, и никакой синхронизации не нужно. Освобождать &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt; тоже не требуется: ячейки используются другими версиями очереди. Версия очереди — запись &amp;lt;math&amp;gt;Q = \langle L, L', R, R', S, \mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}\rangle&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращают новую версию вместе с результатом. Переносы по-прежнему ограничены тремя на операцию, поэтому каждая версия занимает &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек и время &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. Псевдокод (упрощённо, по источнику):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Версия Push(Q, x)&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      Ln ← push(Q.L, x)&lt;br /&gt;
3      return Check-Recopy(⟨Ln, Q.L', Q.R, Q.R', Q.S, Q.recopy, Q.toCopy, Q.copied⟩)&lt;br /&gt;
4  else&lt;br /&gt;
5      Ln' ← push(Q.L', x)&lt;br /&gt;
6      return Check-Normal(⟨Q.L, Ln', Q.R, Q.R', Q.S, Q.recopy, Q.toCopy, Q.copied⟩)&lt;br /&gt;
&lt;br /&gt;
Версия Pop(Q)&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      ⟨Rn, x⟩ ← pop(Q.R)&lt;br /&gt;
3      return ⟨Check-Recopy(⟨Q.L, Q.L', Rn, Q.R', Q.S, …⟩), x⟩&lt;br /&gt;
4  else&lt;br /&gt;
5      ⟨Rn', x⟩ ← pop(Q.R')&lt;br /&gt;
6      if Q.toCopy &amp;gt; 0&lt;br /&gt;
7          cur ← Q.toCopy − 1&lt;br /&gt;
8          return ⟨Check-Normal(⟨Q.L, Q.L', Q.R, Rn', Q.S, …, cur, Q.copied⟩), x⟩&lt;br /&gt;
9      else&lt;br /&gt;
10         ⟨Rn, x⟩ ← pop(Q.R)&lt;br /&gt;
11         return ⟨Check-Normal(⟨Q.L, Q.L', Rn, Rn', Q.S, …, 0, Q.copied⟩), x⟩&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Функции &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Additional-Operations&amp;lt;/code&amp;gt; повторяют версию на шести стеках, но вместо мутации создают новые версии стеков. Важное отличие: в пятистековом варианте &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; при входе в перекопирование фиксирует &amp;lt;math&amp;gt;R' \leftarrow R&amp;lt;/math&amp;gt; — текущую версию выхода; без этого &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в режиме перекопирования читал бы устаревшую версию. См. рис. 7.&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Реализация !! Худший !! Амортизированный !! Память на версию !! Персистентность&lt;br /&gt;
|-&lt;br /&gt;
| Два стека || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || неприменима (&amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;) || нет&lt;br /&gt;
|-&lt;br /&gt;
| Шесть стеков (реальное время) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || — (не персистентна) || нет&lt;br /&gt;
|-&lt;br /&gt;
| Пять стеков (персистентная) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек || да (функциональная)&lt;br /&gt;
|-&lt;br /&gt;
| Кольцевой буфер || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на копию || нет&lt;br /&gt;
|-&lt;br /&gt;
| Связный список || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(1)&amp;lt;/math&amp;gt;, но с риском разделения || частично (copy-on-write)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-07-comparison.svg|frame|none|700px|Рис. 7. Три уровня компромисса: амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; → худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; → худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; с персистентностью.]]&lt;br /&gt;
&lt;br /&gt;
'''Когда что выбирать.''' Нужна простая очередь общего назначения — кольцевой буфер или двусвязный список из [[Очередь]] (план). Нужна гарантия времени отклика — шесть стеков. Нужны прошлые версии (undo, функциональный язык) — персистентная очередь на пяти стеках. Если допустимы амортизированные оценки и не важна задержка, двух стеков достаточно.&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки и библиотеки.''' В Haskell, OCaml, Standard ML очереди и деки — персистентные структуры; техника раскрытия «ленивого» переноса восходит к реальному времени и используется в &amp;lt;code&amp;gt;Data.Sequence&amp;lt;/code&amp;gt;, очередях Okasaki и структурах с неизменяемыми версиями. Почему: в чисто функциональном коде структура не мутируется, а старые версии должны сохраняться [4][5].&lt;br /&gt;
&lt;br /&gt;
# '''Системы реального времени.''' Планировщики, обработчики прерываний, буферы событий требуют ограниченного сверху времени отклика. Очередь с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае исключает редкие «долгие» вызовы, которые опасны в контурах управления и телеком-оборудовании [1][3].&lt;br /&gt;
&lt;br /&gt;
# '''Персистентность и версионирование.''' Undo/redo в редакторах, транзакционные снимки, инкрементальные вычисления и системы контроля версий выигрывают от структур, где каждая версия стоит &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек. Персистентная очередь — базовый кирпич таких систем [4].&lt;br /&gt;
&lt;br /&gt;
# '''Логи событий и распределённые потоки.''' Очередь сообщений, к которой нужен доступ к прошлым состояниям (replay, аудит), строится на персистентных структурах; неизменяемость упрощает репликацию и откат [4].&lt;br /&gt;
&lt;br /&gt;
# '''Обучение алгоритмам.''' Конструкция «очередь на шести стеках» — классическая задача курса дискретной математики и алгоритмов (Викиконспекты ИТМО): она тренирует инварианты, амортизационный анализ и персистентность [1][2].&lt;br /&gt;
&lt;br /&gt;
# '''Олимпиадное программирование.''' Персистентная очередь и её «друзья» (персистентный стек, дек, приоритетная очередь) — инструменты задач на версии и откаты; разборы публикуются в блогах сообщества [2].&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Операции над очередью в реальном времени первыми описали '''Роберт Худ и Роберт Мелвилл''' (R. Hood, R. Melville) в работе ''Real Time Queue Operations in Pure LISP'' (Cornell University, 1980; отчёт доступен через библиотечный депозитарий Корнеллского университета) [3]. Они показали, как реализовать очередь без долгих операций в чистом LISP, где данные неизменяемы, — именно персистентность и подталкивала к распределению переноса по операциям.&lt;br /&gt;
&lt;br /&gt;
Идею подхватил '''Крис Окасаки''' (Chris Okasaki): в статье ''Simple and Efficient Purely Functional Queues and Deques'' (Journal of Functional Programming, 1995) и книге ''Purely Functional Data Structures'' (Cambridge University Press, 1998) он систематизировал персистентные очереди — «банкирскую», «физическую» и очередь реального времени [4]. Формальный '''амортизированный анализ''' (и метод потенциала) восходит к '''Роберту Тарьяну''' (R. E. Tarjan, «Amortized Computational Complexity», 1985) [5] и служит языком описания оценок в этой области.&lt;br /&gt;
&lt;br /&gt;
Изложение конструкции «очередь на шести стеках» и её персистентного варианта на пяти стеках — учебная традиция кафедры компьютерных технологий ИТМО; конспект доступен на «Викиконспектах» (neerc.ifmo.ru) [1]. Популярные разборы — в блоге Codeforces [2] и на Хабрахабре [7]. Отметим, что конкретное число стеков (шесть против пяти) зависит от того, требуется ли явная копия выхода: persistence позволяет обойтись меньшим числом стеков [1].&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Путать амортизированное с худшим.''' Очередь на двух стеках — амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но худший случай &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; только конструкция на шести стеках даёт &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в каждом вызове.&lt;br /&gt;
* '''Забыть подчищать &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;.''' В обычном режиме остаток копии нужно уменьшать на 1 за операцию (инвариант &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt;), иначе к следующему перекопированию не найдётся свободного стека.&lt;br /&gt;
* '''Неверный момент запуска.''' Перекопирование нужно начинать ровно при &amp;lt;math&amp;gt;|L| = |R| + 1&amp;lt;/math&amp;gt;; позже — не хватит операций на перенос, раньше — нарушится инвариант и лишняя работа.&lt;br /&gt;
* '''Ошибка на единицу в &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.''' Это число ''корректных'' элементов в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;; при &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; во время перекопирования его нужно уменьшать, иначе можно прочитать уже извлечённый элемент.&lt;br /&gt;
* '''Забыть фазу 1 (&amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt;).''' Без буфера &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; старый выход не опустеет, если операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; больше, чем &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, и не хватит стека для нового &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; двойной переворот через &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; обязателен.&lt;br /&gt;
* '''Не сделать смену ролей.''' Без &amp;lt;math&amp;gt;\mathtt{swap}(L, L')&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\mathtt{swap}(R_c, R_c')&amp;lt;/math&amp;gt; новые результаты останутся в служебных стеках, а очередь будет читать старое.&lt;br /&gt;
* '''Попытка сделать двухстековую очередь персистентной «в лоб».''' Дорогая операция переноса порождает &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; новых ячеек на версию — нужна именно конструкция с дроблением.&lt;br /&gt;
* '''Мутируемые стеки в персистентном варианте.''' Персистентность требует неизменяемости; изменение «на месте» разрушит прошлые версии.&lt;br /&gt;
* '''Закольцованная рекурсия проверок.''' &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; вызывает &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt;, который может выставить &amp;lt;code&amp;gt;recopy&amp;lt;/code&amp;gt; снова; рекурсии здесь нет — это разные шаги одной операции, но при переносе в код важно не зациклиться.&lt;br /&gt;
* '''Предположение, что &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; ложен всегда в перекопировании.''' Так и есть (в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; остаются элементы), но если реализация не поддерживает инвариант &amp;lt;math&amp;gt;|L| = |R| + 1&amp;lt;/math&amp;gt; в момент старта, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; может соврать.&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Два стека.''' Реализуйте очередь на двух стеках и определите худший случай. Ограничение: не более &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; памяти на элемент. ''Подсказка:'' перенос происходит при опустошении выхода; худший вызов — &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Инвариант.''' Докажите, что в обычном режиме &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;. ''Подсказка:'' используйте &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt; и представление &amp;lt;math&amp;gt;R \mathbin{+\!\!+} \mathrm{reverse}(L)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Счёт действий.''' Покажите, что при старте перекопирования (&amp;lt;math&amp;gt;|L| = |R| + 1 = n + 1&amp;lt;/math&amp;gt;) требуется ровно &amp;lt;math&amp;gt;3n + 3&amp;lt;/math&amp;gt; действий и что их укладывает ровно &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операция по 3. ''Подсказка:'' сложите вклады фаз 1–4.&lt;br /&gt;
&lt;br /&gt;
# '''&amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.''' Объясните, почему некорректные элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; скапливаются у дна, и почему условие &amp;lt;code&amp;gt;toCopy &amp;amp;gt; 0&amp;lt;/code&amp;gt; защищает от чтения мусора. ''Подсказка:'' элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; переносятся по одному, а уменьшение &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt; соответствует уже отданным элементам.&lt;br /&gt;
&lt;br /&gt;
# '''Трассировка.''' Вручную повторите семь &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и семь &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из раздела «Трассировка примера» и сверьте состояния стеков. ''Подсказка:'' следите за флагами &amp;lt;code&amp;gt;recopy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;copied&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Реальное время vs персистентность.''' Объясните, почему шестистековый вариант годится для реального времени, а для персистентности достаточно пяти стеков. ''Подсказка:'' роль копии &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; берёт на себя версия &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Пятистековая очередь.''' Реализуйте персистентную очередь на пяти неизменяемых стеках так, чтобы каждая версия занимала &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек. ''Ограничение:'' стеки неизменяемы.&lt;br /&gt;
&lt;br /&gt;
# '''Расширение до дека.''' Попробуйте добавить &amp;lt;code&amp;gt;pop_back&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;push_front&amp;lt;/code&amp;gt; и обсудите, почему дек на стеках сложнее очереди. ''Подсказка:'' симметрия «лево/право» требует mirrored-структуры.&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Очередь]] (план) — базовая FIFO, кольцевой буфер, очередь на двух стеках.&lt;br /&gt;
* [[Стек]] (план) — строительный блок; стек как персистентная структура.&lt;br /&gt;
* [[Персистентный стек]] (план) — неизменяемый стек; основа персистентной очереди.&lt;br /&gt;
* [[Персистентная очередь]] (план) — персистентность очереди, ленивые очереди Окасаки.&lt;br /&gt;
* [[Асимптотическая сложность]] — амортизация, худший случай, худший и амортизированный.&lt;br /&gt;
* [[Персистентные структуры данных]] — неизменяемость и версии.&lt;br /&gt;
* [[Декартово дерево]] (план) — персистентный treap как ещё один пример персистентности.&lt;br /&gt;
* [[Очередь с приоритетом]] (план) — родственная структура; персистентный вариант.&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Викиконспекты ИТМО: Персистентная очередь (разделы «Реализация очереди на шести стеках», «Персистентная очередь на пяти стеках»). URL: https://neerc.ifmo.ru/wiki/index.php?title=Персистентная_очередь.&lt;br /&gt;
# Codeforces. Персистентная очередь и её друзья (блог). URL: https://codeforces.com/blog/entry/15685.&lt;br /&gt;
# Hood R., Melville R. Real Time Queue Operations in Pure LISP. — Cornell University, 1980. URL: http://hdl.handle.net/1813/6273.&lt;br /&gt;
# Okasaki C. Simple and Efficient Purely Functional Queues and Deques // Journal of Functional Programming. — 1995. — Vol. 5, № 4; и Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998.&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Cormen T. H., Leiserson C. E., Rivest R. L., Stein C. ''Introduction to Algorithms'' (CLRS). 3rd ed. — MIT Press, 2009. — Гл. 10 (элементарные структуры: стек, очередь).&lt;br /&gt;
# Хабрахабр. Персистентная очередь (пост 241231). URL: https://habr.com/ru/post/241231/ ''(проверено 2026-11-03: статья о персистентной очереди, другой алгоритм — на деревьях)''.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Алгоритмы и структуры данных]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/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%D0%B0%D1%8F_%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C&amp;diff=949</id>
		<title>Персистентная очередь</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/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%D0%B0%D1%8F_%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C&amp;diff=949"/>
		<updated>2026-11-04T18:06:18Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «== TL;DR ==  '''Персистентная очередь''' (persistent queue) — очередь, сохраняющая '''все свои версии''': к...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная очередь''' (persistent queue) — очередь, сохраняющая '''все свои версии''': каждая операция возвращает новую очередь, а прежние состояния остаются доступными. Простейший способ — копировать очередь целиком — стоит &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на операцию и &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; суммарно, что неприемлемо. Очередь на двух '''неизменяемых стеках''' (&amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; — голова, &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; — хвост) даёт &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и разделяет узлы версий, но &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; при пустом &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; требует '''разворота''' &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; за &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; хуже того, при '''ветвлении версий''' амортизация ломается — одну и ту же дорогую операцию можно повторить из разных версий. Поэтому персистентные очереди строят иначе: '''банкирская''' и '''физическая''' очереди (Окасаки) дают &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно с помощью '''ленивых потоков''' и кредита/потенциала, а очередь '''реального времени''' — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; '''в худшем''' случае (реализуется, например, на шести стеках; см. [[Очередь на шести стеках]]).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Свойство !! Значение&lt;br /&gt;
|-&lt;br /&gt;
| Класс || Очередь (FIFO) с полной персистентностью&lt;br /&gt;
|-&lt;br /&gt;
| Версии || Хранятся все; доступ к любой за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Цена версии || от &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; (копия или разворот) до &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (ленивые/реальное время)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (1 новый узел)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; (two-stack, энергично) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; при развороте&lt;br /&gt;
|-&lt;br /&gt;
| Амортизация || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; для банкирской/физической; '''не работает''' при ветвлении «в лоб»&lt;br /&gt;
|-&lt;br /&gt;
| Худший случай || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; только у очереди реального времени&lt;br /&gt;
|-&lt;br /&gt;
| Приёмы || ленивые потоки, планировщик, кредит (banker's), потенциал (physicist's)&lt;br /&gt;
|-&lt;br /&gt;
| Реализации || два стека; банкирская; физическая; реального времени (6 стеков)&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается || функциональные языки, версионирование, undo, инкрементальные вычисления&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная очередь''' — очередь (FIFO), операция над которой порождает '''новую версию''', не разрушая предыдущую. Подробнее об уровнях персистентности — в [[Персистентный стек]]: различают частичную (менять можно только последнюю версию), полную (любую) и функциональную (ячейки памяти неизменяемы) персистентность. Персистентная очередь обычно строится как '''функциональная''': узлы и стеки неизменяемы.&lt;br /&gt;
&lt;br /&gt;
'''Версия очереди''' — конкретное состояние очереди (последовательность элементов). Операция — это функция, отображающая версию в новую версию.&lt;br /&gt;
&lt;br /&gt;
'''Разделение (sharing)''' — переиспользование неизменяемых узлов несколькими версиями. Благодаря ему &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; занимают &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; памяти, а не &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Представление на двух стеках.''' Очередь хранится парой неизменяемых стеков: &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; содержит голову очереди (верх — самый старый элемент), &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; — хвост (верх — самый новый). Логическая последовательность равна &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; (сверху вниз) &amp;lt;math&amp;gt;\mathbin{+\!\!+}&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\mathrm{reverse}(back)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Ленивый поток (lazy stream)''' — последовательность, хвост которой вычисляется не сразу, а по требованию. Невычисленный хвост хранится как '''thunk''' (отложенное вычисление). Форсирование thunk — вычисление отложенной части.&lt;br /&gt;
&lt;br /&gt;
'''Планировщик (scheduler)''' — механизм, который форсирует отложенную работу '''маленькими порциями''' (например, по одному узлу за операцию), чтобы не было отдельных дорогих вызовов.&lt;br /&gt;
&lt;br /&gt;
'''Банкирская очередь (banker's queue).''' Персистентная очередь с ленивым разворотом, где каждый элемент несёт '''кредит''', оплачивающий будущие шаги. Даёт &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно [3][4].&lt;br /&gt;
&lt;br /&gt;
'''Физическая очередь (physicist's queue).''' Та же цель, но оплата через '''потенциал''' &amp;lt;math&amp;gt;\Phi = |back|&amp;lt;/math&amp;gt; (метод потенциала Тарьяна) [3][5].&lt;br /&gt;
&lt;br /&gt;
'''Очередь в реальном времени (real-time queue).''' Персистентная очередь с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в '''худшем''' случае на операцию; планировщик форсирует постоянное число шагов за операцию [1][3].&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — число элементов/операций; &amp;lt;math&amp;gt;|X|&amp;lt;/math&amp;gt; — размер структуры &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный.&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Обычная очередь хранит одно состояние: &amp;lt;code&amp;gt;enqueue&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;dequeue&amp;lt;/code&amp;gt; изменяют его на месте (см. [[Очередь]]). Если нужны прошлые состояния, приходится либо копировать очередь перед каждым изменением, либо вовсе отказаться от персистентности. Копирование обходится &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на операцию и &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версий — это слишком дорого. См. рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-01-persistence.svg|thumb|700px|Рис. 1. Каждая операция создаёт новую версию; старые версии живут, и от любой можно ветвить.]]&lt;br /&gt;
&lt;br /&gt;
Придумать персистентную очередь сложнее, чем персистентный стек. Стек — это одна цепочка, и &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; просто добавляет узел в начало (см. [[Персистентный стек]]). У очереди '''два конца''', и FIFO приходится восстанавливать разворотом. Первое естественное решение — два неизменяемых стека: &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; идёт в &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; (это дешёвый «стек», поэтому &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;), &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;; когда &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; пуст, стек &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; разворачивается в &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;. Разворот — это &amp;lt;math&amp;gt;\Theta(|back|)&amp;lt;/math&amp;gt;, и вся трудность персистентности в том, как с ним справиться.&lt;br /&gt;
&lt;br /&gt;
Второе ключевое наблюдение — '''амортизация не переносится на персистентные структуры'''. В мутирующей очереди один разворот за &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; «оплачивается» &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; последующими дешёвыми &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;: амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;. В персистентной структуре ту же дорогую операцию можно запустить из разных версий, и каждый раз она выполняется заново — амортизированная граница для отдельной цепочки операций не даёт гарантии. См. рис. 3. Именно поэтому для персистентных очередей указывают либо честную &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; '''в худшем''' случае, либо используют '''ленивые''' вычисления, чтобы амортизация «принадлежала структуре», а не последовательности вызовов.&lt;br /&gt;
&lt;br /&gt;
Ключевая идея в одном абзаце: '''разворот нужно сделать ленивым и оплачиваемым по частям'''. Если хвост очереди хранить как ленивый поток, обратный порядок получается бесплатно до момента обращения; а планировщик, форсирующий работу по одному шагу за операцию, устраняет «долгие» вызовы. Это и есть банкирская, физическая и очередь реального времени.&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Персистентная очередь на двух стеках — это пара неизменяемых стеков и функция, возвращающая новую пару (см. рис. 2). Версии образуют дерево: каждое &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; добавляет в него узел.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-02-two-stacks.svg|thumb|700px|Рис. 2. &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; идёт в &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;; при пустом &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; стек &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; разворачивается.]]&lt;br /&gt;
&lt;br /&gt;
Основные компоненты и правила:&lt;br /&gt;
&lt;br /&gt;
* '''&amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;''' — головная часть в прямом порядке (верх — самый старый);&lt;br /&gt;
* '''&amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;''' — хвостовая часть в обратном порядке (верх — самый новый);&lt;br /&gt;
* '''&amp;lt;code&amp;gt;push(x)&amp;lt;/code&amp;gt;''' — новая версия с &amp;lt;math&amp;gt;back.push(x)&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, один новый узел;&lt;br /&gt;
* '''&amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt;''' — верх &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;; если &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; пуст, сначала &amp;lt;math&amp;gt;front \leftarrow \mathrm{reverse}(back)&amp;lt;/math&amp;gt;, затем &amp;lt;math&amp;gt;back \leftarrow \varnothing&amp;lt;/math&amp;gt;;&lt;br /&gt;
* '''&amp;lt;code&amp;gt;empty()&amp;lt;/code&amp;gt;''' — &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; пусты.&lt;br /&gt;
&lt;br /&gt;
Логическая инвариантность представления: очередь равна &amp;lt;math&amp;gt;front \mathbin{+\!\!+} \mathrm{reverse}(back)&amp;lt;/math&amp;gt;. Пока &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; не пуст, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; дешёвый; дорогой шаг — исключительно разворот &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Почему не годится «в лоб» (см. рис. 3).''' Общий для нескольких версий стек &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; нельзя развернуть один раз «для всех»: каждая версия, где &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; пуст, вынуждена строить свой разворот, то есть платить &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;. Значит, наивный энергичный вариант даёт худший случай &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; и не спасается амортизацией.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-03-amortize-breaks.svg|thumb|700px|Рис. 3. Общий &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; и две ветви: каждая ветвь платит за разворот заново, поэтому одна операция снова может стоить &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
'''Как исправляют.''' Два приёма (см. рис. 4):&lt;br /&gt;
&lt;br /&gt;
* '''Лень (lazy).''' Хвост хранится как поток, разворот не вычисляется заранее. Обращение к нему форсирует thunk. Это превращает цену разворота в «отложенный долг», который можно оплачивать постепенно.&lt;br /&gt;
* '''Планировщик + кредит/потенциал.''' Банкирская очередь выдаёт каждому элементу кредит на будущие форсирования; физическая списывает работу с потенциала. Планировщик форсирует по шагу за операцию, и худшего вызова не остаётся.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-04-okasaki.svg|thumb|700px|Рис. 4. Ленивый поток хранит невычисленный хвост; планировщик форсирует постоянное число шагов, давая банкирскую, физическую и очередь реального времени.]]&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
Ниже — операции персистентной очереди на двух стеках (энергичный вариант; он корректен, но имеет худший случай &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;). Обозначения: &amp;lt;math&amp;gt;Q = (F, B)&amp;lt;/math&amp;gt; — пара стеков; &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; стеков возвращают новые стеки; &amp;lt;math&amp;gt;\mathrm{reverse}(S)&amp;lt;/math&amp;gt; — разворот.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Push ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Положить элемент в хвостовой стек &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; — это операция над стеком, то есть &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и один новый узел; головной стек не трогаем.&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание:'''&lt;br /&gt;
&lt;br /&gt;
# Построить новую версию: &amp;lt;math&amp;gt;back' \leftarrow back.\mathrm{push}(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вернуть &amp;lt;math&amp;gt;(front, back')&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Очередь версии равна &amp;lt;math&amp;gt;front \mathbin{+\!\!+} \mathrm{reverse}(back)&amp;lt;/math&amp;gt;; добавление в конец не меняет голову.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push(Q, x)&lt;br /&gt;
Вход:  версия Q = (F, B); x — элемент&lt;br /&gt;
Выход: новая версия Q'&lt;br /&gt;
Инвариант: Q = F ++ reverse(B)&lt;br /&gt;
Сложность: O(1) времени, O(1) новых узлов&lt;br /&gt;
&lt;br /&gt;
1  B' ← B.push(x)&lt;br /&gt;
2  return (F, B')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; в пустую очередь даёт &amp;lt;math&amp;gt;(∅, \{x\})&amp;lt;/math&amp;gt;: &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; сразу потребует разворота.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Pop ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Взять голову из &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; пуст, развернуть &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;; вернуть верхний элемент и новую версию с «отрезанной» головой.&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание:'''&lt;br /&gt;
&lt;br /&gt;
# Если очередь пуста — ошибка &amp;lt;code&amp;gt;underflow&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Если &amp;lt;math&amp;gt;front = ∅&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;F \leftarrow \mathrm{reverse}(back)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;B \leftarrow ∅&amp;lt;/math&amp;gt;.&lt;br /&gt;
# &amp;lt;math&amp;gt;x \leftarrow front.\mathrm{top}()&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вернуть &amp;lt;math&amp;gt;(x, (front.\mathrm{pop}(), back))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Возвращаемый &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; — самый старый элемент; новая версия содержит очередь без него.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop(Q)&lt;br /&gt;
Вход:  версия Q = (F, B), Q ≠ (∅, ∅)&lt;br /&gt;
Выход: (x, Q') — головной элемент и новая версия&lt;br /&gt;
Инвариант: x — минимальный по времени поступления&lt;br /&gt;
Сложность: O(1), если F ≠ ∅; иначе Θ(|B|) — разворот&lt;br /&gt;
&lt;br /&gt;
1  if F = ∅ and B = ∅&lt;br /&gt;
2      error &amp;quot;pop из пустой очереди&amp;quot;&lt;br /&gt;
3  if F = ∅&lt;br /&gt;
4      F ← Reverse(B)                 # самая дорогая операция&lt;br /&gt;
5      B ← ∅&lt;br /&gt;
6  x ← F.top()&lt;br /&gt;
7  return (x, (F.pop(), B))&lt;br /&gt;
&lt;br /&gt;
Функция Reverse(S)                    # разворот стека за O(|S|)&lt;br /&gt;
1  R ← ∅&lt;br /&gt;
2  while S ≠ ∅&lt;br /&gt;
3      R ← R.push(S.top())&lt;br /&gt;
4      S ← S.pop()&lt;br /&gt;
5  return R&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Для &amp;lt;math&amp;gt;Q = (∅, \{3,2,1\})&amp;lt;/math&amp;gt; (верх &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; — 3) &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; разворачивает &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;front = \{1,2,3\}&amp;lt;/math&amp;gt;, возвращает 1 и даёт версию &amp;lt;math&amp;gt;(\{2,3\}, ∅)&amp;lt;/math&amp;gt;. Следующие &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; дешёвые, пока &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; не опустеет.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; при непустом &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\Theta(|back|)&amp;lt;/math&amp;gt; при развороте. Амортизированно по одной линии операций — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но при ветвлении это не гарантия (см. рис. 3).&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; пустой очереди — ошибка. Ветвление после разворота повторяет разворот в каждой ветви.&lt;br /&gt;
&lt;br /&gt;
=== Техники: банкирская, физическая, реального времени ===&lt;br /&gt;
&lt;br /&gt;
'''Ленивый разворот.''' Вместо немедленного &amp;lt;math&amp;gt;\mathrm{reverse}(back)&amp;lt;/math&amp;gt; строят поток &amp;lt;code&amp;gt;reverse(back)&amp;lt;/code&amp;gt;, хвост которого — thunk. Обращение к потоку форсирует его. Так как узлы разделяются, отложенная работа выполняется не более одного раза на версию.&lt;br /&gt;
&lt;br /&gt;
'''Банкирская очередь.''' Каждому элементу при &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; выдаётся кредит; форсирование шага разворота списывает кредит. Инвариант «кредитов достаточно» гарантирует &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно, даже при передаче версий между вызовами [3][4].&lt;br /&gt;
&lt;br /&gt;
'''Физическая очередь.''' Вместо явного кредита — потенциал &amp;lt;math&amp;gt;\Phi = |back|&amp;lt;/math&amp;gt;; амортизированная стоимость операции &amp;lt;math&amp;gt;\hat{c} = c + \Delta\Phi&amp;lt;/math&amp;gt; остаётся &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; [3][5]. При &amp;lt;math&amp;gt;\Phi = |back|&amp;lt;/math&amp;gt;: &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; даёт &amp;lt;math&amp;gt;\Delta\Phi = +1&amp;lt;/math&amp;gt; (запас), &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из непустого &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;\Delta\Phi = 0&amp;lt;/math&amp;gt;, разворот — &amp;lt;math&amp;gt;\Delta\Phi = -|back|&amp;lt;/math&amp;gt;; поэтому амортизированная стоимость разворота равна 0 и оплачена предыдущими &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Очередь реального времени.''' Планировщик форсирует постоянное число шагов за операцию (в шестистековой версии — до трёх переносов), поэтому отдельный вызов всегда &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;. Конкретная реализация — на шести (или пяти персистентных) стеках, разобрана в [[Очередь на шести стеках]].&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Два стека (энергично) !! Ленивая (банкирская/физическая) !! Реального времени&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; / &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; разворот || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Доступ к версии || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Новых ячеек на &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Новых ячеек на разворот || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на разворот, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт. || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на операцию&lt;br /&gt;
|-&lt;br /&gt;
| Персистентность || полная || полная || полная&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Оговорка: амортизированная граница &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; для ленивых очередей действительна благодаря тому, что '''кредит/потенциал принадлежат структуре''', а не последовательности вызовов. Наивная энергичная очередь на двух стеках такой гарантии не даёт при ветвлении версий.&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Возьмём последовательность &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(2)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt;, затем два &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, затем ветвление от третьей версии через &amp;lt;code&amp;gt;push(9)&amp;lt;/code&amp;gt;. Стеки записаны сверху вниз (верх … дно).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! № !! Операция !! Версия &amp;lt;math&amp;gt;(F, B)&amp;lt;/math&amp;gt; !! Логическая очередь !! Комментарий&lt;br /&gt;
|-&lt;br /&gt;
| v0 || старт || &amp;lt;math&amp;gt;(∅, ∅)&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt; || пусто&lt;br /&gt;
|-&lt;br /&gt;
| v1 || &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;(∅, [1])&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[1]&amp;lt;/code&amp;gt; || в &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| v2 || &amp;lt;code&amp;gt;push(2)&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;(∅, [2,1])&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[1,2]&amp;lt;/code&amp;gt; || в &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| v3 || &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;(∅, [3,2,1])&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[1,2,3]&amp;lt;/code&amp;gt; || в &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| v4 || &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 1 || &amp;lt;math&amp;gt;([2,3], ∅)&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[2,3]&amp;lt;/code&amp;gt; || разворот &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt; (Θ(n))&lt;br /&gt;
|-&lt;br /&gt;
| v5 || &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 2 || &amp;lt;math&amp;gt;([3], ∅)&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[3]&amp;lt;/code&amp;gt; || дешёвый&lt;br /&gt;
|-&lt;br /&gt;
| b || &amp;lt;code&amp;gt;v3.push(9)&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;(∅, [9,3,2,1])&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[1,2,3,9]&amp;lt;/code&amp;gt; || ветвь от v3&lt;br /&gt;
|-&lt;br /&gt;
| c || &amp;lt;code&amp;gt;v3.pop()&amp;lt;/code&amp;gt; → 1 || &amp;lt;math&amp;gt;([2,3], ∅)&amp;lt;/math&amp;gt; || &amp;lt;code&amp;gt;[2,3]&amp;lt;/code&amp;gt; || своя версия разворота&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Разберём ветвление. Версия v3 не изменилась, хотя от неё построены и &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;: узлы делятся, но разворот в &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt; выполняется заново (в &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; он вообще не нужен). Это ровно тот случай, из-за которого энергичная реализация не имеет &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае, — см. рис. 3. Трассировка сверена с реализацией из раздела «Служебное».&lt;br /&gt;
&lt;br /&gt;
== Корректность и оценка сложности ==&lt;br /&gt;
&lt;br /&gt;
=== Корректность FIFO ===&lt;br /&gt;
&lt;br /&gt;
'''Инвариант представления.''' Логическая очередь версии &amp;lt;math&amp;gt;(F, B)&amp;lt;/math&amp;gt; равна &amp;lt;math&amp;gt;F \mathbin{+\!\!+} \mathrm{reverse}(B)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''База.''' Пустая очередь &amp;lt;math&amp;gt;(∅, ∅)&amp;lt;/math&amp;gt; соответствует пустой последовательности.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;.''' Новая версия &amp;lt;math&amp;gt;(F, B.\mathrm{push}(x))&amp;lt;/math&amp;gt;: логически &amp;lt;math&amp;gt;F \mathbin{+\!\!+} \mathrm{reverse}(B.\mathrm{push}(x)) = F \mathbin{+\!\!+} \mathrm{reverse}(B) \mathbin{+\!\!+} [x]&amp;lt;/math&amp;gt;, то есть &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; в конце. Инвариант сохранён.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;.''' Если &amp;lt;math&amp;gt;F \neq ∅&amp;lt;/math&amp;gt;, верх &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; — самый старый элемент (инвариант), и новая версия &amp;lt;math&amp;gt;(F.\mathrm{pop}(), B)&amp;lt;/math&amp;gt; убирает именно его. Если &amp;lt;math&amp;gt;F = ∅&amp;lt;/math&amp;gt;, логическая очередь равна &amp;lt;math&amp;gt;\mathrm{reverse}(B)&amp;lt;/math&amp;gt;; замена &amp;lt;math&amp;gt;F \leftarrow \mathrm{reverse}(B)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;B \leftarrow ∅&amp;lt;/math&amp;gt; сохраняет её, а верх нового &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; — самый старый элемент &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt;, то есть голова очереди. Значит, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает корректный элемент.&lt;br /&gt;
&lt;br /&gt;
'''Отсюда:''' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; всегда возвращает минимальный по времени поступления элемент, то есть очередь работает по FIFO, и все версии корректны.&lt;br /&gt;
&lt;br /&gt;
=== Персистентность ===&lt;br /&gt;
&lt;br /&gt;
'''Утверждение.''' Операции не изменяют уже существующие узлы.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт новый узел со ссылкой на прежнюю вершину, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает существующий узел, а &amp;lt;math&amp;gt;\mathrm{reverse}&amp;lt;/math&amp;gt; создаёт новые узлы, читая старые. Ни одно поле ранее созданного узла не присваивается. Значит, любая сохранённая ссылка на версию остаётся действительной — персистентность полная (можно продолжать любую версию).&lt;br /&gt;
&lt;br /&gt;
=== Оценка памяти ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создают ровно &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; узлов (по одному на &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;), которые делят версии (см. рис. 5). Развороты создают дополнительные узлы, но каждый — новый; при энергичном варианте один разворот даёт &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; узлов. Если требуется именно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек на операцию, разворот должен быть ленивым (амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;) или распределённым (худший случай &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-05-memory.svg|thumb|700px|Рис. 5. Версии делят общие узлы: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; при копировании.]]&lt;br /&gt;
&lt;br /&gt;
=== Почему амортизация не переносится на персистентность ===&lt;br /&gt;
&lt;br /&gt;
В мутирующей структуре каждая операция выполняется '''один раз''', поэтому суммарная стоимость делится на число операций. В персистентной структуре одну операцию можно выполнить '''из разных версий''' — например, первый &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; после длинной серии &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; запускает разворот, и если от той же версии ветвятся &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; раз, разворот повторится &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; раз. Классический метод потенциала, привязанный к последовательности вызовов, теряет силу.&lt;br /&gt;
&lt;br /&gt;
'''Как это лечат.''' Окасаки показал, что амортизация сохраняется, если «долг» хранится '''в самой структуре''': банкирская очередь несёт кредиты, физическая — потенциал &amp;lt;math&amp;gt;\Phi = |back|&amp;lt;/math&amp;gt;. Тогда любые списания (debits) ограничены накопленным кредитом, и &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно держит даже при передаче версий [3][4]. Для &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; '''в худшем''' случае применяют планировщик: одна операция — постоянное число шагов, что даёт очередь реального времени [1][3].&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Реализация !! Худший !! Амортизир. !! Новых ячеек !! Замечание&lt;br /&gt;
|-&lt;br /&gt;
| Копия всей очереди || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || просто, но &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; на историю&lt;br /&gt;
|-&lt;br /&gt;
| Два стека (энергично) || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; «в линии» || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; push, &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; разворот || амортизация не переносится&lt;br /&gt;
|-&lt;br /&gt;
| Банкирская (Okasaki) || до &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; при форсировании || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт. || кредиты в структуре&lt;br /&gt;
|-&lt;br /&gt;
| Физическая (Okasaki) || до &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; при форсировании || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт. || потенциал &amp;lt;math&amp;gt;\Phi&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Реального времени || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || планировщик / 6 стеков&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Сводка по пяти реализациям — на рис. 6.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pq-06-comparison.svg|frame|none|700px|Рис. 6. От копирования (&amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на версию) до очереди реального времени (&amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;).]]&lt;br /&gt;
&lt;br /&gt;
'''Вердикт.''' Нужны простые версии и допустимы амортизированные оценки — два стека или банкирская очередь. Нужна жёсткая гарантия времени отклика — очередь реального времени ([[Очередь на шести стеках]]). Копирование версий оправдано лишь на крошечных размерах.&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки.''' В Haskell стандартные последовательности и библиотечные очереди персистентны; в OCaml и Standard ML есть как функциональные, так и мутабельные реализации. Банкирская и физическая очереди Окасаки — учебный канон и основа библиотечных реализаций. Почему: в чистом функциональном коде мутация запрещена, а старые версии нужно сохранять [3][4].&lt;br /&gt;
&lt;br /&gt;
# '''Версионирование и системы контроля версий.''' История изменений — это дерево версий структуры; персистентная структура хранит её без копирования данных. Почему: ветвление версий и откаты выполняются за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по указателям [4][6].&lt;br /&gt;
&lt;br /&gt;
# '''Undo/redo и редакторы.''' Стек или очередь действий с историей строится из персистентных структур: каждое действие — новая версия, откат — переход по ссылке. Почему: &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новая память на действие и отсутствие блокировок.&lt;br /&gt;
&lt;br /&gt;
# '''Инкрементальные вычисления и кэширование.''' Персистентные структуры позволяют сравнивать версии и переиспользовать общие части (например, при мемоизации путей). Почему: sharing экономит память и время [4].&lt;br /&gt;
&lt;br /&gt;
# '''Параллельные вычисления.''' Неизменяемые версии безопасно разделяются потоками без синхронизации. Почему: нельзя испортить состояние, разделяемое несколькими потребителями [4].&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Очереди в реальном времени впервые описали '''Худ и Мелвилл''' (R. Hood, R. Melville) в работе ''Real Time Queue Operations in Pure LISP'' (Cornell University, 1980), где данные неизменяемы, а перенос работы распределён по операциям [2]. Идею ленивых очередей развил '''Ф. У. Бартон''' (F. W. Burton) в статье ''An Efficient Functional Implementation of FIFO Queues'' (Information Processing Letters, 1982) [8]. Наиболее известные конструкции — '''банкирская''' и '''физическая''' очереди (с ленивыми потоками и кредитом/потенциалом) и очередь '''реального времени''' — принадлежат '''Крису Окасаки''' (статья ''Simple and Efficient Purely Functional Queues and Deques'', Journal of Functional Programming, 1995; книга ''Purely Functional Data Structures'', Cambridge University Press, 1998) [3].&lt;br /&gt;
&lt;br /&gt;
Общую теорию персистентности (методы path copying, fat nodes, node copying) заложили '''Дрисколл, Сарнак, Слитор и Тарьян''' (''Making Data Structures Persistent'', STOC 1986; JCSS 1989) [6]. Формальный амортизированный анализ, включая вошедший в обиход метод потенциала, развил '''Роберт Тарьян''' («Amortized Computational Complexity», 1985) [5]. Функциональные структуры-предшественники — списки LISP (Джон Маккарти, 1960).&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Переносить амортизацию на персистентную структуру.''' Оценка &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; «в линии» не означает &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; при ветвлении: одну дорогую операцию можно повторить из разных версий.&lt;br /&gt;
* '''Считать персистентную очередь «просто очередью на двух стеках».''' Без лени/планировщика худший случай остаётся &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* '''Форсировать весь ленивый поток в одной операции.''' Это возвращает &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; в отдельный вызов; нужно форсировать маленькими порциями.&lt;br /&gt;
* '''Забыть про инвариант кредита/потенциала.''' Без него ленивая очередь теряет амортизированную оценку (кредиты могут «закончиться»).&lt;br /&gt;
* '''Мутировать разделяемые узлы.''' Любая запись в общий узел испортит все версии, которые на него ссылаются.&lt;br /&gt;
* '''Освобождать узлы «оптимизацией».''' Узел можно удалять, только когда на него не ссылается ни одна живая версия (нужна сборка мусора).&lt;br /&gt;
* '''Путать дебет и кредит.''' В банкирской очереди &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; может ничего не форсировать, но при определённых условиях обязан заплатить лишний кредит; пропуск этого нарушает инвариант.&lt;br /&gt;
* '''Ожидать &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; от разворота eager-варианта.''' Создание &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; узлов на разворот — самая дорогая операция; она и есть источник проблем.&lt;br /&gt;
* '''Хранить версии без нужды.''' Если ветвление не требуется, персистентность — лишние накладные расходы; иногда достаточно мутирующей очереди.&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Две версии.''' Реализуйте персистентную очередь на двух неизменяемых стеках с &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Ограничение: узлы неизменяемы. ''Подсказка:'' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; при пустом &amp;lt;math&amp;gt;front&amp;lt;/math&amp;gt; разворачивает &amp;lt;math&amp;gt;back&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Ветвление.''' Постройте очередь &amp;lt;code&amp;gt;[1,2,3]&amp;lt;/code&amp;gt;, сделайте ветвь &amp;lt;code&amp;gt;push(4)&amp;lt;/code&amp;gt; и ветвь &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Сколько версий получилось и каковы их содержимые? ''Подсказка:'' обе ветви происходят от одной версии.&lt;br /&gt;
&lt;br /&gt;
# '''Амортизация.''' Докажите, что в одной линии операций энергичный вариант амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, и приведите пример ветвления, где отдельный &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; стоит &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;. ''Подсказка:'' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; дешёвых &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; оплачивают один разворот; при ветвлении разворот повторяется.&lt;br /&gt;
&lt;br /&gt;
# '''Память.''' Покажите, что &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; без разворотов дают ровно &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; узлов. Оцените дополнительные узлы при одном развороте. ''Подсказка:'' разворот создаёт &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; узлов.&lt;br /&gt;
&lt;br /&gt;
# '''Ленивый разворот.''' Опишите, как хранить &amp;lt;code&amp;gt;reverse(back)&amp;lt;/code&amp;gt; в виде thunk и когда его форсировать. ''Подсказка:'' форсируйте по требованию головы.&lt;br /&gt;
&lt;br /&gt;
# '''Кредит.''' Для банкирской очереди придумайте правило выдачи кредитов при &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и списания при форсировании так, чтобы инвариант «кредитов хватает» сохранялся. ''Подсказка:'' один кредит на элемент оплачивает один шаг разворота.&lt;br /&gt;
&lt;br /&gt;
# '''Потенциал.''' Для физической очереди вычислите &amp;lt;math&amp;gt;\Delta\Phi&amp;lt;/math&amp;gt; при &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; и покажите &amp;lt;math&amp;gt;\hat{c} = O(1)&amp;lt;/math&amp;gt;. ''Подсказка:'' &amp;lt;math&amp;gt;\Phi = |back|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Реальное время.''' Объясните, почему очереди с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае строят на нескольких стеках, а не на ленивых потоках напрямую. ''Подсказка:'' форсирование должно быть по шагу на операцию (см. [[Очередь на шести стеках]]).&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Очередь]] — обычная FIFO-очередь; кольцевой буфер.&lt;br /&gt;
* [[Персистентный стек]] — базовый неизменяемый стек.&lt;br /&gt;
* [[Очередь на шести стеках]] — очередь реального времени и персистентный вариант на пяти стеках.&lt;br /&gt;
* [[Асимптотическая сложность]] — амортизация, метод потенциала, худший случай.&lt;br /&gt;
* [[Персистентные структуры данных]] — общая теория персистентности.&lt;br /&gt;
* [[Декартово дерево]] — персистентный treap.&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Викиконспекты ИТМО: Персистентная очередь. URL: https://neerc.ifmo.ru/wiki/index.php?title=Персистентная_очередь.&lt;br /&gt;
# Hood R., Melville R. Real Time Queue Operations in Pure LISP. — Cornell University, 1980. URL: http://hdl.handle.net/1813/6273.&lt;br /&gt;
# Okasaki C. Simple and Efficient Purely Functional Queues and Deques // Journal of Functional Programming. — 1995. — Vol. 5, № 4.&lt;br /&gt;
# Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998 (банкирская и физическая очереди, амортизация в функциональных структурах).&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Driscoll J. R., Sarnak N., Sleator D. D., Tarjan R. E. Making Data Structures Persistent // STOC 1986; JCSS. — 1989. — Vol. 38.&lt;br /&gt;
# Cormen T. H., Leiserson C. E., Rivest R. L., Stein C. ''Introduction to Algorithms'' (CLRS). 3rd ed. — MIT Press, 2009. — Гл. 10 (очередь, стек), гл. 17 (амортизированный анализ).&lt;br /&gt;
# Burton F. W. An Efficient Functional Implementation of FIFO Queues // Information Processing Letters. — 1982. — Vol. 14, № 5. — P. 205–206.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Алгоритмы и структуры данных]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-06-comparison.svg&amp;diff=948</id>
		<title>Файл:Pq-06-comparison.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-06-comparison.svg&amp;diff=948"/>
		<updated>2026-11-04T18:05:25Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-05-memory.svg&amp;diff=947</id>
		<title>Файл:Pq-05-memory.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-05-memory.svg&amp;diff=947"/>
		<updated>2026-11-04T18:05:18Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-04-okasaki.svg&amp;diff=946</id>
		<title>Файл:Pq-04-okasaki.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-04-okasaki.svg&amp;diff=946"/>
		<updated>2026-11-04T18:05:11Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-03-amortize-breaks.svg&amp;diff=945</id>
		<title>Файл:Pq-03-amortize-breaks.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-03-amortize-breaks.svg&amp;diff=945"/>
		<updated>2026-11-04T18:05:04Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-02-two-stacks.svg&amp;diff=944</id>
		<title>Файл:Pq-02-two-stacks.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-02-two-stacks.svg&amp;diff=944"/>
		<updated>2026-11-04T18:04:55Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-01-persistence.svg&amp;diff=943</id>
		<title>Файл:Pq-01-persistence.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pq-01-persistence.svg&amp;diff=943"/>
		<updated>2026-11-04T18:04:46Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%91%D0%B0%D0%B7%D0%B8%D1%81_%D0%B8_%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%B0&amp;diff=942</id>
		<title>Базис и размерность линеала</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%91%D0%B0%D0%B7%D0%B8%D1%81_%D0%B8_%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%80%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%BB%D0%B8%D0%BD%D0%B5%D0%B0%D0%BB%D0%B0&amp;diff=942"/>
		<updated>2026-11-04T11:35:20Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «{{Определение |definition= Упорядоченная совокупность линейно независимых векторов ${\bf e}_1, {\bf...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Упорядоченная совокупность линейно независимых векторов ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n$ линеала $L$ называется '''базисом линеала''', &lt;br /&gt;
если для каждого вектора $x \in L$ найдутся вещественные числа $\gamma_i$, $i = \overline {1,n}$, что&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
x = \sum\limits_{i = 1}^n {\gamma _i {\bf e}_i}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Последнее равенство называют разложением вектора $x$ по базису ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n$.&lt;br /&gt;
&lt;br /&gt;
На основании данного определения можно утверждать, что в векторном пространстве $V^1$ произвольный ненулевой вектор может быть взят в качестве базисного, в пространстве $V^2$ упорядоченная пара неколлинеарных векторов образует базис, а в $V^3$ упорядоченная тройка некомпланарных векторов образует базис.&lt;br /&gt;
&lt;br /&gt;
В пространстве $R^n$ векторы ${\bf u}_1 = (1,0,..., 0)$, ${\bf u}_2 = (0,1,..., 0)$, ..., ${\bf u}_n = (0,0,..., 1)$ образуют базис, поскольку они линейно независимы и любой вектор $x = (\gamma_1, \gamma_2, ..., \gamma_n ) \in R^n$ представим в виде ${\bf x} = \gamma_1 {\bf u}_1 + \gamma_2 {\bf u}_2 + ... + \gamma_n {\bf u}_n$.&lt;br /&gt;
&lt;br /&gt;
Отметим, что в определении базиса порядок элементов существенен, так как, переставляя элементы базиса, мы получаем снова базис, но уже другой.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Числа $\gamma_i$, $i = \overline {1,n} $, фигурирующие в разложении элемента $x$ линеала $L$ по заданному базису ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n$ называются '''координатами''' данного вектора относительно рассматриваемого базиса.&lt;br /&gt;
}}&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Координаты всякого элемента линеала $L$ относительно заданного базиса определяются однозначно.&lt;br /&gt;
|proof=Допустим, что ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $ {{---}} базис линеала $L$. Пусть для некоторого вектора $x$ наряду с разложением $x = \sum\limits_{i = 1}^n {\gamma _i {\bf e}_i}$ существует еще и другое разложение $x = \sum\limits_{i = 1}^n {\mu _i {\bf e}_i}$. Но тогда справедливо равенство ${\bf 0} = \sum\limits_{i = 1}^n {(\gamma _i - \mu _i ) {\bf e}_i}$. Базисные элементы ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n$ линейно независимы, поэтому для всех $i = \overline {1,n} $ имеем $\gamma_i = \mu_i$.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-05-01-basis-coordinates.svg|thumb|800px|alt=Слева разложение x = 2e1 + e2 по базису e1, e2; справа другой базис f1, f2 и координаты (1; 1)|Рис. 1. Слева &amp;lt;math&amp;gt;x = 2e_1 + e_2&amp;lt;/math&amp;gt;, координаты &amp;lt;math&amp;gt;(2; 1)&amp;lt;/math&amp;gt;. Справа тот же приём с другим базисом &amp;lt;math&amp;gt;f_1 = e_1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;f_2 = e_1 + e_2&amp;lt;/math&amp;gt;: разложение &amp;lt;math&amp;gt;x = f_1 + f_2&amp;lt;/math&amp;gt; даёт другие координаты &amp;lt;math&amp;gt;(1; 1)&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=При сложении элементов линеала $L$ их координаты складываются, а при умножении вектора на вещественное число все его координаты умножаются на данное число.&lt;br /&gt;
|proof=Пусть элементы ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $ образуют базис в $L$, $x$ и ${\bf y}$ {{---}} произвольные элементы из $L$, $\lambda $ {{---}} произвольное вещественное число, ${\bf s} = x + y$, ${\bf p} = \lambda x$.&lt;br /&gt;
Ясно, что $x = \sum\limits_{i = 1}^n {\gamma _i {\bf e}_i}$, $y = \sum\limits_{i = 1}^n {\mu _i e_i}$, ${\bf s} = \sum\limits_{i = 1}^n {\sigma _i e_i}$, ${\bf p} = \sum\limits_{i = 1}^n {\delta _i e_i}$. Используя аксиомы 1-8 линеала $L$, получаем&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\rm {\bf s} } = x + y = \sum\limits_{i = 1}^n {\gamma _i } e_i + \sum\limits_{i = 1}^n {\mu _i } e_i = \sum\limits_{i = 1}^n {(\gamma _i + \mu _i )} e_i, &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
{\rm {\bf p} } = \lambda x = \lambda \left( {\sum\limits_{i = 1}^n {\gamma _i } e_i } \right) = \sum\limits_{i = 1}^n {\lambda \gamma _i } e_i .&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В силу единственности разложения по базису имеем $\sigma _i = \gamma _i + \mu _i $, $\delta _i = \lambda \gamma _i $, $i = \overline {1,n} $.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Если каждый из $(n + 1)$ элементов $y_0, y_1,..., y_n$  линеала $L$ представим в виде линейной комбинации n линейно независимых элементов $x_1, x_2, ..., x_n$ того же линеала, т. е.&lt;br /&gt;
$y_i = \sum\limits_{i = 1}^n {\lambda _{ij} x_i }$, $j = \overline {0,n}$, то элементы $y_0, y_1, ..., y_n $ линейно зависимы.&lt;br /&gt;
}}&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Любые $(n + 1)$ векторов линейного пространства  $R^n$ являются линейно зависимыми.&lt;br /&gt;
|proof=Возьмем произвольно векторы $y_k = (\alpha _{k1}, \alpha _{k2}, ..., \alpha _{kn} )$, $k = \overline {0,n} $. &lt;br /&gt;
Поскольку ${\rm {\bf u} }_1 = (1,0,..., 0)$, ${\rm {\bf u} }_2 = (0,1,..., 0)$, ..., ${\rm {\bf u} }_n = (0,0,..., 1)$ образуют базис в пространстве $R^n$, то для любого $k = \overline {0,n} $ имеем $y_k = \alpha _{k1} {\rm {\bf u} }_1 + \alpha _{k2} {\rm {\bf u} }_2 + \cdots + \alpha _{kn} {\rm {\bf u} }_n $. Следовательно, в силу теоремы 3.5.3 произвольные $(n + 1)$ векторов из линеала $R^n$ линейно зависимы.&lt;br /&gt;
}}&lt;br /&gt;
{{Следствие&lt;br /&gt;
|statement=Любые два базиса линеала $L$ содержат одно и то же число векторов.&lt;br /&gt;
|proof=Пусть ${\bf e}_1, {\bf e}_2 ,..., {\bf e}_n $ базис линеала $L$, а &amp;lt;math&amp;gt;{\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_{n + 1}&amp;lt;/math&amp;gt; другой базис этого же линеала. Так как &amp;lt;math&amp;gt;{\rm {\bf {e}'} }_i = \sum\limits_{j = 1}^n {\alpha _j {\bf e}_j }, i = \overline {1,n + 1} &amp;lt;/math&amp;gt;, причем ${\bf e}_1, {\bf e}_2,..., {\bf e}_n $ линейно независимы. В силу теоремы &amp;lt;math&amp;gt;{\rm {\bf {e}'} }_1, {\rm {\bf {e}'} }_2, ..., {\rm {\bf {e}'} }_{n + 1}&amp;lt;/math&amp;gt; {{---}} линейно зависимы. Полученное противоречие и доказывает следствие.'''&lt;br /&gt;
}}&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Линеал $L$ называют $n$-мерным, если в нем существует базис, состоящий из $n$ векторов.&lt;br /&gt;
}}&lt;br /&gt;
Число $n$ называют '''размерностью''' линеала $L$ и обозначают символом $\dim (L) = n$.&lt;br /&gt;
Таким образом, размерность пространства {{---}} это наибольшее число его линейно независимых элементов.&lt;br /&gt;
Если линеал $L$ является $n-$мерным и необходимо подчеркнуть его размерность, то обычно используют обозначение $L^n$. Ясно, что $\dim (V^1) = 1$, $\dim (V^2) = 2$, $\dim (V^3) = 3.$&lt;br /&gt;
Линеал $\Theta $, содержащий единственный нулевой элемент, является нуль-мерным.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=&lt;br /&gt;
Линеал $L$ называется бесконечномерным, если для любого натурального числа $N$ в нем найдется линейно независимая система, состоящая из $N$ элементов.&lt;br /&gt;
}}&lt;br /&gt;
Примером бесконечномерного линеала является линейное пространство непрерывных на заданном отрезке функций.&lt;br /&gt;
{{Теорема&lt;br /&gt;
|statement=Для того чтобы линеал $L был n$-мерным, необходимо и достаточно, чтобы в нем существовала линейно независимая система, состоящая из $n$ элементов, а всякая система, содержащая более $n$ векторов, была бы линейно зависимой.&lt;br /&gt;
|proof='''Необходимость.''' Если линеал является $n$-мерным, то в нем существует базис ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $, состоящий из $n$ элементов. Произвольный вектор $x_j \in L$, $j = \overline {0,n} $ представим в виде линейной комбинации векторов ${\rm {\bf e} }_1, {\bf e}_2, ..., {\bf e}_n $, поэтому любая совокупность более чем $n$ элементов является линейно зависимой.&lt;br /&gt;
&lt;br /&gt;
'''Достаточность.''' Пусть ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $ линейно независимые элементы. Возьмем произвольный вектор $x \in L$. По условию векторы ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $,$x$ линейно зависимы, т. е. существует нетривиальная линейная комбинация $\alpha _1 {\bf e}_1 + \alpha _2 {\bf e}_2 + ... + \alpha _n {\bf e}_n + \alpha x = {\bf 0}$, причем $\alpha \ne 0$. Но тогда x может быть разложен по ${\bf e}_1, {\bf e}_2, ..., {\bf e}_n $, т. е. эти элементы {{---}} базис линеала $L$.&lt;br /&gt;
В $n$-мерном линеале $L$ всякий базис состоит из n упорядоченных линейно независимых элементов, при этом любой вектор линеала $L^n$ единственным образом представим в виде линейной комбинации базисных элементов.&lt;br /&gt;
}}&lt;br /&gt;
Это утверждение не вытекает из аксиом 1 {{---}} 8 линейного пространства. Поэтому следует сформулировать новую аксиому.&lt;br /&gt;
&lt;br /&gt;
'''9. (аксиома размерности).''' Линейное пространство $L$ конечномерно и его размерность равна $n$.&lt;br /&gt;
При выполнении этой аксиомы из аксиом 1-8 очевидно вытекает сформулированное утверждение.&lt;br /&gt;
&lt;br /&gt;
[[Файл:p03-05-02-dimension-ladder.svg|frame|none|800px|alt=Шесть линеалов с размерностями 0, 1, 2, 3, n и ∞ и пиктограммами числа независимых направлений|Рис. 2. Размерность — наибольшее число независимых направлений: &amp;lt;math&amp;gt;\dim \Theta = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\dim V^1 = 1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\dim V^2 = 2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\dim V^3 = 3&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\dim R^n = n&amp;lt;/math&amp;gt;; пространство непрерывных функций бесконечномерно.]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Конспект: [[Алгебра и геометрия]]&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-05-02-dimension-ladder.svg&amp;diff=941</id>
		<title>Файл:P03-05-02-dimension-ladder.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-05-02-dimension-ladder.svg&amp;diff=941"/>
		<updated>2026-11-04T11:34:54Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-05-01-basis-coordinates.svg&amp;diff=940</id>
		<title>Файл:P03-05-01-basis-coordinates.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:P03-05-01-basis-coordinates.svg&amp;diff=940"/>
		<updated>2026-11-04T11:33:30Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/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%B9_%D1%81%D1%82%D0%B5%D0%BA&amp;diff=939</id>
		<title>Персистентный стек</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/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%B9_%D1%81%D1%82%D0%B5%D0%BA&amp;diff=939"/>
		<updated>2026-11-04T11:22:04Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «== TL;DR ==  '''Персистентный стек''' (persistent stack) — стек, хранящий '''все свои предыдущие версии''':...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентный стек''' (persistent stack) — стек, хранящий '''все свои предыдущие версии''': каждая операция не изменяет структуру, а создаёт новую версию, оставляя старые доступными. Реализуется как '''односвязный список''': узел хранит значение и ссылку &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; на предыдущую версию, а «текущая версия» — это просто ссылка на верхний узел. &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт один новый узел за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает значение и ссылку на предыдущую версию — тоже &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и '''без новых ячеек'''. &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создают ровно &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; новых узлов, а вместе с корнем в структуре &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел, потому что все версии '''разделяют общий хвост''' (sharing): память &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;, а не &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;. Все узлы неизменяемы, поэтому персистентный стек — это '''функциональная''' персистентность; он служит строительным блоком для персистентной очереди ([[Очередь на шести стеках]]), версионирования и неизменяемых списков.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Свойство !! Значение&lt;br /&gt;
|-&lt;br /&gt;
| Класс || Стек (LIFO) с полной персистентностью; функциональная структура данных&lt;br /&gt;
|-&lt;br /&gt;
| Версии || Хранятся все; доступ к версии по ссылке за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, материализация — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; времени, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; времени, 0 новых ячеек (список) / 1 ячейка (массив)&lt;br /&gt;
|-&lt;br /&gt;
| Память || &amp;lt;math&amp;gt;O(V)&amp;lt;/math&amp;gt; под &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; узлов; &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; → &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; новых узлов (вместе с корнем &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| Изменяемость || нет; узлы инициализируются один раз и не меняются&lt;br /&gt;
|-&lt;br /&gt;
| Представления || односвязный список (&amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;) или массив запросов&lt;br /&gt;
|-&lt;br /&gt;
| Разделение || версии делят общие хвосты; ветвление даёт дерево версий&lt;br /&gt;
|-&lt;br /&gt;
| Персистентность || функциональная (сильнейшая форма)&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается || функциональные языки (списки), персистентная очередь, undo/redo, версионирование&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных''' — структура, сохраняющая доступ ко всем предшествующим версиям при выполнении операций. Различают три уровня:&lt;br /&gt;
&lt;br /&gt;
* '''частичная персистентность''' (partial): можно изменять только самую новую версию, но читать — любую;&lt;br /&gt;
* '''полная персистентность''' (full): изменять и читать можно любую версию;&lt;br /&gt;
* '''функциональная персистентность''' (functional): каждая ячейка памяти инициализируется один раз и больше не изменяется, поэтому версии разделяют неизменяемые данные.&lt;br /&gt;
&lt;br /&gt;
'''Версия стека''' — конкретное состояние стека (упорядоченная последовательность элементов). В персистентном стеке версия отождествляется с узлом-вершиной.&lt;br /&gt;
&lt;br /&gt;
'''Узел (node)''' — запись &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;, где &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; — значение на вершине, а &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; — ссылка на предыдущую версию (узел ниже) или &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; для пустой версии. Узел неизменяем.&lt;br /&gt;
&lt;br /&gt;
'''Текущая вершина''' (&amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;) — ссылка на узел, представляющий актуальную версию. Изменение стека не переписывает узлы, а лишь заменяет &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Разделение хвоста (sharing)''' — ситуация, когда несколько версий ссылаются на один и тот же узел (и, следовательно, на общий остаток стека). Безопасно только при неизменяемости узлов.&lt;br /&gt;
&lt;br /&gt;
'''Две реализации.''' ''Списковая'': узлы связаны ссылками. ''Массивная'' (массив запросов): &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет ячейку &amp;lt;code&amp;gt;{x, i}&amp;lt;/code&amp;gt;, а &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — копию версии &amp;lt;code&amp;gt;s[i].prev&amp;lt;/code&amp;gt; (той, что лежит под вершиной); номер новой версии равен &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — число элементов/операций; &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; — число версий; &amp;lt;code&amp;gt;s[i]&amp;lt;/code&amp;gt; — ячейка массива запросов с номером &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный.&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Обычный стек из [[Стек]] хранит '''одно''' состояние: &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; записывает элемент в ячейку, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; её очищает. Прошлые состояния теряются. Если нужно вернуться к предыдущему состоянию — приходится заранее копировать стек целиком, а это &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; времени и памяти на каждую версию и &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; суммарно. См. рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-01-idea.svg|thumb|700px|Рис. 1. Мутирующий стек хранит единственное состояние: &amp;lt;code&amp;gt;push(d)&amp;lt;/code&amp;gt; теряет прежнюю версию; персистентный добавляет новый узел, сохраняя старые версии.]]&lt;br /&gt;
&lt;br /&gt;
Списковая структура стека подсказывает решение. Стек — это цепочка «верх → … → дно». Добавление элемента можно представить не как запись в общую память, а как '''новую голову цепочки''': новый узел хранит добавленное значение и ссылку на прежнюю голову. Тогда старая цепочка никуда не девается — на неё просто никто не указывает как на текущую. Аналогично &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не удаляет узел, а возвращает ссылку на следующий узел цепочки. Такой стек никогда ничего не перезаписывает.&lt;br /&gt;
&lt;br /&gt;
Ключевая идея в одном абзаце: '''вершина — это всего лишь указатель на узел, а узел неизменяем'''; поэтому «изменение» сводится к замене указателя, а вся история сохраняется как цепочка (точнее — дерево) узлов, делящих общие хвосты. Отсюда &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на операцию и &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарной памяти.&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Персистентный стек состоит из узлов &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt; и ссылки &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt; на текущую вершину (см. рис. 2). Пустая версия — это специальный корневой узел без значения; в массиве его роль играет sentinel &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-02-versions.svg|thumb|700px|Рис. 2. Версия = узел; &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет узел перед текущим, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает предыдущий узел.]]&lt;br /&gt;
&lt;br /&gt;
Версии образуют связную структуру: цепочка &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;-ссылок из любого узла ведёт вниз, к корню. Несколько версий могут указывать на один узел — это '''разделение хвостов'''. Ветвление (например, два &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; от одной и той же версии) превращает линию версий в '''дерево версий'''; именно так устроены история в редакторах и снапшоты в базах данных.&lt;br /&gt;
&lt;br /&gt;
Есть два эквивалентных представления (см. рис. 3):&lt;br /&gt;
&lt;br /&gt;
* '''Список.''' Узел хранит &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;; ссылка на узел — это версия. &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт узел за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает пару &amp;lt;code&amp;gt;(value, prev)&amp;lt;/code&amp;gt; и не создаёт узлов.&lt;br /&gt;
* '''Массив запросов.''' Ячейка &amp;lt;code&amp;gt;s[i]&amp;lt;/code&amp;gt; хранит &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt; — значение вершины версии &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt; и индекс её предыдущей версии. &amp;lt;code&amp;gt;push(i, x)&amp;lt;/code&amp;gt; добавляет ячейку &amp;lt;code&amp;gt;{x, i}&amp;lt;/code&amp;gt; и возвращает её индекс &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;pop(i)&amp;lt;/code&amp;gt; возвращает значение &amp;lt;code&amp;gt;s[i].value&amp;lt;/code&amp;gt; и создаёт новую версию — копию версии &amp;lt;code&amp;gt;s[i].prev&amp;lt;/code&amp;gt;, лежащей под вершиной.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-03-array.svg|thumb|700px|Рис. 3. Массив запросов: каждая ячейка = {value, prev}; операция добавляет ровно одну ячейку.]]&lt;br /&gt;
&lt;br /&gt;
Массивное представление удобно, когда версии нумеруются подряд и нужен быстрый доступ по индексу; списковое — когда версии передаются как ссылки, а структура используется как неизменяемое значение (например, список в функциональном языке).&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
=== Операция: Push ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Создать новый узел со значением &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и ссылкой на текущую вершину; объявить его новой вершиной. Прежняя версия сохраняется без изменений.&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание (список):'''&lt;br /&gt;
&lt;br /&gt;
# Создать узел &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; со значением &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и ссылкой &amp;lt;code&amp;gt;prev ← top&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Присвоить &amp;lt;code&amp;gt;top ← k&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Вернуть новую вершину (новую версию).&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Цепочка &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;-ссылок из &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt; перечисляет элементы стека сверху вниз; все ранее созданные узлы сохраняют свои поля.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push(s, x)&lt;br /&gt;
Вход:  персистентный стек s (ссылка top на вершину); x — элемент&lt;br /&gt;
Выход: новая версия стека (ссылка на новый узел)&lt;br /&gt;
Инвариант: элементы версии = цепочка top → prev → … → корень&lt;br /&gt;
Сложность: O(1) времени, O(1) новых ячеек&lt;br /&gt;
&lt;br /&gt;
1  k ← новый узел&lt;br /&gt;
2  k.value ← x&lt;br /&gt;
3  k.prev ← s.top&lt;br /&gt;
4  s.top ← k&lt;br /&gt;
5  return k&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Массивная версия:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push-Array(s, i, x)&lt;br /&gt;
Вход:  массив s запросов; i — номер версии, от которой добавляем; x — элемент&lt;br /&gt;
Выход: номер новой версии&lt;br /&gt;
Сложность: O(1)&lt;br /&gt;
&lt;br /&gt;
1  s.top ← s.top + 1&lt;br /&gt;
2  s[s.top].value ← x&lt;br /&gt;
3  s[s.top].prev ← i&lt;br /&gt;
4  return s.top&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Стек, представленный версией 2 (&amp;lt;code&amp;gt;value = 3, prev = 1&amp;lt;/code&amp;gt;), при &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt; получает ячейку 3 (&amp;lt;code&amp;gt;value = 5, prev = 2&amp;lt;/code&amp;gt;). Версия 2 не изменилась: &amp;lt;code&amp;gt;s[2]&amp;lt;/code&amp;gt; по-прежнему &amp;lt;code&amp;gt;{3, 1}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; и на списке, и на массиве: одна запись, одна ссылка.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; в пустую версию даёт узел со ссылкой на корень. Если требуется добавить элемент в ''прошлую'' версию, создаётся ветвь — текущая линия не затрагивается.&lt;br /&gt;
&lt;br /&gt;
См. рис. 1 и рис. 2.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Pop ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Вернуть значение текущей вершины и «перейти» на предыдущую версию. В списковой реализации это просто возврат ссылки &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;; в массивной — запись новой ячейки, повторяющей предыдущую версию (см. рис. 4).&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание (список):'''&lt;br /&gt;
&lt;br /&gt;
# Если стек пуст — ошибка &amp;lt;code&amp;gt;underflow&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Запомнить значение &amp;lt;code&amp;gt;x ← top.value&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Перейти к предыдущей версии: &amp;lt;code&amp;gt;top ← top.prev&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Вернуть пару &amp;lt;code&amp;gt;(x, top)&amp;lt;/code&amp;gt; — значение и новую версию.&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Новая версия содержит все элементы исходной, кроме верхнего; исходная версия остаётся доступной по прежней ссылке.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop(s)&lt;br /&gt;
Вход:  персистентный стек s (top ≠ корень)&lt;br /&gt;
Выход: пара (x, s) — верхний элемент и новая версия s&lt;br /&gt;
Сложность: O(1) времени, 0 новых ячеек&lt;br /&gt;
&lt;br /&gt;
1  if s.top = корень&lt;br /&gt;
2      error &amp;quot;попытка pop из пустого стека&amp;quot;&lt;br /&gt;
3  x ← s.top.value&lt;br /&gt;
4  s.top ← s.top.prev&lt;br /&gt;
5  return (x, s)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Массивная версия (копирует предыдущую ячейку):'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop-Array(s, i)&lt;br /&gt;
Вход:  массив s; i — номер версии (i ≠ корень)&lt;br /&gt;
Выход: (значение, номер новой версии)&lt;br /&gt;
Сложность: O(1)&lt;br /&gt;
&lt;br /&gt;
1  if s[i].prev = null&lt;br /&gt;
2      error &amp;quot;pop из пустого стека&amp;quot;&lt;br /&gt;
3  x ← s[i].value&lt;br /&gt;
4  k ← s[s[i].prev]                 # узел под вершиной&lt;br /&gt;
5  Push-Array(s, k.prev, k.value)   # новая ячейка = копия версии под i&lt;br /&gt;
6  return (x, s.top)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; — sentinel пустой версии (в C++-реализации ниже он кодируется индексом 0). Поэтому &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; от версии из одного элемента создаёт новую пустую ячейку с &amp;lt;code&amp;gt;prev = null&amp;lt;/code&amp;gt; (в трассировке — ячейка 7).&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Для &amp;lt;code&amp;gt;s = [a, b, c]&amp;lt;/code&amp;gt; (вершина &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt;) операция &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; вернёт &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; и версию &amp;lt;code&amp;gt;[a, b]&amp;lt;/code&amp;gt;; версия &amp;lt;code&amp;gt;[a, b, c]&amp;lt;/code&amp;gt; сохраняется. В массивной версии &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt; для ячейки 3 вернул &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt; и завёл ячейку 4, повторяющую версию 2.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; в списке ноль новых ячеек, в массиве — ровно одна.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из пустой версии — ошибка. Если &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; применяется к «прошлой» версии, новые ячейки пишутся в конец массива, а прежние ветви остаются нетронутыми.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-04-pop.svg|thumb|700px|Рис. 4. &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не удаляет данные: он лишь указывает на предыдущую версию.]]&lt;br /&gt;
&lt;br /&gt;
=== Операция: Top и Empty ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Вершина и признак пустоты — чтение за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Top(s)&lt;br /&gt;
Вход:  персистентный стек s&lt;br /&gt;
Выход: значение верхнего элемента&lt;br /&gt;
Инвариант: s.top — вершина текущей версии&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  if s.top = корень&lt;br /&gt;
2      error &amp;quot;вершина пустого стека не определена&amp;quot;&lt;br /&gt;
3  return s.top.value&lt;br /&gt;
&lt;br /&gt;
Операция Empty(s)&lt;br /&gt;
Вход:  персистентный стек s&lt;br /&gt;
Выход: true, если текущая версия пуста&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  return (s.top = корень)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; пустой версии — ошибка; &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; для корня возвращает &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Доступ к произвольной версии ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Версия — это ссылка (или индекс), поэтому «переход» к ней бесплатен. Материализация (выписывание всех элементов версии) стоит &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция To-List(v)                 # материализация версии v&lt;br /&gt;
Вход:  вершина v (версия стека)&lt;br /&gt;
Выход: список значений сверху вниз&lt;br /&gt;
Инвариант: v — ещё не пройденная часть версии&lt;br /&gt;
Сложность: O(k) времени и O(k) памяти для k элементов&lt;br /&gt;
&lt;br /&gt;
1  out ← пустой список&lt;br /&gt;
2  while v ≠ корень&lt;br /&gt;
3      out.append(v.value)&lt;br /&gt;
4      v ← v.prev&lt;br /&gt;
5  return out&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов; при этом версии не мешают друг другу.&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Время !! Новых ячеек !! Память&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || +1 узел&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 (список) / 1 (массив) || 0 (список) / 1 ячейка (массив)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| Доступ к версии || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || 0&lt;br /&gt;
|-&lt;br /&gt;
| Материализация версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарно || — || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; узлов&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Оговорка: в списковой и массивной моделях '''амортизация не нужна''' — каждая операция уже &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. В C++-реализации ниже используется &amp;lt;code&amp;gt;std::vector&amp;lt;/code&amp;gt;: его &amp;lt;code&amp;gt;push_back&amp;lt;/code&amp;gt; даёт амортизированный &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, поэтому для строгого худшего случая нужен заранее выделенный пул с резервом. Суммарная память пропорциональна '''числу созданных версий''', а не их размерам, потому что версии делят общие хвосты.&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Повторим последовательность из источника для массивной реализации: &amp;lt;code&amp;gt;push(1, 3)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(3, 6)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(5, 1)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop(5)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;push(7, 9)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! index !! value !! prev !! Операция&lt;br /&gt;
|-&lt;br /&gt;
| 1 || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || начальная пустая версия&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 3 || 1 || &amp;lt;code&amp;gt;push(1, 3)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 5 || 2 || &amp;lt;code&amp;gt;push(2, 5)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 3 || 1 || &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt;: вернул 5, скопировал версию 2&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 6 || 3 || &amp;lt;code&amp;gt;push(3, 6)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 6 || 1 || 5 || &amp;lt;code&amp;gt;push(5, 1)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 7 || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt;: вернул 3, скопировал версию 1 (пустую)&lt;br /&gt;
|-&lt;br /&gt;
| 8 || 5 || 2 || &amp;lt;code&amp;gt;pop(5)&amp;lt;/code&amp;gt;: вернул 6, скопировал версию 3&lt;br /&gt;
|-&lt;br /&gt;
| 9 || 9 || 7 || &amp;lt;code&amp;gt;push(7, 9)&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Разберём строку 4: &amp;lt;code&amp;gt;pop(3)&amp;lt;/code&amp;gt; читает ячейку 3 (&amp;lt;code&amp;gt;value = 5, prev = 2&amp;lt;/code&amp;gt;), возвращает &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;, а новой версией делает копию ячейки под ней — ячейка 4 повторяет версию 2 (&amp;lt;code&amp;gt;value = 3, prev = 1&amp;lt;/code&amp;gt;). Строка 7: &amp;lt;code&amp;gt;pop(4)&amp;lt;/code&amp;gt; возвращает &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;, а новой версией становится пустая (ячейка 7 повторяет корень). Все прошлые ячейки (1–6) остаются доступными: в массиве запросов ничего не перезаписывается.&lt;br /&gt;
&lt;br /&gt;
== Корректность и оценка сложности ==&lt;br /&gt;
&lt;br /&gt;
=== Инвариант ===&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Для любого узла &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; цепочка &amp;lt;math&amp;gt;v, \mathrm{prev}(v), \mathrm{prev}^2(v), \dots&amp;lt;/math&amp;gt; заканчивается корнем, и значения узлов вдоль цепочки образуют содержимое версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; сверху вниз.&lt;br /&gt;
&lt;br /&gt;
'''База.''' Корень — пустая версия, цепочка длины 0.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;.''' Новый узел &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; указывает на прежнюю вершину &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, поэтому цепочка из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; есть &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; + цепочка из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;; значения — &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; + содержимое &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, что и требуется. Ни один старый узел не изменён.&lt;br /&gt;
&lt;br /&gt;
'''Переход &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;.''' Новая вершина — &amp;lt;math&amp;gt;\mathrm{prev}(v)&amp;lt;/math&amp;gt;; её цепочка есть содержимое версии без верхнего элемента. Исходная ссылка &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; сохраняет старый стек.&lt;br /&gt;
&lt;br /&gt;
Отсюда &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt; возвращает верхний элемент, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; корректно распознаёт корень, а любая версия читается за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по ссылке (переход к прошлому состоянию — операция над указателем).&lt;br /&gt;
&lt;br /&gt;
=== Оценка памяти: разделение хвостов ===&lt;br /&gt;
&lt;br /&gt;
Каждый &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; создаёт '''ровно один''' новый узел; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в списковой реализации не создаёт узлов вовсе. Значит, после &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; в структуре &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел (включая корень), то есть &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; памяти — независимо от того, сколько версий сохранено и каковы их размеры (см. рис. 5). Если бы каждая версия хранилась отдельным массивом, суммарная память была бы &amp;lt;math&amp;gt;1 + 2 + \dots + n = \Theta(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-05-sharing.svg|thumb|700px|Рис. 5. Ветви делят общий хвост: &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; дают &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узел, а не &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; памяти.]]&lt;br /&gt;
&lt;br /&gt;
=== Время ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Top&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; выполняют постоянное число операций над указателями и записями — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. Материализация версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов требует прохода по &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; узлам — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;; это неизбежно, поскольку выписываются &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; значений.&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Структура !! &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; !! &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; !! Доступ к версиям !! Память под &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версий&lt;br /&gt;
|-&lt;br /&gt;
| Обычный стек ([[Стек]]) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || нет (одно состояние) || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Обычный стек + полное копирование || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; + &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; копия || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || да || &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| '''Персистентный стек (список)''' || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 узел || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 0 узлов || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Персистентный стек (массив запросов) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 ячейка || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, 1 ячейка || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Персистентная очередь ([[Очередь на шести стеках]]) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || да, &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; суммарно (&amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых на операцию)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Вердикт.''' Нужны прошлые состояния стека — только персистентная реализация; копирование неприемлемо из-за &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt;. Между списком и массивом выбор по стилю: список удобнее для функционального кода и передачи версий как значений, массив — для нумерованных версий и индексного доступа. Персистентная очередь — следующий шаг: стек — её элементарный кирпич.&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки и неизменяемые списки.''' Односвязный список в LISP, Haskell, OCaml, Standard ML — это персистентный стек: добавление в голову за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, хвосты разделяются. Почему: скрытая мутация в чистом функциональном языке невозможна, а неизменяемый список её и не требует [2][3].&lt;br /&gt;
&lt;br /&gt;
# '''Персистентная очередь и другие персистентные структуры.''' Делая стек персистентным, мы получаем строительный блок для очереди ([[Очередь на шести стеках]]), дека и приоритетной очереди. Почему: многие структуры сводятся к комбинации стеков/списков [1][2].&lt;br /&gt;
&lt;br /&gt;
# '''Undo/redo и версионирование.''' Редакторы, СУБД с MVCC, системы контроля версий и транзакционные снапшоты хранят историю как версии структуры; откат — переход по ссылке без копирования данных [3][4].&lt;br /&gt;
&lt;br /&gt;
# '''Обход и разбор с ветвлением.''' В backtracking, разборе выражений и DFS путь можно хранить как персистентный стек: каждое ветвление — новая версия, а возврат в точку выбора — переход по сохранённой ссылке. Почему: состояние стека восстанавливается за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; [2].&lt;br /&gt;
&lt;br /&gt;
# '''Параллельные и распределённые вычисления.''' Неизменяемые данные естественно разделяются между потоками и репликами без блокировок: структура, разделяемая несколькими потребителями, не может быть испорчена мутацией [3][4].&lt;br /&gt;
&lt;br /&gt;
Обзор основных областей — на рис. 6.&lt;br /&gt;
&lt;br /&gt;
[[Файл:pstack-06-apply.svg|frame|none|700px|Рис. 6. Персистентная очередь, версионирование и undo, функциональные списки, история обхода и разбора, параллельные вычисления — пять областей, где персистентный стек служит фундаментом.]]&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Идея функциональных списков-стеков восходит к языку '''LISP''' (Джон Маккарти, 1958–1960): операции &amp;lt;code&amp;gt;cons&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;car&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cdr&amp;lt;/code&amp;gt; строят разделяемые неизменяемые цепочки — фактически персистентный стек [2]. Термин «персистентные структуры данных» и систематическое изучение методов (путь-копирование, fat nodes, node-copying) ввели '''Дрисколл, Сарнак, Слитор и Тарьян''' в работе ''Making Data Structures Persistent'' (STOC 1986; журнальная версия — JCSS, 1989) [4]. Формальный амортизированный анализ, которым описывают стоимость операций, развил '''Роберт Тарьян''' («Amortized Computational Complexity», 1985) [5].&lt;br /&gt;
&lt;br /&gt;
Учебное изложение персистентного стека (список и массив запросов) — конспект кафедры компьютерных технологий ИТМО на «Викиконспектах» [1]; оттуда же распространён разбор персистентной очереди. Понятие «функциональная персистентность» и коллекция персистентных структур систематизированы '''Крисом Окасаки''' в книге ''Purely Functional Data Structures'' (Cambridge University Press, 1998) [3].&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Путать персистентность со снапшотом-копией.''' Персистентный стек не копирует содержимое при каждой версии: узлы общие, копируется только «верх» (один узел).&lt;br /&gt;
* '''Мутировать узел.''' Изменение &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; существующего узла немедленно испортит все версии, которые через него проходят. Узлы обязаны быть неизменяемыми.&lt;br /&gt;
* '''Освобождать память «оптимизацией».''' Удаление узлов из-под старых версий ломает персистентность; сборка мусора нужна, но только когда на узел не ссылается ни одна версия.&lt;br /&gt;
* '''Считать память пропорциональной числу версий, умноженному на их размер.''' Правильная оценка — &amp;lt;math&amp;gt;O(\text{число операций})&amp;lt;/math&amp;gt; благодаря разделению хвостов.&lt;br /&gt;
* '''Ожидать &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; от &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в массивной реализации без учёта новой ячейки.''' В массиве &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; добавляет запись; это всё ещё &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но память растёт на каждую модификацию.&lt;br /&gt;
* '''Переполнение/уход индексов в массиве запросов.''' Версии нумеруются монотонно; смешение «номера версии» и «индекса ячейки» при ветвлении приводит к ошибкам.&lt;br /&gt;
* '''Смешивать ''current'' и ''версию''.''' Возвращая из операции узел, а не пару «результат + новая версия», легко потерять возможность продолжать от прежней версии.&lt;br /&gt;
* '''Забыть про корень.''' Пустая версия должна быть корректным состоянием: без корня &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; до конца и &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; работают неверно.&lt;br /&gt;
* '''Ожидать, что &amp;lt;code&amp;gt;To-List&amp;lt;/code&amp;gt; бесплатен.''' Материализация версии — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;; путать её с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; доступом к самой версии нельзя.&lt;br /&gt;
* '''Полагать, что персистентность автоматически даёт быстрый произвольный &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;.''' Для очереди на двух стеках этого мало — требуется дробление переноса (см. [[Очередь на шести стеках]]).&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Базовые операции.''' Реализуйте персистентный стек на списке (&amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;) с &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt;. Ограничение: узлы неизменяемы. ''Подсказка:'' &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращает пару «значение + новая версия».&lt;br /&gt;
&lt;br /&gt;
# '''Ветвление.''' В списковой реализации от версии &amp;lt;code&amp;gt;[a, b, c]&amp;lt;/code&amp;gt; примените &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, затем &amp;lt;code&amp;gt;push(d)&amp;lt;/code&amp;gt;, затем ещё раз &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Сколько версий существует и какие у них содержимые? ''Подсказка:'' постройте дерево версий.&lt;br /&gt;
&lt;br /&gt;
# '''Память.''' Для списковой реализации докажите, что &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и любые &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; порождают не более &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; узлов. ''Подсказка:'' каждый &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет ровно один узел, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — ноль.&lt;br /&gt;
&lt;br /&gt;
# '''Массив запросов.''' Реализуйте массивную версию и воспроизведите трассировку из раздела «Трассировка примера». Сверьте значения &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Материализация.''' Напишите &amp;lt;code&amp;gt;To-List(v)&amp;lt;/code&amp;gt; и оцените время; добейтесь &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов. ''Подсказка:'' идти по &amp;lt;code&amp;gt;prev&amp;lt;/code&amp;gt; от вершины к корню.&lt;br /&gt;
&lt;br /&gt;
# '''Общий хвост.''' Для двух версий найдите длину общего хвоста, используя только поля &amp;lt;code&amp;gt;{value, prev}&amp;lt;/code&amp;gt;; добейтесь времени &amp;lt;math&amp;gt;O(\ell_1 + \ell_2)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\ell_i&amp;lt;/math&amp;gt; — длины версий. ''Дополнительно:'' добавьте &amp;lt;code&amp;gt;depth&amp;lt;/code&amp;gt; и двоичные подъёмы (binary lifting); оцените preprocessing и время запроса LCA. ''Подсказка:'' без глубины сначала найдите глубины, затем выровняйте вершины и идите вверх вместе.&lt;br /&gt;
&lt;br /&gt;
# '''Персистентная очередь.''' Используя персистентный стек, постройте персистентную очередь и оцените худшее время &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. ''Подсказка:'' два стека; обсудите, почему нужен &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае.&lt;br /&gt;
&lt;br /&gt;
# '''Сравнение.''' Сравните сумму памяти для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версий при (а) полном копировании, (б) списковой персистентности, (в) массивной. Приведите асимптотики и пример численно при &amp;lt;math&amp;gt;n = 1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Стек]] — базовый LIFO-стек; мутирующая реализация.&lt;br /&gt;
* [[Персистентная очередь]] — персистентность очереди целиком.&lt;br /&gt;
* [[Очередь на шести стеках]] — очередь реального времени на шести стеках.&lt;br /&gt;
* [[Асимптотическая сложность]] — оценки &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, амортизация, худший случай.&lt;br /&gt;
* [[Персистентные структуры данных]] — общая теория: путь-копирование, fat nodes.&lt;br /&gt;
* [[Декартово дерево]] — персистентный treap; ещё один пример персистентности.&lt;br /&gt;
* [[Двоичное дерево поиска]] — персистентные структуры на деревьях.&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Викиконспекты ИТМО: Персистентный стек (реализации на массиве и на списке, пример). URL: https://neerc.ifmo.ru/wiki/index.php?title=Персистентный_стек.&lt;br /&gt;
# McCarthy J. Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I // Communications of the ACM. — 1960. — Vol. 3, № 4 (основы LISP и списков).&lt;br /&gt;
# Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998.&lt;br /&gt;
# Driscoll J. R., Sarnak N., Sleator D. D., Tarjan R. E. Making Data Structures Persistent // STOC 1986; журнальная версия: Journal of Computer and System Sciences. — 1989. — Vol. 38.&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Cormen T. H., Leiserson C. E., Rivest R. L., Stein C. ''Introduction to Algorithms'' (CLRS). 3rd ed. — MIT Press, 2009. — Гл. 10 (элементарные структуры: стек, список).&lt;br /&gt;
# Хабрахабр. Персистентные структуры, часть 1: персистентный стек (пост 113585). URL: https://habr.com/ru/post/113585/.&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-06-apply.svg&amp;diff=938</id>
		<title>Файл:Pstack-06-apply.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-06-apply.svg&amp;diff=938"/>
		<updated>2026-11-04T11:21:36Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-05-sharing.svg&amp;diff=937</id>
		<title>Файл:Pstack-05-sharing.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-05-sharing.svg&amp;diff=937"/>
		<updated>2026-11-04T11:21:29Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-04-pop.svg&amp;diff=936</id>
		<title>Файл:Pstack-04-pop.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-04-pop.svg&amp;diff=936"/>
		<updated>2026-11-04T11:21:21Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-03-array.svg&amp;diff=935</id>
		<title>Файл:Pstack-03-array.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-03-array.svg&amp;diff=935"/>
		<updated>2026-11-04T11:21:13Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-02-versions.svg&amp;diff=934</id>
		<title>Файл:Pstack-02-versions.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-02-versions.svg&amp;diff=934"/>
		<updated>2026-11-04T11:21:06Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-01-idea.svg&amp;diff=933</id>
		<title>Файл:Pstack-01-idea.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pstack-01-idea.svg&amp;diff=933"/>
		<updated>2026-11-04T11:20:57Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C_%D0%BD%D0%B0_%D1%88%D0%B5%D1%81%D1%82%D0%B8_%D1%81%D1%82%D0%B5%D0%BA%D0%B0%D1%85&amp;diff=932</id>
		<title>Очередь на шести стеках</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%9E%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D1%8C_%D0%BD%D0%B0_%D1%88%D0%B5%D1%81%D1%82%D0%B8_%D1%81%D1%82%D0%B5%D0%BA%D0%B0%D1%85&amp;diff=932"/>
		<updated>2026-11-04T10:14:09Z</updated>

		<summary type="html">&lt;p&gt;СВ: Новая страница: «== TL;DR ==  Обычная очередь на '''двух стеках''' работает амортизированно за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но отде...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
Обычная очередь на '''двух стеках''' работает амортизированно за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но отдельная операция может стоить &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;: когда выходной стек пуст, приходится разом перевернуть весь входной. '''Очередь на шести стеках''' (real-time queue) распределяет этот перенос на маленькие порции — не более '''3 действий за операцию''' — и тем самым даёт '''&amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае''' на каждую операцию, то есть работает в реальном времени. Стеки &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt; и переменные &amp;lt;math&amp;gt;\mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}&amp;lt;/math&amp;gt; следят за фоновым перекопированием, а копии &amp;lt;math&amp;gt;R_c, R_c'&amp;lt;/math&amp;gt; обслуживают &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, пока выход перестраивается. Из этой конструкции одной заменой &amp;lt;math&amp;gt;(R_c, R_c') \to R'&amp;lt;/math&amp;gt; получается '''персистентная очередь на пяти стеках''': каждая версия занимает &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек, а операции остаются &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае.&lt;br /&gt;
&lt;br /&gt;
== Инфобокс ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Свойство !! Значение&lt;br /&gt;
|-&lt;br /&gt;
| Класс || Очередь (FIFO) в реальном времени; на стеках&lt;br /&gt;
|-&lt;br /&gt;
| Порядок доступа || FIFO&lt;br /&gt;
|-&lt;br /&gt;
| Худший случай || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Амортизированный || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (совпадает с худшим — амортизация не нужна)&lt;br /&gt;
|-&lt;br /&gt;
| Память || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; под элементы; &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; переноса на операцию (не более 6 стеков)&lt;br /&gt;
|-&lt;br /&gt;
| Стеки || &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Переменные || &amp;lt;math&amp;gt;\mathtt{recopy}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{copied}&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Режимы || обычный и перекопирования&lt;br /&gt;
|-&lt;br /&gt;
| Сравнение || 2 стека — амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, худший &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; 6 стеков — худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;; 5 стеков — ещё и персистентность&lt;br /&gt;
|-&lt;br /&gt;
| Авторы идеи || Hood, Melville (1980); персистентный вариант — учебная традиция ИТМО, Okasaki&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается || функциональные языки, персистентные структуры, системы реального времени, версионирование&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Очередь''' (см. [[Очередь]] (план)) — коллекция с порядком '''FIFO''': &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; добавляет в конец, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; забирает из начала. Реализуется кольцевым буфером, списком или двумя стеками.&lt;br /&gt;
&lt;br /&gt;
'''Реализация на двух стеках.''' Входной стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; принимает &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;, выходной &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; обслуживает &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Когда &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; пуст, все элементы &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переворачиваются в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; разбирается сверху, каждый элемент кладётся в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;. Разовый перенос стоит &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;, поэтому &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; здесь — '''амортизированный''', а не худший.&lt;br /&gt;
&lt;br /&gt;
'''Очередь в реальном времени (real-time queue).''' Очередь, у которой каждая операция выполняется за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; '''в худшем случае''', без «долгих» вызовов. Синоним: очередь с ограниченным (постоянным) временем отклика.&lt;br /&gt;
&lt;br /&gt;
'''Перекопирование (recopy mode).''' Фоновый режим, в котором часть работы переноса &amp;lt;math&amp;gt;(L \to R)&amp;lt;/math&amp;gt; выполняется маленькими порциями параллельно с обычными операциями. Начинается, когда &amp;lt;math&amp;gt;L.size &amp;gt; R.size&amp;lt;/math&amp;gt;, и заканчивается, когда служебный стек &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; опустеет.&lt;br /&gt;
&lt;br /&gt;
'''Шесть стеков.''' Основные: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — вход обычного режима, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — выход обычного режима. Вспомогательные: &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; — вход во время перекопирования; &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; — буфер двойного переворота; &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копия текущего &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (из неё идут &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; во время перекопирования); &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; — копия строящегося &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Переменные состояния.''' &amp;lt;math&amp;gt;\mathtt{recopy}&amp;lt;/math&amp;gt; — булев флаг режима; &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; — сколько элементов в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; ещё «корректны» (принадлежат очереди); &amp;lt;math&amp;gt;\mathtt{copied}&amp;lt;/math&amp;gt; — начат ли перенос &amp;lt;math&amp;gt;L \to R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных.''' Структура, к которой доступны все прошлые версии. '''Функциональная персистентность''' — сильнейшая форма: ячейка памяти инициализируется один раз и больше не меняется, поэтому версии разделяют неизменяемые данные. См. [[Персистентные структуры данных]].&lt;br /&gt;
&lt;br /&gt;
'''Персистентная очередь на пяти стеках.''' Вариант, где пара &amp;lt;math&amp;gt;(R_c, R_c')&amp;lt;/math&amp;gt; заменена одной неизменяемой версией &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;, а версия очереди описывается записью &amp;lt;math&amp;gt;Q = \langle L, L', R, R', S, \mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}\rangle&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — число элементов очереди; &amp;lt;math&amp;gt;|X|&amp;lt;/math&amp;gt; — размер стека &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный. Стеки трактуются как последовательности «дно … верх».&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Очередь на двух стеках красива, но у неё есть скрытый дефект: один &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; на пустом &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; тянет за собой перенос всех элементов — &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; работы и, что важнее для персистентности, &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; '''новых ячеек памяти''' в одной операции. Для систем реального времени (планировщики, обработка сигналов, сетевые буферы) задержка должна быть ограничена сверху; один вызов, пропорциональный размеру структуры, недопустим. См. рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-01-two-stacks.svg|thumb|700px|Рис. 1. Перенос &amp;lt;math&amp;gt;L \to R&amp;lt;/math&amp;gt; происходит разом и даёт пик &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; задача — размазать его по операциям так, чтобы каждая стоила &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Ключевая идея: '''дробить дорогую работу'''. Пока очередь обслуживает обычные &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, она потихоньку готовит новый выходной стек, выполняя ровно '''3 действия''' за операцию. Если начать перекопирование заранее — когда &amp;lt;math&amp;gt;|L|&amp;lt;/math&amp;gt; лишь на единицу превысил &amp;lt;math&amp;gt;|R|&amp;lt;/math&amp;gt;, — то на перенос останется ровно столько операций, сколько нужно, и «долгих» вызовов не будет вовсе.&lt;br /&gt;
&lt;br /&gt;
Почему именно шесть стеков? Во время переноса стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; разбирается, поэтому новые &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; надо куда-то девать — так появляется &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt;. Пока строится новый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, старый выход ещё должен отвечать на &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; — так появляется копия &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;. У копии тоже есть версия «на будущее» — &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Наконец, старый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; может не успеть опустеть (если операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; было много), поэтому его содержимое временно уводят в буфер &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, чтобы через двойной переворот вернуть уже над новыми элементами &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;. Итого &amp;lt;math&amp;gt;L, L', R, R_c, R_c', S&amp;lt;/math&amp;gt; — шесть стеков.&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Очередь хранит шесть стеков и три переменные; вместе они реализуют FIFO с постоянным временем отклика (см. рис. 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-02-roles.svg|frame|none|700px|Рис. 2. &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — вход обычного режима, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — выход; &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; принимает вход во время перекопирования, &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; — копии выхода, &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; — буфер двойного переворота.]]&lt;br /&gt;
&lt;br /&gt;
Работа идёт в '''двух режимах''' (см. рис. 3):&lt;br /&gt;
&lt;br /&gt;
* '''Обычный режим.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; кладёт в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (и синхронно из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;); &amp;lt;math&amp;gt;\mathtt{empty} = (R = \varnothing)&amp;lt;/math&amp;gt;. После каждой операции проверка &amp;lt;math&amp;gt;\mathtt{recopy} \leftarrow (|L| &amp;gt; |R|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* '''Режим перекопирования.''' &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; кладёт в &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берёт из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\mathtt{empty} = \text{false}&amp;lt;/math&amp;gt;; каждая операция делает не более 3 переносов. Режим длится, пока &amp;lt;math&amp;gt;S \neq \varnothing&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-03-modes.svg|thumb|700px|Рис. 3. Переключение режимов: вход в перекопирование по &amp;lt;math&amp;gt;\lvert L\rvert &amp;gt; \lvert R\rvert&amp;lt;/math&amp;gt;, выход — по пустому &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Перекопирование не должно затянуться дольше, чем простоит старый выход, — иначе не хватит свободного стека. Гарантию дают '''инварианты''' (см. рис. 4). В обычном режиме: &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — половины очереди, &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копия &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; подчищены так, что &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|L'| = |S| = 0&amp;lt;/math&amp;gt;. В режиме перекопирования: &amp;lt;math&amp;gt;|R_c| \ge \mathtt{toCopy}&amp;lt;/math&amp;gt; (равенство, пока из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; не переносили корректных элементов), и если &amp;lt;math&amp;gt;|L| = 0&amp;lt;/math&amp;gt;, то либо первые &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; элементов &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; корректны, либо (при &amp;lt;math&amp;gt;\mathtt{toCopy} \le 0&amp;lt;/math&amp;gt;) стек &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; уже содержит весь правый кусок очереди в правильном порядке.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-04-invariants.svg|thumb|700px|Рис. 4. Инвариант &amp;lt;math&amp;gt;\lvert R_c'\rvert \le \lvert R\rvert - \lvert L\rvert&amp;lt;/math&amp;gt; гарантирует, что остаток &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; рассосётся по элементу за операцию до следующего перекопирования.]]&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
Ниже — операции очереди. Обозначим &amp;lt;math&amp;gt;|X| = \mathtt{X.size}&amp;lt;/math&amp;gt;; стек как объект со стандартными &amp;lt;math&amp;gt;\mathtt{push}, \mathtt{pop}&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;\mathtt{swap}&amp;lt;/math&amp;gt; меняет местами переменные-стеки.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Empty ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме очередь пуста тогда и только тогда, когда пуст &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; в режиме&lt;br /&gt;
перекопирования очередь заведомо непуста (см. лемму о корректности &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; ниже).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Empty(Q)&lt;br /&gt;
Вход:  очередь Q&lt;br /&gt;
Выход: true, если элементов нет&lt;br /&gt;
Инвариант: R = ∅ ⟺ Q = ∅ (в обычном режиме)&lt;br /&gt;
Сложность: O(1) времени, O(1) памяти&lt;br /&gt;
&lt;br /&gt;
1  return (not Q.recopy) and (|R| = 0)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Во время перекопирования &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; всегда &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, даже если все «старые» элементы уже извлечены: за время переноса выполняется не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; извлечений, а в очереди было &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; элементов, поэтому неперекопированный остаток лежит в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Push ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме элемент идёт в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;; попутно подчищаем &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; и проверяем, не пора ли начать перекопирование. В режиме перекопирования элемент идёт в &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; (стек &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; сейчас разбирается), и продолжаем фоновую работу.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Push(Q, x)&lt;br /&gt;
Вход:  очередь Q; x — элемент&lt;br /&gt;
Выход: x добавлен в конец очереди&lt;br /&gt;
Инвариант: в обычном режиме Q = R (сверху вниз) ++ reverse(L)&lt;br /&gt;
Сложность: O(1) в худшем случае (не более 3 переносов)&lt;br /&gt;
&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      push(L, x)&lt;br /&gt;
3      if |Rc'| &amp;gt; 0&lt;br /&gt;
4          pop(Rc')                       # рассасываем остаток копии&lt;br /&gt;
5      Check-Recopy(Q)&lt;br /&gt;
6  else&lt;br /&gt;
7      push(L', x)&lt;br /&gt;
8      Check-Normal(Q)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если очередь была пуста, &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; запускает перекопирование (так как &amp;lt;math&amp;gt;|L| = 1 &amp;gt; 0 = |R|&amp;lt;/math&amp;gt;), но завершает его сразу же за один вызов, попадая в обычный режим.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Pop ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' В обычном режиме берём верх &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (это самый старый элемент) и синхронно чистим &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;. В режиме перекопирования элемент берётся из копии &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;; если &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt;, он «оплачен» корректным элементом из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, иначе берём и из перестраиваемого &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Pop(Q)&lt;br /&gt;
Вход:  очередь Q&lt;br /&gt;
Выход: самый старый элемент; error, если очередь пуста&lt;br /&gt;
Инвариант: возвращаемый элемент — минимальный по времени поступления&lt;br /&gt;
Сложность: O(1) в худшем случае (не более 3 переносов)&lt;br /&gt;
&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      x ← pop(R)&lt;br /&gt;
3      pop(Rc)&lt;br /&gt;
4      if |Rc'| &amp;gt; 0&lt;br /&gt;
5          pop(Rc')&lt;br /&gt;
6      Check-Recopy(Q)&lt;br /&gt;
7      return x&lt;br /&gt;
8  else&lt;br /&gt;
9      x ← pop(Rc)&lt;br /&gt;
10     if Q.toCopy &amp;gt; 0&lt;br /&gt;
11         Q.toCopy ← Q.toCopy − 1&lt;br /&gt;
12     else&lt;br /&gt;
13         pop(R)&lt;br /&gt;
14         pop(Rc')&lt;br /&gt;
15     Check-Normal(Q)&lt;br /&gt;
16     return x&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если в режиме перекопирования &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; дошёл до нуля, значит, оставшиеся старые элементы уже продублированы в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; тогда &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; синхронно снимает элемент из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Обращение к пустой очереди — ошибка (&amp;lt;code&amp;gt;underflow&amp;lt;/code&amp;gt;), как и в любой очереди.&lt;br /&gt;
&lt;br /&gt;
=== Операция: Check-Recopy ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' После операции в обычном режиме проверить, не перерос ли вход выход; если да — переключиться в режим перекопирования и сделать первый пакет переносов.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Check-Recopy(Q)&lt;br /&gt;
1  Q.recopy ← (|L| &amp;gt; |R|)&lt;br /&gt;
2  if Q.recopy&lt;br /&gt;
3      Q.toCopy ← |R|                    # столько старых элементов ещё «в деле»&lt;br /&gt;
4      Q.copied ← false                 # перенос L → R ещё не начат&lt;br /&gt;
5      Check-Normal(Q)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Операция: Check-Normal ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Доделать пакет переносов; режим перекопирования продолжается, пока непуст &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Check-Normal(Q)&lt;br /&gt;
1  Additional-Operations(Q)&lt;br /&gt;
2  Q.recopy ← (|S| ≠ 0)                 # S пуст — перекопирование завершено&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Операция: Additional-Operations ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' За один вызов выполнить не более '''3''' действий над вспомогательными стеками (см. рис. 5). Четыре фазы:&lt;br /&gt;
&lt;br /&gt;
# '''&amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt;''' — пока &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; не пуст и &amp;lt;math&amp;gt;\mathtt{copied} = \text{false}&amp;lt;/math&amp;gt;, перекладывать &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; (временный переворот выхода).&lt;br /&gt;
# '''&amp;lt;math&amp;gt;L \to R, R_c'&amp;lt;/math&amp;gt;''' — как только начали, ставим &amp;lt;math&amp;gt;\mathtt{copied} \leftarrow \text{true}&amp;lt;/math&amp;gt;; каждый элемент &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; идёт и в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, и в &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;.&lt;br /&gt;
# '''&amp;lt;math&amp;gt;S \to R, R_c'&amp;lt;/math&amp;gt;''' — первые &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; элементов &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; возвращаются в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; (двойной переворот восстанавливает порядок &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и кладёт его '''над''' элементами &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;); остальные (уже логически извлечённые) отбрасываются.&lt;br /&gt;
# '''Смена ролей''' — если &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; пуст: &amp;lt;math&amp;gt;\mathtt{swap}(L, L')&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\mathtt{swap}(R_c, R_c')&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Операция Additional-Operations(Q)&lt;br /&gt;
Вход:  очередь Q в режиме перекопирования&lt;br /&gt;
Выход: сделано не более 3 переносов&lt;br /&gt;
Инвариант: фазы 1–4 выполняются строго по порядку&lt;br /&gt;
Сложность: O(1) (счётчик toDo = 3)&lt;br /&gt;
&lt;br /&gt;
1  toDo ← 3&lt;br /&gt;
2  while (not Q.copied) and toDo &amp;gt; 0 and |R| &amp;gt; 0&lt;br /&gt;
3      push(S, pop(R))                   # фаза 1: R → S&lt;br /&gt;
4      toDo ← toDo − 1&lt;br /&gt;
5  while toDo &amp;gt; 0 and |L| &amp;gt; 0&lt;br /&gt;
6      Q.copied ← true&lt;br /&gt;
7      x ← pop(L)&lt;br /&gt;
8      push(R, x)                        # фаза 2: L → R&lt;br /&gt;
9      push(Rc', x)                      #         и L → Rc'&lt;br /&gt;
10     toDo ← toDo − 1&lt;br /&gt;
11 while toDo &amp;gt; 0 and |S| &amp;gt; 0&lt;br /&gt;
12     x ← pop(S)                        # фаза 3: S → R, Rc'&lt;br /&gt;
13     if Q.toCopy &amp;gt; 0&lt;br /&gt;
14         push(R, x)&lt;br /&gt;
15         push(Rc', x)&lt;br /&gt;
16         Q.toCopy ← Q.toCopy − 1&lt;br /&gt;
17     toDo ← toDo − 1&lt;br /&gt;
18 if |S| = 0&lt;br /&gt;
19     swap(L, L')                       # фаза 4: смена ролей&lt;br /&gt;
20     swap(Rc, Rc')&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Разбор.''' Точные состояния стеков на примере &amp;lt;code&amp;gt;push(1..7)&amp;lt;/code&amp;gt; и последующих &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; приведены в разделе «Трассировка примера». Ключевой момент фазы 3 — двойной переворот &amp;lt;math&amp;gt;R \to S \to R&amp;lt;/math&amp;gt;: он возвращает относительный порядок старого выхода неизменным, но ставит его поверх элементов &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;; поскольку старые элементы и есть «перед» очереди, &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из нового &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; выдаёт FIFO.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Если &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; уже пуст, фаза 1 пропускается и работа сразу начинается с фазы 2. Если &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; пуст, фаза 2 пропускается. Если &amp;lt;math&amp;gt;\mathtt{toCopy} = 0&amp;lt;/math&amp;gt;, элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; просто извлекаются и выбрасываются — они уже были отданы через &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-05-recopy-steps.svg|frame|none|700px|Рис. 5. За вызов &amp;lt;code&amp;gt;additionalOperations&amp;lt;/code&amp;gt; — не более 3 переносов; фазы идут строго по порядку.]]&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Худший случай !! Амортизированный !! Переносов за вызов !! Память&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || 0 || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Push&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\le 3&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Вся очередь || — || — || — || &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; под элементы&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Здесь '''худший и амортизированный случаи совпадают''': амортизация не нужна, потому что никакая операция не выполняет больше трёх переносов. Для сравнения, очередь на двух стеках имеет амортизированный &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но худший &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;. В режиме перекопирования каждая «обычная» операция дополнительно тратит до 3 переносов — это уже учтено в оценке &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Проследим &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt; … &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt;, затем семь &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;. Стеки записаны в виде «дно … верх»; в столбце «режим»: &amp;lt;code&amp;gt;обыч.&amp;lt;/code&amp;gt; — обычный, &amp;lt;code&amp;gt;перекоп.&amp;lt;/code&amp;gt; — перекопирование. Значения получены прогоном реализации из раздела «Служебное».&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция !! Режим !! &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;L'&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; !! &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; !! &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(1)&amp;lt;/code&amp;gt; || обыч.→перекоп.→обыч. || — || — || 1 || 1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(2)&amp;lt;/code&amp;gt; || обыч. || 2 || — || 1 || 1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt; || обыч.→перекоп. || — || — || 3,2 || 1 || 3,2 || 1 || 1&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(4)&amp;lt;/code&amp;gt; || перекоп.→обыч. || 4 || — || 3,2,1 || 3,2,1 || 1 || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(5)&amp;lt;/code&amp;gt; || обыч. || 4,5 || — || 3,2,1 || 3,2,1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(6)&amp;lt;/code&amp;gt; || обыч. || 4,5,6 || — || 3,2,1 || 3,2,1 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt; || обыч.→перекоп. || 4,5,6,7 || — || — || 3,2,1 || — || 1,2,3 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 1 || перекоп. || 4 || — || 7,6,5 || 3,2 || 7,6,5 || 1,2,3 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 2 || перекоп. || — || — || 7,6,5,4,3 || 3 || 7,6,5,4,3 || 1 || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 3 || перекоп.→обыч. || — || — || 7,6,5,4 || 7,6,5,4 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 4 || обыч. || — || — || 7,6,5 || 7,6,5 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 5 || обыч. || — || — || 7,6 || 7,6 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 6 || обыч. || — || — || 7 || 7 || — || — || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pop()&amp;lt;/code&amp;gt; → 7 || обыч. || — || — || — || — || — || — || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Разберём два важных момента. Во-первых, &amp;lt;code&amp;gt;push(3)&amp;lt;/code&amp;gt; запускает перекопирование сразу, но успевает сделать только 3 переноса (старый &amp;lt;math&amp;gt;R = \{1\}&amp;lt;/math&amp;gt; уходит в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, два элемента &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; — в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;); завершается перенос на &amp;lt;code&amp;gt;push(4)&amp;lt;/code&amp;gt;, после чего пары &amp;lt;math&amp;gt;L \leftrightarrow L'&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c \leftrightarrow R_c'&amp;lt;/math&amp;gt; меняются ролями. Во-вторых, при &amp;lt;code&amp;gt;push(7)&amp;lt;/code&amp;gt; вход (&amp;lt;math&amp;gt;|L| = 4&amp;lt;/math&amp;gt;) перерос выход (&amp;lt;math&amp;gt;|R| = 3&amp;lt;/math&amp;gt;), начался новый перенос: &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; целиком ушёл в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, &amp;lt;code&amp;gt;toCopy = 3&amp;lt;/code&amp;gt;. Следующие &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; берут элементы из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; (копия старого &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;), пока в фоне &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; переливается в новый &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; когда &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt; доходит до нуля, корректная часть &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; переносится в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а остальное отбрасывается, и перекопирование заканчивается. Порядок выдачи — &amp;lt;math&amp;gt;1,2,\dots,7&amp;lt;/math&amp;gt; — ровно FIFO, и ни один &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; не сделал больше трёх внутренних переносов.&lt;br /&gt;
&lt;br /&gt;
== Корректность и оценка сложности ==&lt;br /&gt;
&lt;br /&gt;
=== Инвариант и FIFO ===&lt;br /&gt;
&lt;br /&gt;
Будем считать, что в обычном режиме элементы очереди равны &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; (сверху вниз) &amp;lt;math&amp;gt;\mathbin{+\!\!+}&amp;lt;/math&amp;gt; &amp;lt;math&amp;gt;\mathrm{reverse}(L)&amp;lt;/math&amp;gt;. Тогда верх &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; — самый старый элемент, и &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; возвращает его.&lt;br /&gt;
&lt;br /&gt;
'''Лемма (сохранение порядка при перекопировании).''' После завершения перекопирования стек &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; содержит все элементы очереди в правильном порядке для извлечения.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' Пусть до перекопирования очередь равна &amp;lt;math&amp;gt;R_0 \mathbin{+\!\!+} L_0&amp;lt;/math&amp;gt; (в указанном смысле). Фаза 1 переносит &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, разворачивая: &amp;lt;math&amp;gt;S = \mathrm{reverse}(R_0)&amp;lt;/math&amp;gt;. Фаза 2 переносит &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, разворачивая: &amp;lt;math&amp;gt;R = \mathrm{reverse}(L_0)&amp;lt;/math&amp;gt;. Фаза 3 переносит &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; (в порядке &amp;lt;math&amp;gt;\mathrm{reverse}(R_0)&amp;lt;/math&amp;gt;) обратно в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, что даёт &amp;lt;math&amp;gt;R = \mathrm{reverse}(L_0) \mathbin{+\!\!+} R_0&amp;lt;/math&amp;gt; в смысле стека, то есть сверху оказывается &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; в исходном порядке, а под ним — &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt; в исходном порядке. Поскольку элементы &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; старее элементов &amp;lt;math&amp;gt;L_0&amp;lt;/math&amp;gt;, это и есть FIFO-порядок. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Лемма (обслуживание во время перекопирования).''' Пока перекопирование не завершено, &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; возвращает корректный самый старый элемент, не дожидаясь конца переноса.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' В режиме перекопирования &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; читает &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — копию старого &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt;; в варианте на шести стеках она синхронно уменьшается вместе с &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, а в персистентном заменена неизменяемой версией &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;. Элементы &amp;lt;math&amp;gt;R_0&amp;lt;/math&amp;gt; старее всех элементов &amp;lt;math&amp;gt;L_0 \cup L'&amp;lt;/math&amp;gt;, поэтому выдача из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; сохраняет FIFO. Счётчик &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; уменьшается и при извлечении из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, и при переносе корректного элемента из &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;; поэтому &amp;lt;math&amp;gt;|R_c| \ge \mathtt{toCopy}&amp;lt;/math&amp;gt;. Пока &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt;, верх &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; — ещё не перенесённый корректный элемент; когда счётчик доходит до нуля, старый правый кусок уже распределён между &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, и &amp;lt;code&amp;gt;Pop&amp;lt;/code&amp;gt; синхронно снимает элемент из &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;. Инвариант &amp;lt;math&amp;gt;\mathtt{toCopy}&amp;lt;/math&amp;gt; не даёт прочитать «мусорные» элементы в дне &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Лемма (корректность &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt;).''' В обычном режиме &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;; в режиме перекопирования очередь непуста.&lt;br /&gt;
&lt;br /&gt;
''Доказательство.'' В обычном режиме инвариант &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt; и представление очереди как &amp;lt;math&amp;gt;R \mathbin{+\!\!+} \mathrm{reverse}(L)&amp;lt;/math&amp;gt; дают эквивалентность. В режиме перекопирования в момент старта (после запускающей операции) в очереди &amp;lt;math&amp;gt;n + (n + 1) = 2n + 1&amp;lt;/math&amp;gt; элементов. Перенос занимает &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операций, включая запускающую; после неё остаётся не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций, поэтому извлечь можно не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; элементов. Значит, хотя бы один элемент остаётся: при &amp;lt;math&amp;gt;\mathtt{toCopy} &amp;gt; 0&amp;lt;/math&amp;gt; он лежит в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt;, а при &amp;lt;math&amp;gt;\mathtt{toCopy} = 0&amp;lt;/math&amp;gt; весь правый кусок уже находится в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;. Поэтому &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; возвращает &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; корректно. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Оценка &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае ===&lt;br /&gt;
&lt;br /&gt;
Все переносы выполняются только в &amp;lt;code&amp;gt;Additional-Operations&amp;lt;/code&amp;gt;, где счётчик &amp;lt;code&amp;gt;toDo&amp;lt;/code&amp;gt; ограничен тройкой, и в фазах 1–3 каждый шаг — один перенос (pop и один-два push). Значит, одна операция очереди делает не более 3 переносов, то есть &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае (см. рис. 6).&lt;br /&gt;
&lt;br /&gt;
Покажем, что перекопирование '''успевает''' завершиться. В момент старта &amp;lt;math&amp;gt;|R| = n&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|L| = n + 1&amp;lt;/math&amp;gt;. Требуется: &amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; действий; &amp;lt;math&amp;gt;L \to R, R_c'&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt;; &amp;lt;math&amp;gt;S \to R, R_c'&amp;lt;/math&amp;gt; — &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;; две смены ролей — 2. Итого &amp;lt;math&amp;gt;3n + 3&amp;lt;/math&amp;gt; действий. В распоряжении — &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операций (сама запускающая перекопирование плюс следующие за ней до завершения переноса), по 3 действия каждая, то есть ровно &amp;lt;math&amp;gt;3(n + 1) = 3n + 3&amp;lt;/math&amp;gt;. Расчёт сходится: перенос гарантированно завершается.&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-06-timeline.svg|thumb|700px|Рис. 6. &amp;lt;math&amp;gt;3(n+1)&amp;lt;/math&amp;gt; действий на &amp;lt;math&amp;gt;n+1&amp;lt;/math&amp;gt; операций — ровно 3 действия на операцию, без запаздывания.]]&lt;br /&gt;
&lt;br /&gt;
'''После перекопирования.''' Пусть за &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; операций после старта произошло &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; извлечений и &amp;lt;math&amp;gt;n - x&amp;lt;/math&amp;gt; добавлений. Тогда в новых стеках останется &amp;lt;math&amp;gt;n - x&amp;lt;/math&amp;gt; элементов в &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;2n + 1 - x = (n - x) + (n + 1)&amp;lt;/math&amp;gt; в &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;, значит, до следующего перекопирования не менее &amp;lt;math&amp;gt;n + 2&amp;lt;/math&amp;gt; операций. За это время остаток &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt; (не более &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; элементов) успевает рассосаться: он уменьшается на 1 при каждой операции в обычном режиме. Это и есть содержание инварианта &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Персистентная очередь на пяти стеках ===&lt;br /&gt;
&lt;br /&gt;
Сделаем все стеки '''персистентными''' (неизменяемыми): операция возвращает новую версию, старые остаются доступны. Тогда шесть стеков превращаются в пять: пара &amp;lt;math&amp;gt;(R_c, R_c')&amp;lt;/math&amp;gt; избыточна — достаточно одной версии &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;, записанной в момент старта перекопирования; &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; обращается к ней, и никакой синхронизации не нужно. Освобождать &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt; тоже не требуется: ячейки используются другими версиями очереди. Версия очереди — запись &amp;lt;math&amp;gt;Q = \langle L, L', R, R', S, \mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}\rangle&amp;lt;/math&amp;gt;; &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; возвращают новую версию вместе с результатом. Переносы по-прежнему ограничены тремя на операцию, поэтому каждая версия занимает &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек и время &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае. Псевдокод (упрощённо, по источнику):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Версия Push(Q, x)&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      Ln ← push(Q.L, x)&lt;br /&gt;
3      return Check-Recopy(⟨Ln, Q.L', Q.R, Q.R', Q.S, Q.recopy, Q.toCopy, Q.copied⟩)&lt;br /&gt;
4  else&lt;br /&gt;
5      Ln' ← push(Q.L', x)&lt;br /&gt;
6      return Check-Normal(⟨Q.L, Ln', Q.R, Q.R', Q.S, Q.recopy, Q.toCopy, Q.copied⟩)&lt;br /&gt;
&lt;br /&gt;
Версия Pop(Q)&lt;br /&gt;
1  if not Q.recopy&lt;br /&gt;
2      ⟨Rn, x⟩ ← pop(Q.R)&lt;br /&gt;
3      return ⟨Check-Recopy(⟨Q.L, Q.L', Rn, Q.R', Q.S, …⟩), x⟩&lt;br /&gt;
4  else&lt;br /&gt;
5      ⟨Rn', x⟩ ← pop(Q.R')&lt;br /&gt;
6      if Q.toCopy &amp;gt; 0&lt;br /&gt;
7          cur ← Q.toCopy − 1&lt;br /&gt;
8          return ⟨Check-Normal(⟨Q.L, Q.L', Q.R, Rn', Q.S, …, cur, Q.copied⟩), x⟩&lt;br /&gt;
9      else&lt;br /&gt;
10         ⟨Rn, x⟩ ← pop(Q.R)&lt;br /&gt;
11         return ⟨Check-Normal(⟨Q.L, Q.L', Rn, Rn', Q.S, …, 0, Q.copied⟩), x⟩&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Функции &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Additional-Operations&amp;lt;/code&amp;gt; повторяют версию на шести стеках, но вместо мутации создают новые версии стеков. Важное отличие: в пятистековом варианте &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; при входе в перекопирование фиксирует &amp;lt;math&amp;gt;R' \leftarrow R&amp;lt;/math&amp;gt; — текущую версию выхода; без этого &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; в режиме перекопирования читал бы устаревшую версию. См. рис. 7.&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Реализация !! Худший !! Амортизированный !! Память на версию !! Персистентность&lt;br /&gt;
|-&lt;br /&gt;
| Два стека || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || неприменима (&amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;) || нет&lt;br /&gt;
|-&lt;br /&gt;
| Шесть стеков (реальное время) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || — (не персистентна) || нет&lt;br /&gt;
|-&lt;br /&gt;
| Пять стеков (персистентная) || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек || да (функциональная)&lt;br /&gt;
|-&lt;br /&gt;
| Кольцевой буфер || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на копию || нет&lt;br /&gt;
|-&lt;br /&gt;
| Связный список || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;\Theta(1)&amp;lt;/math&amp;gt;, но с риском разделения || частично (copy-on-write)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Файл:q6-07-comparison.svg|frame|none|700px|Рис. 7. Три уровня компромисса: амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; → худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; → худший &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; с персистентностью.]]&lt;br /&gt;
&lt;br /&gt;
'''Когда что выбирать.''' Нужна простая очередь общего назначения — кольцевой буфер или двусвязный список из [[Очередь]] (план). Нужна гарантия времени отклика — шесть стеков. Нужны прошлые версии (undo, функциональный язык) — персистентная очередь на пяти стеках. Если допустимы амортизированные оценки и не важна задержка, двух стеков достаточно.&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки и библиотеки.''' В Haskell, OCaml, Standard ML очереди и деки — персистентные структуры; техника раскрытия «ленивого» переноса восходит к реальному времени и используется в &amp;lt;code&amp;gt;Data.Sequence&amp;lt;/code&amp;gt;, очередях Okasaki и структурах с неизменяемыми версиями. Почему: в чисто функциональном коде структура не мутируется, а старые версии должны сохраняться [4][5].&lt;br /&gt;
&lt;br /&gt;
# '''Системы реального времени.''' Планировщики, обработчики прерываний, буферы событий требуют ограниченного сверху времени отклика. Очередь с &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае исключает редкие «долгие» вызовы, которые опасны в контурах управления и телеком-оборудовании [1][3].&lt;br /&gt;
&lt;br /&gt;
# '''Персистентность и версионирование.''' Undo/redo в редакторах, транзакционные снимки, инкрементальные вычисления и системы контроля версий выигрывают от структур, где каждая версия стоит &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек. Персистентная очередь — базовый кирпич таких систем [4].&lt;br /&gt;
&lt;br /&gt;
# '''Логи событий и распределённые потоки.''' Очередь сообщений, к которой нужен доступ к прошлым состояниям (replay, аудит), строится на персистентных структурах; неизменяемость упрощает репликацию и откат [4].&lt;br /&gt;
&lt;br /&gt;
# '''Обучение алгоритмам.''' Конструкция «очередь на шести стеках» — классическая задача курса дискретной математики и алгоритмов (Викиконспекты ИТМО): она тренирует инварианты, амортизационный анализ и персистентность [1][2].&lt;br /&gt;
&lt;br /&gt;
# '''Олимпиадное программирование.''' Персистентная очередь и её «друзья» (персистентный стек, дек, приоритетная очередь) — инструменты задач на версии и откаты; разборы публикуются в блогах сообщества [2].&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Операции над очередью в реальном времени первыми описали '''Роберт Худ и Роберт Мелвилл''' (R. Hood, R. Melville) в работе ''Real Time Queue Operations in Pure LISP'' (Cornell University, 1980; отчёт доступен через библиотечный депозитарий Корнеллского университета) [3]. Они показали, как реализовать очередь без долгих операций в чистом LISP, где данные неизменяемы, — именно персистентность и подталкивала к распределению переноса по операциям.&lt;br /&gt;
&lt;br /&gt;
Идею подхватил '''Крис Окасаки''' (Chris Okasaki): в статье ''Simple and Efficient Purely Functional Queues and Deques'' (Journal of Functional Programming, 1995) и книге ''Purely Functional Data Structures'' (Cambridge University Press, 1998) он систематизировал персистентные очереди — «банкирскую», «физическую» и очередь реального времени [4]. Формальный '''амортизированный анализ''' (и метод потенциала) восходит к '''Роберту Тарьяну''' (R. E. Tarjan, «Amortized Computational Complexity», 1985) [5] и служит языком описания оценок в этой области.&lt;br /&gt;
&lt;br /&gt;
Изложение конструкции «очередь на шести стеках» и её персистентного варианта на пяти стеках — учебная традиция кафедры компьютерных технологий ИТМО; конспект доступен на «Викиконспектах» (neerc.ifmo.ru) [1]. Популярные разборы — в блоге Codeforces [2] и на Хабрахабре [7]. Отметим, что конкретное число стеков (шесть против пяти) зависит от того, требуется ли явная копия выхода: persistence позволяет обойтись меньшим числом стеков [1].&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Путать амортизированное с худшим.''' Очередь на двух стеках — амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;, но худший случай &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;; только конструкция на шести стеках даёт &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в каждом вызове.&lt;br /&gt;
* '''Забыть подчищать &amp;lt;math&amp;gt;R_c'&amp;lt;/math&amp;gt;.''' В обычном режиме остаток копии нужно уменьшать на 1 за операцию (инвариант &amp;lt;math&amp;gt;|R_c'| \le |R| - |L|&amp;lt;/math&amp;gt;), иначе к следующему перекопированию не найдётся свободного стека.&lt;br /&gt;
* '''Неверный момент запуска.''' Перекопирование нужно начинать ровно при &amp;lt;math&amp;gt;|L| = |R| + 1&amp;lt;/math&amp;gt;; позже — не хватит операций на перенос, раньше — нарушится инвариант и лишняя работа.&lt;br /&gt;
* '''Ошибка на единицу в &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.''' Это число ''корректных'' элементов в &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;; при &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; во время перекопирования его нужно уменьшать, иначе можно прочитать уже извлечённый элемент.&lt;br /&gt;
* '''Забыть фазу 1 (&amp;lt;math&amp;gt;R \to S&amp;lt;/math&amp;gt;).''' Без буфера &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; старый выход не опустеет, если операций &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; больше, чем &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt;, и не хватит стека для нового &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt;; двойной переворот через &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; обязателен.&lt;br /&gt;
* '''Не сделать смену ролей.''' Без &amp;lt;math&amp;gt;\mathtt{swap}(L, L')&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;\mathtt{swap}(R_c, R_c')&amp;lt;/math&amp;gt; новые результаты останутся в служебных стеках, а очередь будет читать старое.&lt;br /&gt;
* '''Попытка сделать двухстековую очередь персистентной «в лоб».''' Дорогая операция переноса порождает &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; новых ячеек на версию — нужна именно конструкция с дроблением.&lt;br /&gt;
* '''Мутируемые стеки в персистентном варианте.''' Персистентность требует неизменяемости; изменение «на месте» разрушит прошлые версии.&lt;br /&gt;
* '''Закольцованная рекурсия проверок.''' &amp;lt;code&amp;gt;Check-Recopy&amp;lt;/code&amp;gt; вызывает &amp;lt;code&amp;gt;Check-Normal&amp;lt;/code&amp;gt;, который может выставить &amp;lt;code&amp;gt;recopy&amp;lt;/code&amp;gt; снова; рекурсии здесь нет — это разные шаги одной операции, но при переносе в код важно не зациклиться.&lt;br /&gt;
* '''Предположение, что &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; ложен всегда в перекопировании.''' Так и есть (в &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;R&amp;lt;/math&amp;gt; остаются элементы), но если реализация не поддерживает инвариант &amp;lt;math&amp;gt;|L| = |R| + 1&amp;lt;/math&amp;gt; в момент старта, &amp;lt;code&amp;gt;Empty&amp;lt;/code&amp;gt; может соврать.&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Два стека.''' Реализуйте очередь на двух стеках и определите худший случай. Ограничение: не более &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; памяти на элемент. ''Подсказка:'' перенос происходит при опустошении выхода; худший вызов — &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Инвариант.''' Докажите, что в обычном режиме &amp;lt;math&amp;gt;R = \varnothing \iff Q = \varnothing&amp;lt;/math&amp;gt;. ''Подсказка:'' используйте &amp;lt;math&amp;gt;|L| \le |R|&amp;lt;/math&amp;gt; и представление &amp;lt;math&amp;gt;R \mathbin{+\!\!+} \mathrm{reverse}(L)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Счёт действий.''' Покажите, что при старте перекопирования (&amp;lt;math&amp;gt;|L| = |R| + 1 = n + 1&amp;lt;/math&amp;gt;) требуется ровно &amp;lt;math&amp;gt;3n + 3&amp;lt;/math&amp;gt; действий и что их укладывает ровно &amp;lt;math&amp;gt;n + 1&amp;lt;/math&amp;gt; операция по 3. ''Подсказка:'' сложите вклады фаз 1–4.&lt;br /&gt;
&lt;br /&gt;
# '''&amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.''' Объясните, почему некорректные элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; скапливаются у дна, и почему условие &amp;lt;code&amp;gt;toCopy &amp;amp;gt; 0&amp;lt;/code&amp;gt; защищает от чтения мусора. ''Подсказка:'' элементы &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; переносятся по одному, а уменьшение &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt; соответствует уже отданным элементам.&lt;br /&gt;
&lt;br /&gt;
# '''Трассировка.''' Вручную повторите семь &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; и семь &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; из раздела «Трассировка примера» и сверьте состояния стеков. ''Подсказка:'' следите за флагами &amp;lt;code&amp;gt;recopy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;copied&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;toCopy&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Реальное время vs персистентность.''' Объясните, почему шестистековый вариант годится для реального времени, а для персистентности достаточно пяти стеков. ''Подсказка:'' роль копии &amp;lt;math&amp;gt;R_c&amp;lt;/math&amp;gt; берёт на себя версия &amp;lt;math&amp;gt;R'&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# '''Пятистековая очередь.''' Реализуйте персистентную очередь на пяти неизменяемых стеках так, чтобы каждая версия занимала &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; новых ячеек. ''Ограничение:'' стеки неизменяемы.&lt;br /&gt;
&lt;br /&gt;
# '''Расширение до дека.''' Попробуйте добавить &amp;lt;code&amp;gt;pop_back&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;push_front&amp;lt;/code&amp;gt; и обсудите, почему дек на стеках сложнее очереди. ''Подсказка:'' симметрия «лево/право» требует mirrored-структуры.&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Очередь]] (план) — базовая FIFO, кольцевой буфер, очередь на двух стеках.&lt;br /&gt;
* [[Стек]] (план) — строительный блок; стек как персистентная структура.&lt;br /&gt;
* [[Персистентный стек]] (план) — неизменяемый стек; основа персистентной очереди.&lt;br /&gt;
* [[Персистентная очередь]] (план) — персистентность очереди, ленивые очереди Окасаки.&lt;br /&gt;
* [[Асимптотическая сложность]] — амортизация, худший случай, худший и амортизированный.&lt;br /&gt;
* [[Персистентные структуры данных]] — неизменяемость и версии.&lt;br /&gt;
* [[Декартово дерево]] (план) — персистентный treap как ещё один пример персистентности.&lt;br /&gt;
* [[Очередь с приоритетом]] (план) — родственная структура; персистентный вариант.&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Викиконспекты ИТМО: Персистентная очередь (разделы «Реализация очереди на шести стеках», «Персистентная очередь на пяти стеках»). URL: https://neerc.ifmo.ru/wiki/index.php?title=Персистентная_очередь.&lt;br /&gt;
# Codeforces. Персистентная очередь и её друзья (блог). URL: https://codeforces.com/blog/entry/15685.&lt;br /&gt;
# Hood R., Melville R. Real Time Queue Operations in Pure LISP. — Cornell University, 1980. URL: http://hdl.handle.net/1813/6273.&lt;br /&gt;
# Okasaki C. Simple and Efficient Purely Functional Queues and Deques // Journal of Functional Programming. — 1995. — Vol. 5, № 4; и Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998.&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Cormen T. H., Leiserson C. E., Rivest R. L., Stein C. ''Introduction to Algorithms'' (CLRS). 3rd ed. — MIT Press, 2009. — Гл. 10 (элементарные структуры: стек, очередь).&lt;br /&gt;
# Хабрахабр. Персистентная очередь (пост 241231). URL: https://habr.com/ru/post/241231/ ''(проверено 2026-11-03: статья о персистентной очереди, другой алгоритм — на деревьях)''.&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-07-comparison.svg&amp;diff=931</id>
		<title>Файл:Q6-07-comparison.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-07-comparison.svg&amp;diff=931"/>
		<updated>2026-11-04T10:12:35Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-06-timeline.svg&amp;diff=930</id>
		<title>Файл:Q6-06-timeline.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-06-timeline.svg&amp;diff=930"/>
		<updated>2026-11-04T10:12:25Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-05-recopy-steps.svg&amp;diff=929</id>
		<title>Файл:Q6-05-recopy-steps.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-05-recopy-steps.svg&amp;diff=929"/>
		<updated>2026-11-04T10:12:15Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-04-invariants.svg&amp;diff=928</id>
		<title>Файл:Q6-04-invariants.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-04-invariants.svg&amp;diff=928"/>
		<updated>2026-11-04T10:12:07Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-03-modes.svg&amp;diff=927</id>
		<title>Файл:Q6-03-modes.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-03-modes.svg&amp;diff=927"/>
		<updated>2026-11-04T10:11:58Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-02-roles.svg&amp;diff=926</id>
		<title>Файл:Q6-02-roles.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-02-roles.svg&amp;diff=926"/>
		<updated>2026-11-04T10:11:48Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-01-two-stacks.svg&amp;diff=925</id>
		<title>Файл:Q6-01-two-stacks.svg</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Q6-01-two-stacks.svg&amp;diff=925"/>
		<updated>2026-11-04T10:11:28Z</updated>

		<summary type="html">&lt;p&gt;СВ: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
	<entry>
		<id>https://apmath.info/w/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=924</id>
		<title>Персистентные структуры данных</title>
		<link rel="alternate" type="text/html" href="https://apmath.info/w/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=924"/>
		<updated>2026-11-04T10:09:59Z</updated>

		<summary type="html">&lt;p&gt;СВ: /* TL;DR */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных''' (persistent data structure) сохраняет доступ ко '''всем своим предыдущим версиям''': каждая операция создаёт новую версию, не разрушая старые. Наивный способ — копировать структуру целиком — стоит &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на операцию и &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; на историю. Поэтому применяют '''разделение данных (sharing)''': три классических метода — '''path copying''' (копируем только путь до изменённого места, остальные поддеревья общие), '''fat nodes''' (каждое поле хранит историю значений по версиям) и '''node copying''' (узел копируется при заполнении, с ограничением входящей степени). Различают уровни персистентности: '''частичная''', '''полная''', '''функциональная''' и '''слитная''' (confluent, с объединением версий). Персистентность — основа функциональных языков, систем контроля версий, многоверсионных СУБД (MVCC), undo/redo и безопасного разделения данных между потоками. Конкретные реализации разобраны в [[Персистентный стек|персистентный стек]], [[Персистентная очередь|персистентная очередь]] и [[Очередь на шести стеках|очередь на шести стеках]].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&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;
|-&lt;br /&gt;
| Методы&lt;br /&gt;
| path copying, fat nodes, node copying, функциональная пересборка&lt;br /&gt;
|-&lt;br /&gt;
| Цена обновления&lt;br /&gt;
| от &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; (копия) до &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt; (path copying); fat nodes — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на обновление; node copying — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт. при ограниченной входящей степени&lt;br /&gt;
|-&lt;br /&gt;
| Цена доступа к версии&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по ссылке; материализация — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Память&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;–&amp;lt;math&amp;gt;O(n\log n)&amp;lt;/math&amp;gt; при разделении против &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; при копировании&lt;br /&gt;
|-&lt;br /&gt;
| Ключевое требование&lt;br /&gt;
| неизменяемость (или учёт версий) разделяемых узлов&lt;br /&gt;
|-&lt;br /&gt;
| Родственные&lt;br /&gt;
| ретроактивные и кинетические структуры, MVCC&lt;br /&gt;
|-&lt;br /&gt;
| Где встречается&lt;br /&gt;
| функциональные языки, Git, СУБД, геометрия, undo, параллельность&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Определения ==&lt;br /&gt;
&lt;br /&gt;
'''Персистентная структура данных''' — структура, поддерживающая доступ ко всем версиям, порождённым последовательностью операций. Противоположность — '''эфемерная''' (ephemeral) структура, хранящая лишь текущее состояние.&lt;br /&gt;
&lt;br /&gt;
'''Версия (version)''' — состояние структуры после некоторого набора операций. Доступ к версии обычно осуществляется по ссылке на её «корень» (или по индексу) за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Уровни персистентности''' (см. рис. 1):&lt;br /&gt;
&lt;br /&gt;
* '''частичная (partial)''' — изменять можно только самую новую версию, читать — любую;&lt;br /&gt;
* '''полная (full)''' — изменять и читать можно любую версию (версии образуют дерево);&lt;br /&gt;
* '''функциональная (functional)''' — каждая ячейка инициализируется один раз и не меняется; новая версия строится только из старых;&lt;br /&gt;
* '''слитная (confluent)''' — любую версию можно изменять и, кроме того, '''объединять''' две версии (merge); версии образуют ациклический граф (DAG).&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-01-levels.svg|thumb|700px|Рис. 1. Уровни персистентности различаются тем, что разрешено менять и читать; функциональный — сильнейший по неизменяемости, слитный — по операциям.]]&lt;br /&gt;
&lt;br /&gt;
'''Path copying (копирование пути).''' Обновление копирует только узлы вдоль пути от корня до изменяемого места; поддеревья, не лежащие на пути, '''разделяются''' между версиями. Применяется к деревьям.&lt;br /&gt;
&lt;br /&gt;
'''Fat node (толстый узел).''' Каждое поле узла хранит не одно значение, а '''список пар (версия, значение)''', накапливаемый по мере изменения. Чтение поля на версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; — поиск последнего присваивания с номером &amp;lt;math&amp;gt;\le v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Node copying (копирование узла).''' Узел копируется, когда его «слоты» для новых указателей заняты; копия и оригинал хранят списки версий указателей. Требует '''ограниченной входящей степени''' (bounded in-degree): у каждого узла не более &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; входящих ссылок.&lt;br /&gt;
&lt;br /&gt;
'''Функциональная пересборка.''' Новая версия строится целиком из старых данных (например, &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt;-дерево пересобирается снизу вверх); проста, но стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Обозначения.''' &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — размер структуры; &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; — число модификаций поля; &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; — число версий; &amp;lt;math&amp;gt;\log&amp;lt;/math&amp;gt; — двоичный. «Ссылка на версию» — указатель на корень (или индекс).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Мотивация и интуиция ==&lt;br /&gt;
&lt;br /&gt;
Обычные структуры '''эфемерны''': массив, список или дерево хранят одно состояние, а операции его перезаписывают. Если нужна история, приходится либо заранее копировать структуру, либо реконструировать прошлое по журналу. Копирование даёт &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на операцию и, при &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; версиях, &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; суммарной памяти (см. рис. 2). Для больших структур это неприемлемо.&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-02-naive.svg|thumb|700px|Рис. 2. Полная копия на каждое изменение: &amp;lt;math&amp;gt;1+2+\dots+n = \Theta(n^2)&amp;lt;/math&amp;gt;; разделение данных снижает память до &amp;lt;math&amp;gt;O(n\log n)&amp;lt;/math&amp;gt; для сбалансированного BST (для списка/стека — до &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;).]]&lt;br /&gt;
&lt;br /&gt;
Ключевая идея — '''не копировать то, что не изменилось'''. Меняя одно поле, мы затрагиваем лишь небольшую часть структуры; всё остальное можно '''переиспользовать''' — на неизменяемые части могут ссылаться и старая, и новая версии. Отсюда два больших подхода:&lt;br /&gt;
&lt;br /&gt;
* '''Функциональный''' (immutable): узлы не меняются вовсе; изменение = создание новых узлов со ссылками на старые части. Пример — path copying.&lt;br /&gt;
* '''In-place''': структура дополняется информацией о версиях, но сами «старые» данные остаются на месте. Примеры — fat nodes и node copying.&lt;br /&gt;
&lt;br /&gt;
Зачем это нужно: функциональные языки (нет мутации), версионирование и undo, многоверсионные СУБД (читатель видит снапшот, писатель ему не мешает), вычислительная геометрия (история изменений), безопасное разделение данных между потоками. Подробности по конкретным структурам — в [[Персистентный стек|персистентный стек]], [[Персистентная очередь|персистентная очередь]], [[Декартово дерево|декартово-дерево]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Устройство ==&lt;br /&gt;
&lt;br /&gt;
Персистентная структура — это набор '''неизменяемых узлов''' (или узлов с версионной историей), связанных ссылками, и '''набор версий''', каждая из которых задаётся ссылкой на свой корень. Обычно версия включает номер (или время), монотонно возрастающий.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Path copying ===&lt;br /&gt;
&lt;br /&gt;
При обновлении (например, &amp;lt;code&amp;gt;insert&amp;lt;/code&amp;gt; в дерево поиска) копируются узлы '''вдоль пути''' корень → лист, а не соседние поддеревья. Новая версия получает новый корень; старая продолжает ссылаться на нетронутые поддеревья. См. рис. 3: при вставке 55 копируются только узлы 50 и 70, а поддерево 30 остаётся общим.&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-03-pathcopying.svg|thumb|700px|Рис. 3. Копируется только путь вставки; поддеревья вне пути разделяются между версиями.]]&lt;br /&gt;
&lt;br /&gt;
Цена: для сбалансированного дерева путь имеет длину &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;, значит, &amp;lt;code&amp;gt;insert&amp;lt;/code&amp;gt; создаёт &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt; новых узлов и столько же времени. Память на одну операцию — &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;; суммарно за &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вставок — &amp;lt;math&amp;gt;O(n\log n)&amp;lt;/math&amp;gt;. Доступ к любой версии — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по её корню. Требование: узлы '''неизменяемы''' (иначе испортится старая версия).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Fat nodes и node copying ===&lt;br /&gt;
&lt;br /&gt;
Если структура не дерево (или хочется экономить на копировании пути), применяют in-place методы (см. рис. 4). В '''fat node''' каждое поле хранит список значений по версиям; изменение — это добавление пары в конец списка. В '''node copying''' узел имеет фиксированное число слотов; когда они заполнены, создаётся копия, а список версий указывает, какая копия «актуальна» для версии.&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-04-fatnodes.svg|thumb|700px|Рис. 4. Fat nodes хранят историю в полях; node copying копирует узел при заполнении слотов (нужна ограниченная входящая степень).]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Версии как дерево или граф ===&lt;br /&gt;
&lt;br /&gt;
Для полной и слитной персистентности версии удобно представлять '''деревом''' (или DAG), где ребро — операция над версией (см. рис. 5). Это делает явной структуру истории и объясняет, почему амортизированные оценки «не переносятся»: одну и ту же дорогую операцию можно выполнить из разных версий.&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-05-versiontree.svg|thumb|700px|Рис. 5. Частичная персистентность — линия, полная — дерево, слитная — DAG с операциями merge.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Операции и алгоритмы ==&lt;br /&gt;
&lt;br /&gt;
Общих «операций» у класса нет — они определяются конкретной структурой. Но есть универсальные процедуры: обновление через path copying, чтение/запись fat-поля, доступ к версии. Все примеры ниже даны для '''персистентного бинарного дерева поиска (BST)''' как канонической структуры.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Операция: Insert через path copying ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Спускаясь к месту вставки, каждый узел пути заменяем его копией с обновлённой ссылкой на изменённого ребёнка; у листа создаём новый узел. Поддеревья вне пути переиспользуются.&lt;br /&gt;
&lt;br /&gt;
'''Пошаговое описание:'''&lt;br /&gt;
&lt;br /&gt;
# Если дерево пусто — вернуть версию из одного нового узла.&lt;br /&gt;
# Спуститься по ключу: влево при &amp;lt;math&amp;gt;k &amp;lt; \text{key}&amp;lt;/math&amp;gt;, вправо при &amp;lt;math&amp;gt;k &amp;gt; \text{key}&amp;lt;/math&amp;gt;.&lt;br /&gt;
# На обратном пути создать копию каждого узла пути, подставив обновлённого ребёнка.&lt;br /&gt;
# Если ключ уже есть — вернуть узел без копирования (структура не меняется).&lt;br /&gt;
&lt;br /&gt;
'''Инвариант.''' Каждый узел любой версии неизменяем; версия определяется ссылкой на корень; все поддеревья, не лежащие на пути, общие с предыдущей версией.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;Операция Insert(v, k)&lt;br /&gt;
Вход:  версия v (корень дерева); k — ключ&lt;br /&gt;
Выход: новая версия v' (корень), содержащая k&lt;br /&gt;
Инвариант: v и v' — корректные BST; изменены только узлы пути&lt;br /&gt;
Сложность: O(h) времени и новых узлов (h — высота; O(log n) для сбалансированного)&lt;br /&gt;
&lt;br /&gt;
1  if v = NIL&lt;br /&gt;
2      return новый узел ⟨k, NIL, NIL⟩&lt;br /&gt;
3  if k = v.key&lt;br /&gt;
4      return v                          # без изменений&lt;br /&gt;
5  if k &amp;lt; v.key&lt;br /&gt;
6      l' ← Insert(v.left, k)&lt;br /&gt;
7      if l' = v.left&lt;br /&gt;
8          return v                      # изменений не было&lt;br /&gt;
9      return новый узел ⟨v.key, l', v.right⟩&lt;br /&gt;
10 else&lt;br /&gt;
11     r' ← Insert(v.right, k)&lt;br /&gt;
12     if r' = v.right&lt;br /&gt;
13         return v&lt;br /&gt;
14     return новый узел ⟨v.key, v.left, r'⟩&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
'''Разбор.''' Дерево &amp;lt;code&amp;gt;{50, 30, 70}&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;Insert(55)&amp;lt;/code&amp;gt; копирует 50 и 70, создаёт узел 55; поддерево 30 остаётся общим (рис. 3). Другая ветвь от той же версии (&amp;lt;code&amp;gt;Insert(25)&amp;lt;/code&amp;gt;) не конфликтует: версии независимы.&lt;br /&gt;
&lt;br /&gt;
'''Сложность.''' &amp;lt;math&amp;gt;O(h)&amp;lt;/math&amp;gt;; для сбалансированного дерева &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Совпадающий ключ не создаёт копий; пустое дерево — один новый узел.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Операция: Fat-Node Set и Get ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Поле хранит список &amp;lt;code&amp;gt;(версия, значение)&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;Set&amp;lt;/code&amp;gt; добавляет пару, &amp;lt;code&amp;gt;Get&amp;lt;/code&amp;gt; ищет последнее присваивание с номером &amp;lt;math&amp;gt;\le&amp;lt;/math&amp;gt; запрошенного.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;Операция Fat-Set(node, field, version, value)&lt;br /&gt;
Вход:  узел; поле; номер версии; значение&lt;br /&gt;
Сложность: O(1)&lt;br /&gt;
&lt;br /&gt;
1  list[node][field].append((version, value))&lt;br /&gt;
&lt;br /&gt;
Операция Fat-Get(node, field, version)&lt;br /&gt;
Вход:  узел; поле; номер версии&lt;br /&gt;
Выход: значение поля в этой версии&lt;br /&gt;
Сложность: O(log m) — двоичный поиск по истории длины m&lt;br /&gt;
&lt;br /&gt;
1  h ← list[node][field]&lt;br /&gt;
2  return Value-At(h, version)          # последняя пара с version' ≤ version&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
'''Разбор.''' Поле &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt; менялось в версиях 1 и 4: &amp;lt;code&amp;gt;[(1,5), (4,8)]&amp;lt;/code&amp;gt;. &amp;lt;code&amp;gt;Fat-Get(·, 3)&amp;lt;/code&amp;gt; вернёт 5, &amp;lt;code&amp;gt;Fat-Get(·, 4)&amp;lt;/code&amp;gt; и позже — 8.&lt;br /&gt;
&lt;br /&gt;
'''Крайние случаи.''' Запрос версии раньше первого присваивания — ошибка (значения ещё не было).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Замечание: доступ к версии ===&lt;br /&gt;
&lt;br /&gt;
'''Идея.''' Версия — это ссылка на корень; доступ &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;. Материализация (выписывание всех элементов) — &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt; для версии из &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; элементов. Обновление любой версии даёт новую ветвь.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Сводная таблица сложности ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&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;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по индексу&lt;br /&gt;
| &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| Path copying&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| сбалансированное дерево, узлы неизменяемы&lt;br /&gt;
|-&lt;br /&gt;
| Fat nodes&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log m)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| история в полях&lt;br /&gt;
|-&lt;br /&gt;
| Node copying&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт.&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт.&lt;br /&gt;
| ограниченная входящая степень&lt;br /&gt;
|-&lt;br /&gt;
| Функциональная пересборка&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; по индексу&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| полная перестройка&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Оговорки: доступ к корню версии — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; для всех методов; &amp;lt;math&amp;gt;O(\log m)&amp;lt;/math&amp;gt; у fat nodes — это чтение поля, а не доступ к версии. Память указана на операцию; суммарно за &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; версий полная копия даёт &amp;lt;math&amp;gt;\Theta(Vn)&amp;lt;/math&amp;gt;, path copying — &amp;lt;math&amp;gt;O(V\log n)&amp;lt;/math&amp;gt; для сбалансированного дерева. Точные классы зависят от модели (pointer machine) и структуры; &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt; в path copying достигается за счёт балансировки. См. рис. 6.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Трассировка примера ==&lt;br /&gt;
&lt;br /&gt;
Проследим серию вставок в персистентное BST и число '''новых''' узлов на каждую операцию (прогон реализации из раздела «Служебное»).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Операция&lt;br /&gt;
! Версия&lt;br /&gt;
! Новых узлов&lt;br /&gt;
! Общие поддеревья&lt;br /&gt;
|-&lt;br /&gt;
| старт&lt;br /&gt;
| v0&lt;br /&gt;
| 0&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(50)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v1&lt;br /&gt;
| 1&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(30)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v2&lt;br /&gt;
| 2&lt;br /&gt;
| —&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(70)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v3&lt;br /&gt;
| 2&lt;br /&gt;
| 30&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(20)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v4&lt;br /&gt;
| 3&lt;br /&gt;
| 70&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(40)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v5&lt;br /&gt;
| 3&lt;br /&gt;
| 70, 20&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(60)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v6&lt;br /&gt;
| 3&lt;br /&gt;
| 30, 20, 40&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(80)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v7&lt;br /&gt;
| 3&lt;br /&gt;
| 30, 60&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;insert(55)&amp;lt;/code&amp;gt;&lt;br /&gt;
| v8&lt;br /&gt;
| 4&lt;br /&gt;
| 30, 20, 40, 80&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Вставка &amp;lt;code&amp;gt;55&amp;lt;/code&amp;gt; в дерево из семи узлов создаёт '''четыре''' новых узла: путь &amp;lt;code&amp;gt;50 → 70 → 60 → 55&amp;lt;/code&amp;gt; (копируются 50, 70, 60 и создаётся лист 55), тогда как полная копия дала бы семь. Все поддеревья вне пути (в частности, содержащие 20, 30, 40 и 80) разделяются между v7 и v8: это и есть sharing.&lt;br /&gt;
&lt;br /&gt;
Проверка fat-узла: поле менялось в версиях 1, 4 и 7. Тогда &amp;lt;code&amp;gt;Fat-Get(·, v)&amp;lt;/code&amp;gt; возвращает значение версии 1 при &amp;lt;math&amp;gt;v \in [1,3]&amp;lt;/math&amp;gt;, версии 4 при &amp;lt;math&amp;gt;v \in [4,6]&amp;lt;/math&amp;gt; и версии 7 при &amp;lt;math&amp;gt;v \ge 7&amp;lt;/math&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;math&amp;gt;v&amp;lt;/math&amp;gt; остаётся корректной навсегда: её узлы и все достижимые из них узлы не изменяются после создания.&lt;br /&gt;
&lt;br /&gt;
'''Для функциональных структур (path copying).''' Операция создаёт только новые узлы; поля старых узлов не присваиваются. Значит, множество узлов, достижимых из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, и их поля постоянны — инвариант выполняется по определению.&lt;br /&gt;
&lt;br /&gt;
'''Для fat nodes.''' Узлы «на месте» меняются, но каждое изменение фиксируется с номером версии; чтение на версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; выбирает последнее присваивание с номером &amp;lt;math&amp;gt;\le v&amp;lt;/math&amp;gt;. Если все запросы версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; используют один и тот же номер &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, старое «содержимое» восстанавливается однозначно — инвариант сохраняется на уровне запроса.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Корректность path copying ===&lt;br /&gt;
&lt;br /&gt;
'''Утверждение.''' &amp;lt;code&amp;gt;Insert(v, k)&amp;lt;/code&amp;gt; возвращает корректное BST, содержащее тот же набор ключей, что и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, плюс &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; (если его не было).&lt;br /&gt;
&lt;br /&gt;
''Доказательство (по индукции по высоте).'' База: пустое дерево → узел-лист с ключом &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;, корректно. Шаг: при &amp;lt;math&amp;gt;k &amp;lt; v.\text{key}&amp;lt;/math&amp;gt; рекурсивно вставляем в левое поддерево; по индукции получаем корректное BST &amp;lt;math&amp;gt;l'&amp;lt;/math&amp;gt; с прежними ключами плюс &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;. Новый узел &amp;lt;math&amp;gt;\langle v.\text{key}, l', v.\text{right}&lt;br /&gt;
\rangle&amp;lt;/math&amp;gt; сохраняет порядок (все ключи &amp;lt;math&amp;gt;l'&amp;lt;/math&amp;gt; меньше &amp;lt;math&amp;gt;v.\text{key}&amp;lt;/math&amp;gt;, все ключи &amp;lt;math&amp;gt;v.\text{right}&amp;lt;/math&amp;gt; больше). Аналогично для правого поддерева. Ключ &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; не создаёт копий, если уже есть. &amp;lt;math&amp;gt;\square&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Оценка сложности и памяти ===&lt;br /&gt;
&lt;br /&gt;
* '''Path copying.''' Время и число новых узлов — длина пути &amp;lt;math&amp;gt;O(h)&amp;lt;/math&amp;gt;. Для сбалансированного дерева &amp;lt;math&amp;gt;h = O(\log n)&amp;lt;/math&amp;gt;; для вырожденного — &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;. Суммарно за &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вставок — &amp;lt;math&amp;gt;O(n\log n)&amp;lt;/math&amp;gt; узлов.&lt;br /&gt;
* '''Fat nodes.''' Обновление &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (добавление в список), доступ &amp;lt;math&amp;gt;O(\log m)&amp;lt;/math&amp;gt; (двоичный поиск по истории длины &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt;), память &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; на изменение.&lt;br /&gt;
* '''Node copying.''' При ограниченной входящей степени — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированно на обновление и &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; доступ; без ограничения метод теряет оценку.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Почему нельзя слепо копировать амортизацию ===&lt;br /&gt;
&lt;br /&gt;
Оценки вида «амортизированно &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;» опираются на '''последовательность операций'''. В персистентной структуре одну и ту же операцию можно выполнить из '''разных версий''', поэтому делить работу на «число операций» нельзя: дорогой шаг «переигрывается» через ветвление. Именно поэтому, например, энергичная очередь на двух стеках ([[Персистентная очередь|персистентная очередь]]) не даёт худшего &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; без дополнительных приёмов (ленивые потоки и кредит/потенциал). Более того, для функциональных структур с ленью амортизацию удаётся сохранить, только если «долг» хранится '''внутри структуры''' (кредит у банкирской очереди, потенциал у физической) [3][4].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Сравнение с альтернативами ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&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;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| крошечные структуры, простота&lt;br /&gt;
|-&lt;br /&gt;
| Path copying&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| сбалансированные деревья; функциональный стиль&lt;br /&gt;
|-&lt;br /&gt;
| Fat nodes&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log m)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| важна память, дорогой доступ приемлем&lt;br /&gt;
|-&lt;br /&gt;
| Node copying&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт.&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; аморт.&lt;br /&gt;
| ограниченная входящая степень&lt;br /&gt;
|-&lt;br /&gt;
| Функциональная пересборка&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| пакетные обновления, простота кода&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-06-comparison.svg|thumb|700px|Рис. 6. Ориентировочные оценки методов; точные классы зависят от модели и структуры.]]&lt;br /&gt;
&lt;br /&gt;
'''Вердикт.''' Для деревьев почти всегда берут path copying: он прост и даёт &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;. Когда память критична и структура произвольная, смотрят на fat nodes/node copying (с оговоркой про входящую степень). Если обновления пакетные — допустима пересборка. Для гарантии &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае нужны специальные конструкции ([[Очередь на шести стеках|очередь на шести стеках]] ).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Где применяется ==&lt;br /&gt;
&lt;br /&gt;
# '''Функциональные языки.''' В Haskell, OCaml, Standard ML списки, деревья и очереди неизменяемы; любая операция возвращает новое значение, а старые продолжают существовать. Почему: язык не допускает мутации, и персистентность — не надстройка, а базовый способ работы [3].&lt;br /&gt;
# '''Системы контроля версий.''' Git хранит коммиты как '''DAG''' неизменяемых объектов (blob, tree, commit): новая версия ссылается на старые. Ветвление и слияние коммитов — операции над DAG, но это не confluent merge структур. Почему: неизменяемость даёт целостность и дешёвое ветвление [6].&lt;br /&gt;
# '''СУБД и многоверсионность (MVCC).''' Читателю выдаётся '''снапшот''', соответствующий версии данных на момент старта транзакции, а писатель не блокирует его. Индексы могут использовать versioned pages или copy-on-write; конкретный механизм зависит от СУБД. Почему: согласованное чтение без блокировок [4][7].&lt;br /&gt;
# '''Вычислительная геометрия.''' Для точек, отрезков и планарных подразбиений методы персистентности позволяют отвечать на запросы «на момент времени &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt;»; классическая работа Дрисколла и др. посвящена именно таким применениям [1].&lt;br /&gt;
# '''Undo/redo и редакторы.''' История действий — дерево версий документа; откат — переход по ссылке, а не восстановление из журнала. Почему: &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; для списка/стека и &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt; для дерева на шаг истории.&lt;br /&gt;
# '''Параллельные вычисления.''' Неизменяемые данные безопасно разделяются между потоками без блокировок: гонки и порча общего состояния невозможны. Почему: нет записи в общие узлы.&lt;br /&gt;
&lt;br /&gt;
См. рис. 7: общий мотив применений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:psd-07-apply.svg|thumb|700px|Рис. 7. Общий мотив: заменить мутацию созданием версии.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Историческая справка ==&lt;br /&gt;
&lt;br /&gt;
Методы персистентности систематически изложили '''Джеймс Дрисколл, Нил Сарнак, Дэниел Слитор и Роберт Тарьян''' в работе ''Making Data Structures Persistent'' (STOC 1986; журнальная версия — Journal of Computer and System Sciences, 1989). Они ввели fat nodes и node copying и показали, что структуры с ограниченной входящей степенью можно сделать полностью персистентными за &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; амортизированного времени и памяти на операцию [1]. Метод '''path copying''' для функциональных («аппликативных») структур описал '''Юджин Майерс''' (E. W. Myers) в работе ''Efficient Applicative Data Types'' (POPL 1984) ⚠️ требует проверки точных выходных данных [2].&lt;br /&gt;
&lt;br /&gt;
Понятие '''функциональной персистентности''' и обширную коллекцию персистентных структур систематизировал '''Крис Окасаки''' в книге ''Purely Functional Data Structures'' (Cambridge University Press, 1998); он же показал, как сохранять амортизированные оценки в ленивых структурах (кредит/потенциал) [3]. Формальный амортизированный анализ, включая метод потенциала, развил '''Роберт Тарьян''' («Amortized Computational Complexity», 1985) [5]. Предшественником функциональных структур считаются списки LISP (Джон Маккарти, 1960).&lt;br /&gt;
&lt;br /&gt;
Термин «персистентная структура данных» закрепился после работы [1]; обзоры и уточнения (ретроактивность, кинетические структуры) даны, например, в ''Handbook of Data Structures and Applications'' (глава H. Kaplan о персистентности) ⚠️ требует проверки деталей [8].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Типичные ошибки и подводные камни ==&lt;br /&gt;
&lt;br /&gt;
* '''Копировать всё «на всякий случай».''' Новички копируют структуру целиком, получая &amp;lt;math&amp;gt;\Theta(n)&amp;lt;/math&amp;gt; на операцию; выигрыш персистентности — именно в разделении.&lt;br /&gt;
* '''Мутировать разделяемые узлы.''' Любая запись в общий узел портит все версии, которые на него ссылаются; функциональный подход требует неизменяемости.&lt;br /&gt;
* '''Забыть про ограничение входящей степени.''' Node copying корректен и быстр только при &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; входящих ссылок на узел; иначе число копий растёт.&lt;br /&gt;
* '''Ожидать &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; доступа от fat nodes.''' Доступ — поиск по истории, обычно &amp;lt;math&amp;gt;O(\log m)&amp;lt;/math&amp;gt;; путать дешёвое обновление с дешёвым чтением нельзя.&lt;br /&gt;
* '''Переносить амортизацию на персистентность без оговорок.''' Дорогая операция «переигрывается» через ветвление; нужен кредит/потенциал внутри структуры.&lt;br /&gt;
* '''Не освобождать недостижимые версии.''' Память растёт неограниченно; для удаления нужна сборка мусора (или явное «забывание» старых версий).&lt;br /&gt;
* '''Путать персистентность с транзакциями.''' Персистентность — про версии, а не про атомарность и изоляцию; это разные уровни абстракции.&lt;br /&gt;
* '''Сливать версии без правил (confluent).''' Merge должен быть определён и согласован с операциями структуры; коммутативность не обязательна — конкатенация списков не коммутативна.&lt;br /&gt;
* '''Считать path copying бесплатным для любой структуры.''' Он опирается на дерево и короткий путь; для графа с большим числом обратных ссылок он деградирует.&lt;br /&gt;
* '''Забывать про вырождение.''' В несбалансированном дереве path copying стоит &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;; балансировка критична для оценки &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Задачи для практики ==&lt;br /&gt;
&lt;br /&gt;
# '''Path copying.''' Реализуйте персистентное BST с &amp;lt;code&amp;gt;insert&amp;lt;/code&amp;gt; через копирование пути. Ограничение: старые версии неизменяемы. ''Подсказка:'' возвращайте новый корень; не меняйте поля старых узлов.&lt;br /&gt;
# '''Счёт узлов.''' Для вставки в сбалансированное дерево докажите, что создаётся &amp;lt;math&amp;gt;O(\log n)&amp;lt;/math&amp;gt; узлов. ''Подсказка:'' копируется ровно путь.&lt;br /&gt;
# '''Fat nodes.''' Реализуйте поле с историей &amp;lt;code&amp;gt;(version, value)&amp;lt;/code&amp;gt; и чтение на версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; двоичным поиском. Оцените время &amp;lt;code&amp;gt;Set&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;Get&amp;lt;/code&amp;gt;. ''Подсказка:'' &amp;lt;code&amp;gt;upper_bound&amp;lt;/code&amp;gt; по номеру версии.&lt;br /&gt;
# '''Ветвление.''' От версии &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; примените две разные вставки и покажите, что обе новые версии корректны и не влияют друг на друга. ''Подсказка:'' подумайте про дерево версий.&lt;br /&gt;
# '''Память.''' Сравните суммарную память &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; обновлений при (а) полной копии и (б) path copying. Приведите асимптотики и численный пример при &amp;lt;math&amp;gt;n = 10^4&amp;lt;/math&amp;gt;. ''Подсказка:'' &amp;lt;math&amp;gt;\Theta(n^2)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(n\log n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# '''Node copying.''' Объясните, почему ограничение входящей степени необходимо, и приведите структуру, где оно нарушается. ''Подсказка:'' узел с &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; входящими ссылками.&lt;br /&gt;
# '''Амортизация.''' Приведите пример, где амортизированная граница ломается при ветвлении, и объясните, как кредит/потенциал это исправляет. ''Подсказка:'' см. [[Персистентная очередь]].&lt;br /&gt;
# '''Confluent.''' Предложите правило merge для персистентного множества ключей (объединение без дубликатов) и оцените время. ''Подсказка:'' слияние двух деревьев поиска.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Связанные темы ==&lt;br /&gt;
&lt;br /&gt;
* [[Персистентный стек]] — простейшая персистентная структура (список и массив запросов).&lt;br /&gt;
* [[Персистентная очередь]] — персистентность очереди, ленивые очереди Окасаки.&lt;br /&gt;
* [[Очередь на шести стеках]] — &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; в худшем случае; персистентная очередь на пяти стеках.&lt;br /&gt;
* [[Декартово дерево]] — персистентный treap (split/merge, path copying).&lt;br /&gt;
* [[Двоичное дерево поиска]] — база для path copying.&lt;br /&gt;
* [[B+-дерево]] — индекс СУБД; обновление копированием пути.&lt;br /&gt;
* [[Асимптотическая сложность]] — амортизация, метод потенциала, худший случай.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Источники ==&lt;br /&gt;
&lt;br /&gt;
# Driscoll J. R., Sarnak N., Sleator D. D., Tarjan R. E. Making Data Structures Persistent // STOC 1986; Journal of Computer and System Sciences. — 1989. — Vol. 38 (fat nodes, node copying, ограниченная входящая степень).&lt;br /&gt;
# Myers E. W. Efficient Applicative Data Types // POPL. — 1984 (path copying для аппликативных структур) ⚠️ требует проверки точных выходных данных.&lt;br /&gt;
# Okasaki C. ''Purely Functional Data Structures''. — Cambridge University Press, 1998.&lt;br /&gt;
# Silberschatz A., Korth H. F., Sudarshan S. ''Database System Concepts''. — McGraw-Hill, 7th ed., 2019 (многоверсионность, индексы).&lt;br /&gt;
# Tarjan R. E. Amortized Computational Complexity // SIAM J. Algebraic and Discrete Methods. — 1985. — Vol. 6, № 2.&lt;br /&gt;
# Chacon S., Straub B. ''Pro Git''. — Apress, 2nd ed., 2014 (объекты и DAG версий).&lt;br /&gt;
# Wikipedia: Persistent data structure. URL: &amp;lt;nowiki&amp;gt;https://en.wikipedia.org/wiki/Persistent_data_structure&amp;lt;/nowiki&amp;gt;.&lt;br /&gt;
# Kaplan H. Persistent Data Structures // ''Handbook of Data Structures and Applications''. — Chapman &amp;amp;amp; Hall/CRC, 2004.&lt;/div&gt;</summary>
		<author><name>СВ</name></author>
	</entry>
</feed>