Изменения
Нет описания правки
Такой анализ чаще всего используется, чтобы показать, что даже если некоторые из операций последовательности являются дорогостоящими, то при усреднении по всем операциям средняя их стоимость будет небольшой за счёт низкой частоты встречаемости. Подчеркнём, что оценка, даваемая амортизационным анализом, не является вероятностной: это оценка среднего времени выполнения операций для худшего случая.
{{Определение | definition =
'''Средняя амортизационная стоимость операций''' {{---}} величина <tex>a</tex>, находящаяся по формуле: <tex dpi = "150130">a = \fracgenfrac{}{}{}{}{\sum\limits^{n}_{i = 1} {t_i}}{n}</tex>, где <tex>t_1,t_2, ... \dots t_n</tex> - время выполнения операций <tex>1,2, ... , \dots n,</tex> , совершённых над структурой данных.
}}
Амортизационный анализ использует следующие методы:
====Примеры====
Распишем приведённые рассуждения более формально.Пусть <tex dpi = "150">a = \frac{\sum\limits^{n}_{i=1} {t_i}}{n} = \frac{\sum\limits^{n}_{i=1} \sum\limits^{m}_{j=1} {t_{ij}}}{n} = \frac{\sum\limits^{m}_{j=1} \sum\limits^{n}_{i=1} {t_{ij}}}{n},</tex> где <tex>{t_{ij}}</tex> {{---}} стоимость количество операций, <tex>im</tex>{{--ой операции над <tex>j</tex>-ым элементом}} количество элементов, задействованных в этих операциях. Величина Очевидно, <tex>m \sum\limits^{leqslant n}_{i=1} {t_{ij}}</tex> не превосходит 2, т. к. над элементом можно совершить только 2 операции, стоимость которых равна 1 {{---}} добавление и удаление. Тогда:
<texdpi = "130">a = \leq genfrac{}{}{}{}{\sum\limits^{n}_{i=1} {t_i}}{n} = \genfrac{}{}{}{}{\sum\limits^{n}_{i=1} \sum\limits^{m}_{j=1} {t_{ij}}}{n} = \genfrac{}{}{}{}{\sum\limits^{m}_{j=1} \fracsum\limits^{n}_{i=1} {t_{ij}}}{n},</tex> где <tex>{t_{ij}}</tex> {{---}} стоимость <tex>i</tex>-ой операции над <tex>j</tex>-ым элементом. Величина <tex>\sum\limits^{n}_{i=1} {t_{ij}}</tex> не превосходит 2, т. к. над элементом можно совершить только 2 операции, стоимость которых равна 1 {{---}} добавление и удаление. Тогда: <tex dpi = "130">a \leqslant \genfrac{}{}{}{}{2m}{n} \leq leqslant 2,</tex> так как <tex>m \leq leqslant n</tex>.
Таким образом, средняя амортизационная стоимость операций <tex>a = O(1)</tex>.
=====Двоичный счётчик=====
Рассмотрим также двоичный инкрементальный счётчик(единственная возможная операция - увеличить значение на единицу).
Пусть результат увеличения счётчика - <tex>n</tex>, тогда в худшем случае необходимо изменить значения <tex> 1 + \lfloor log n \rfloor</tex> бит, тогда стоимость <tex>n</tex> операций - <tex> O(n log n) </tex>.
Теперь воспользуемся для анализа методом усреднения.
Каждый следующий бит изменяет своё значение в <texdpi = "130">n, \genfrac{}{}{}{}{n/}{2}, \genfrac{}{}{}{}{n/}{4...} \dots</tex> операциях.Общая стоимость: <texdpi = "130"> \sum\limits_{i=0}^{\lfloor log n \rfloor} \fracgenfrac{}{}{}{}{n}{2^i} < 2n = O(n)</tex>;
<texdpi = "130"> \fracgenfrac{}{}{}{}{O(n)}{n} = O(1) </tex>;
В итоге амортизированная стоимость одной операции - <tex>O(1)</tex>.
==Метод потенциалов==
2) Для любого <tex>i: \enskip \Phi_i = O(n \relax f(n, m))</tex>
|proof =
<tex dpi = "150130">a = \fracgenfrac{}{}{}{}{\sum\limits^{n}_{i = 1} {t_i}}{n} = \fracgenfrac{}{}{}{}{\sum\limits^{n}_{i = 1} {a_i} + \sum\limits^{n - 1}_{i = 0} {\Phi_i} - \sum\limits^{n}_{i = 1} {\Phi_i} }{n} = \fracgenfrac{}{}{}{}{n \relax O(f(n, m)) + \Phi_0 - \Phi_n}{n} = O(f(n, m))</tex>
}}
====Примеры====
=====Стек с multipop=====В качестве примера вновь рассмотрим стек с операцией <tex>\mathrm{multipop}{a}</tex>. Пусть потенциал {{---}} это количество элементов в стеке. Тогда: 1.1) <tex>a_{push} = 1 + 1 = 2,</tex> т. к. время выполнения операции <tex>\mathrm{push}{}</tex> {{---}} 1, и изменение потенциала {{---}} тоже 1.
1.2) <tex>a_{pop} = 1 - 1 = 0,</tex> т. к. время выполнения операции <tex>\mathrm{pop}{}</tex> {{---}} 1, а изменение потенциала {{---}} -1.
1.3) <tex>a_{multipop} = k - k = 0,</tex> т. к. время выполнения операции <tex>\mathrm{multipop(}{k)}</tex> {{---}} k, а изменение потенциала {{---}} -k.
2) Для любого <tex>i: \enskip \Phi_i = O(n),</tex> так как элементов в стеке не может быть больше <tex>n</tex>
Таким образом, <tex>f(n, m) = 1</tex>, а значит, средняя амортизационная стоимость операций <tex>a = O(1)</tex>.
=====Динамические хэш-таблицы=====
Рассмотрим хэш-таблицы, использующие цепочки для разрешения коллизий. Для того, чтобы поиск элемента в цепочке не начал слишком сильно влиять на производительность, введём величину <tex> \alpha </tex> {{- --}} фактор загруженности(load factor) нашей таблицы.Пусть в нашей таблице размером <tex> m </tex> хранится <tex> n </tex> элементов, тогда <texdpi = "130"> \alpha = \fracgenfrac{}{}{}{}{n}{m} </tex>
Введём значение <tex>\alpha_{max}</tex>, при превышении которого таблица будет пересоздана с увеличением размера и все элементы будут перераспределены по-новому(rehashing). Аналогично будем пересоздавать таблицу с уменьшением размера при удалении элемента.
Из-за этого сложность операции <tex>\mathrm{add}{}</tex> из <tex> O(1) </tex> станет в худшем случае <tex> O(n) </tex>, так как для пересоздания таблицы нам требуется <tex> O(n) </tex> операций.
Стоит отметить, что изменение размера массива всегда должно быть геометрическим(в <tex> k </tex> раз), потому как при изменении размера на константу нам потребуется <tex>O(n)</tex> раз пересоздать таблицу, что приведёт к сложности операции <tex>add</tex> в <tex> O(n^2) </tex>.
Введём такую потенциальную функцию, что она будет "запасать" время по мере удаления фактора загруженности от <texdpi = "130">\fracgenfrac{}{}{}{}{1}{2}</tex>:<tex> \phi Phi = 2|{n - m/2}| </tex>
Предположим, не умаляя общности, что<tex>\alpha_{max} = 1</tex>.
Теперь рассмотрим все возможные случаи для каждой из операций <tex>add, remove, find</tex>.
Операция <tex>\mathrm{add}{}: n</tex> увеличивается на единицу. Следовательно, имеются три случая:
<texdpi = "130">\fracgenfrac{}{}{}{}{1}{2} \leq leqslant \alpha < 1 </tex>: потенциал увеличивается на 2, следовательно амортизированное время {{- --}} <tex>1 + 2 = 3</tex>.
<texdpi = "130">\alpha < \fracgenfrac{}{}{}{}{1}{2}</tex>: потенциал уменьшается на 2, и амортизированное время <tex> 1 - 2 = -1 </tex>.
<tex>\alpha = 1</tex>: Таблица увеличивается в размере, так что реальная сложность {{- --}} <tex> 1 + m </tex>. Но потенциал изменяется с <tex>m</tex> до нуля, следовательно амортизационная сложность {{--- }} <tex> 1 + m - m = 1</tex>.
<tex>\mathrm{find}{}:</tex> Потенциал не изменяется, следовательно и реальная, и амортизированная сложности {{- --}} <tex>1</tex>.
<tex>\mathrm{remove}{}: n</tex> уменьшается на единицу. Возможны три случая:
<texdpi = "130">\fracgenfrac{}{}{}{}{1}{2} \leq leqslant \alpha < 1 </tex>: потенциал уменьшается на 2, и амортизированное время <tex> 1 - 2 = -1 </tex>.
<texdpi = "130">\alpha < \fracgenfrac{}{}{}{}{1}{2}</tex>: потенциал увеличивается на 2, следовательно амортизированное время {{- --}} <tex>1 + 2 = 3</tex>.
<tex>\alpha = 1</tex>: Таблица уменьшается в размере, следовательно реальная сложность {{-- -}} <texdpi = "130"> 1 + \fracgenfrac{}{}{}{}{m}{4}</tex>, а потенциал меняется от <texdpi = "130">\fracgenfrac{}{}{}{}{m}{2}</tex> до нуля, следовательно амортизированная стоимость {{- --}} <texdpi = "130"> 1 + \fracgenfrac{}{}{}{}{m}{4} - \fracgenfrac{}{}{}{}{m}{2} = 1 - \fracgenfrac{}{}{}{}{m}{4}</tex>.
В каждом случае амортизированное время одной операции {{- --}} <tex>O(1)</tex>. Если мы создадим нашу таблицу так, что <texdpi = "130">\alpha = \fracgenfrac{}{}{}{}{1}{2}</tex>, то изначально потенциал будет равен нулю. И так мы будем знать, что потенциал никогда не станет меньше этого значения; в итоге амортизированная стоимость будет верхней границей реальной оценки. Следовательно, сложность последовательности из <tex> n</tex> операций будет равна <tex>O(n)</tex>.
==Метод предоплаты==
Представим, что использование определенного количества времени равносильно использованию определенного количества монет (плата за выполнение каждой операции). В методе предоплаты каждому типу операций присваивается своя учётная стоимость. Эта стоимость может быть больше фактической, в таком случае лишние монеты используются как резерв для выполнения других операций в будущем, а может быть меньше, тогда гарантируется, что текущего накопленного резерва достаточно для выполнения операции. Для доказательства оценки средней амортизационной стоимости <tex>O(f(n, m))</tex> нужно построить учётные стоимости так, что для каждой операции она будет составлять <tex>O(f(n, m))</tex>. Тогда для последовательности из <tex>n</tex> операций суммарно будет затрачено <tex>n \cdot O(f(n, m))</tex> монет, следовательно, cредняя амортизационная стоимость операций будет <tex dpi = "150130">a = \fracgenfrac{}{}{}{}{\sum\limits^{n}_{i = 1} {t_i}}{n} = \fracgenfrac{}{}{}{}{n \cdot O(f(n, m))}{n}</tex> <tex>= O(f(n, m))</tex>.
====Примеры====
Таким образом, для каждой операции требуется <tex>O(1)</tex> монет, а значит, средняя амортизационная стоимость операций <tex>a = O(1)</tex>.
=====Очередь на двух стеках=====
Рассмотрим реализацию очереди на двух стеках. Пусть наши стеки имеют операции <tex>\mathrm{push}{}</tex> и <tex>\mathrm{pop}{}</tex>, обе стоимостью <tex>1</tex>.Пусть каждое добавление элемента в очередь будет стоить 3 монеты. Это покроет стоимость операций <tex>\mathrm{push}{}</tex> и <tex>\mathrm{pop}{}</tex> для переноса элемента из первого стека во второй, если потребуется, и ещё 1 монета будет нужна для того, чтобы поместить элемент в первый стек. Стоимость удаления элемента из очереди {{- --}} 1 монета(она потребуется нам для удаления элемента из второго стека).
Таким образом, амортизационная стоимость каждой операции {{- --}} <tex> O(1) </tex>.
==Литература==