Счётчик Кнута — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Переписывание конспекта заново)
(Исправления)
Строка 1: Строка 1:
'''Счетчик Кнута''' (англ. ''Knuth's Counter'') &mdash; структура данных, представленная избыточной двоичной системой счисления, где добавление единицы к числу выполняется за <tex>O(1)</tex>.
+
'''Счетчик Кнута''' (англ. ''Knuth's Counter'') {{---}} структура данных, представленная избыточной двоичной системой счисления, где добавление единицы к числу выполняется за <tex>O(1)</tex>.
  
Неотрицательное целое число <tex>N</tex> в ''избыточной двоичной системе счисления'' записывается в виде последовательности разрядов <tex>d_n d_{n-1} \dotsc  d_2 d_1</tex>, где <tex>n</tex> обозначает количество разрядов в числе, <math>d_i</math> &mdash; <tex>i</tex>&ndash;й разряд числа <tex>(1 \le i \le n)</tex>, причем <math>d_i \in \{0,1,2\}</math> и <math>\sum\limits_{i=1}^n d_i \cdot 2^i = N.
+
Неотрицательное целое число <tex>N</tex> в ''избыточной двоичной системе счисления'' записывается в виде последовательности разрядов <tex>d_n d_{n-1} \dotsc  d_2 d_1</tex>, где <tex>n</tex> обозначает количество разрядов в числе, <tex>d_i</tex> {{---}} <tex>i</tex>&ndash;й разряд числа <tex>(1 \leqslant i \leqslant n)</tex>, причем <tex>d_i \in \{0,1,2\}</tex> и <tex>\sum\limits_{i=1}^n d_i \cdot 2^i = N.
</math>
+
</tex>
  
 
== Алгоритм ==
 
== Алгоритм ==
  
Оригинальный алгоритм представлен в Clancy, Knuth и состоит из двух правил:
+
Оригинальный алгоритм предложен в Кнутом, и состоит из двух правил:
  
 
# Найти младший разряд <tex>d_i</tex> равный <tex>2</tex> и, если таковой имеется, заменить последовательность <tex>d_{i+1}d_i</tex> на <tex>(d_{i+1}+1)0</tex>
 
# Найти младший разряд <tex>d_i</tex> равный <tex>2</tex> и, если таковой имеется, заменить последовательность <tex>d_{i+1}d_i</tex> на <tex>(d_{i+1}+1)0</tex>
Строка 13: Строка 13:
 
Чтобы достичь необходимой оценки и не выполнять каждый раз поиск для первого правила, можно хранить односвязный список позиций двоек в числе. Тогда чтобы найти младший разряд равный двум нужно просто взять первый элемент списка. Также, непосредственно перед изменением значений разрядов в правилах необходимо выполнять следующее:
 
Чтобы достичь необходимой оценки и не выполнять каждый раз поиск для первого правила, можно хранить односвязный список позиций двоек в числе. Тогда чтобы найти младший разряд равный двум нужно просто взять первый элемент списка. Также, непосредственно перед изменением значений разрядов в правилах необходимо выполнять следующее:
  
# Если <tex>d_{i+1}=1</tex>, то заменить первый элемент списка с <math>i</math> на <math>i+1</math>, иначе удалить его.
+
# Если <tex>d_{i+1}=1</tex>, то заменить первый элемент списка с <tex>i</tex> на <tex>i+1</tex>, иначе удалить его.
# Если <tex>d_1=1</tex>, то добавить в начало списка 1.
+
# Если <tex>d_1=1</tex>, то добавить в начало списка <tex>1</tex>.
  
 
Проблемой может оказаться появление двух последовательных двоек, при этом перое правило может породить
 
Проблемой может оказаться появление двух последовательных двоек, при этом перое правило может породить
тройку, что недопустимо. Однако если между любой парой двоек всегда будет находится хотя бы один
+
тройку. То есть недопустима следующая ситуация <tex>\dotsc 22\dotsc \rightarrow \dotsc 30\dotsc</tex>.
0, то такой ситуации не возникнет. Покажем, что если это условие с нулем выполняется, то оно
+
В свою очередь такая ситуация получается из этой <tex>\dotsc 212\dotsc \rightarrow \dotsc 220\dotsc</tex>.
выполняется и после инкремента, рассмотрев основные случаи
+
Причем количество единиц между двойками может быть любое, в итоге это приведет к появлению тройки.
(в обозначениях левой части предполагается что правая двойка самая младшая из всех до инкремента):
+
Однако если между любой парой двоек всегда будет находится хотя бы один
* <math>\dotsc 2\dotsc 0\dotsc 12\dotsc </math> переходит в <math>\dotsc 2\dotsc 0\dotsc 20\dotsc </math>, при этом в младшем разряде может появиться новая 2-ка и между ней следующей будет хотя бы один ноль.
+
<tex>0</tex>, то такой ситуации не возникнет. Покажем, что что этот инвариант
* <math>\dotsc 2\dotsc 0\dotsc 02\dotsc </math> переходит в <math>\dotsc 2\dotsc 0\dotsc 10\dotsc </math>, ситуация почти аналогична предыдущей.
+
поддерживается после инкремента, рассмотрев возможные ситуации:
* <math>\dotsc 202\dotsc </math> переходит в <math>\dotsc 210\dotsc </math>, также гарантируется наличие нуля при появлении двойки в младшем разряде
+
# Число двоек не изменяется
* Появление двух двоек, если уже есть ровно одна возможно, тогда <math>\dotsc 12\dotsc 1</math> переходит в <math>\dotsc 20\dotsc 2</math>, ясно что в этом случае между ними также будет хотя бы один ноль.
+
## <tex>\dotsc 2\dotsc 0\dotsc 12\dotsc 0 \rightarrow \dotsc 2\dotsc 0\dotsc 20\dotsc 1</tex>.
 +
## <tex>\dotsc 02\dotsc 1 \rightarrow \dotsc 10\dotsc 2</tex>.
 +
## <tex>\dotsc 2\dotsc 02\dotsc 1 \rightarrow \dotsc 2\dotsc 10\dotsc 2</tex> (частный случай предыдущего).
 +
# Проподает одна двойка
 +
## <tex> \dotsc 02\dotsc 0 \rightarrow \dotsc 10\dotsc 1</tex>.
 +
## <tex> \dotsc 02 \rightarrow \dotsc 11</tex>.
 +
# Появление новой двойки
 +
## <tex>\dotsc 1 \rightarrow \dotsc 2</tex> (имеется в виду появление единственной двойки).
 +
## <tex>\dotsc 12\dotsc 1 \rightarrow \dotsc 20\dotsc 2</tex>.
 +
## <tex>\dotsc 2\dotsc 0\dotsc 12\dotsc 1 \rightarrow \dotsc 2\dotsc 0\dotsc 20\dotsc 2</tex> (частный случай предыдущего).
  
В таблице можно увидеть как будет изменятья представление при применении данных правил десять раз к нулю (представления чисел от 0 до 10):
+
Таким образом мы видим, что 0 всегда сохраняется.
 +
 
 +
В таблице можно увидеть как будет изменятья представление при применении данных правил десять раз к нулю (представления чисел от <tex>0</tex> до <tex>9</tex>):
  
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! Шаг
 
! Представление
 
 
! Шаг
 
! Шаг
 
! Представление
 
! Представление
Строка 37: Строка 46:
 
| 0  
 
| 0  
 
| 0
 
| 0
 +
|-
 +
| 1
 +
| 1
 +
|-
 +
| 2
 +
| 2
 +
|-
 +
| 3
 +
| 11
 +
|-
 +
| 4
 +
| 12
 +
|-
 
| 5  
 
| 5  
 
| 21
 
| 21
 
|-
 
|-
| 1
 
| 1
 
 
| 6  
 
| 6  
 
| 102
 
| 102
 
|-
 
|-
| 2
 
| 2
 
 
| 7  
 
| 7  
 
| 111
 
| 111
 
|-
 
|-
| 3
 
| 11
 
 
| 8  
 
| 8  
 
| 112
 
| 112
 
|-
 
|-
| 4
 
| 12
 
 
| 9  
 
| 9  
 
| 121
 
| 121
Строка 63: Строка 77:
 
== Обобщение на системы с произвольным основанием ==
 
== Обобщение на системы с произвольным основанием ==
  
В общем случае подобные счётчики называются ''<math>b</math>-ричными избыточными счетчиками'' (''ИС''), которые похожи на счетчик Кнута,
+
В общем случае подобные счётчики называются ''<tex>b</tex>-ричными избыточными счетчиками'' (''ИС''), которые похожи на счетчик Кнута,
но основание системы может быть произвольным, то есть <math>d_i \in \{0,1,\dotsc ,b\}</math> и <math>\sum\limits_{i=1}^n d_i \cdot b^i = N</math>
+
но основание системы может быть произвольным, то есть <tex>d_i \in \{0,1,\dotsc ,b\}</tex> и <tex>\sum\limits_{i=1}^n d_i \cdot b^i = N</tex>
, где <math>b</math> &mdash; основание. Такие счетчики позволяют прибавить единицу к любому разряду, то есть увеличить число на <math>b^i</math>
+
, где <tex>b</tex> {{---}} основание. Такие счетчики позволяют прибавить единицу к любому разряду, то есть увеличить число на <tex>b^i</tex>
за <math>O(1)</math>
+
за <tex>O(1)</tex>
Назовем такое представление ''регулярным'', если между дувумя разрядами равными <math>b</math> есть хотя бы один разряд отличный от
+
Назовем такое представление ''регулярным'', если между дувумя разрядами равными <tex>b</tex> есть хотя бы один разряд отличный от
<math>b-1</math>.
+
<tex>b-1</tex>.
Операция ''исправления'' (''fix'') разряда <math>d_i=b</math> в регулярной <math>b</math>-ричного
+
Операция ''исправления'' (''fix'') разряда <tex>d_i=b</tex> в регулярной <tex>b</tex>-ричного
счетчика <math>d</math> увеличивает <math>d_{i+1}</math> на 1 и устанавливает
+
счетчика <tex>d</tex> увеличивает <tex>d_{i+1}</tex> на <tex>1</tex> и устанавливает
<math>d_i</math> в 0, образуая новый регулярный счетчик, представляющий то же число,
+
<tex>d_i</tex> в <tex>0</tex>, образуая новый регулярный счетчик, представляющий то же число,
что и <math>d</math>.
+
что и <tex>d</tex>.
Чтобы добавить 1 к разряду <math>d_i</math> регулярного ИС <math>d</math>,
+
Чтобы добавить <tex>1</tex> к разряду <tex>d_i</tex> регулярного ИС <tex>d</tex>,
 
нужно сделать следующее:
 
нужно сделать следующее:
# Если <math>d_i=b</math>, исправить <math>d_i</math>.
+
# Если <tex>d_i=b</tex>, исправить <tex>d_i</tex>.
# Если <math>d_i=b-1</math> и самый младший значащий разряд <math>d_j</math>, такой, что <math>j>i</math> и <math>d_j \ne b-1</math>, равен <math>b</math> (т.е. <math>d_j=b</math>), применить операцию исправления к разряду <math>d_j</math>.
+
# Если <tex>d_i=b-1</tex> и самый младший значащий разряд <tex>d_j</tex>, такой, что <tex>j>i</tex> и <tex>d_j \ne b-1</tex>, равен <tex>b</tex> (т.е. <tex>d_j=b</tex>), применить операцию исправления к разряду <tex>d_j</tex>.
# Добавить 1 к <math>d_i</math>.
+
# Добавить 1 к <tex>d_i</tex>.
# Если <math>d_i=b</math>, исправить <math>d_i</math>.
+
# Если <tex>d_i=b</tex>, исправить <tex>d_i</tex>.
  
Для реализации данной схемы, мы используем односвязный список разрадов от младших
+
Для реализации данной схемы, мы используем односвязный список разрядов от младших
к старшим. В дополнение каждый разряд <math>d_i</math> равный <math>b-1</math>
+
к старшим. В дополнение каждый разряд <tex>d_i</tex> равный <tex>b-1</tex>
будет иметь указатель на самый младший разряд <math>d_j</math>, такой,
+
будет иметь указатель на самый младший разряд <tex>d_j</tex>, такой,
что <math>j>i</math> и <math>d_j \ne b-1</math>, если он равен <math>b</math>,
+
что <tex>j>i</tex> и <tex>d_j \ne b-1</tex>, если он равен <tex>b</tex>,
иначе этот указатель будет на произвольный разряд <math>d_j</math> (<math>j>i</math>).
+
иначе этот указатель будет на произвольный разряд <tex>d_j</tex> (<tex>j>i</tex>).
Теперь, во время увеличения разряда <math>d_i</math> на 1, будем проверять
+
Теперь, во время увеличения разряда <tex>d_i</tex> на 1, будем проверять
 
разряд по указателю вперед (п. 2).
 
разряд по указателю вперед (п. 2).
  
 
Такое представление позволяет увеличиать произвольный разряд за константное
 
Такое представление позволяет увеличиать произвольный разряд за константное
время. Обновление указателя вперед происходит следующим образом: когда <math>d_{i+}</math> становится равен <math>b-1</math> при исправлении разряда <math>d_{i-1}</math>, устанавливаем указатель вперед разряда <math>d_{i}</math> на <math>d_{i+1}</math>, если <math>d_{i+1}=b</math>, либо копируем указатель вперед из <math>d_{i+1}</math> в <math>d_{i}</math>, если <math>d_{i+1}=b-1</math>.
+
время. Обновление указателя вперед происходит следующим образом: когда <tex>d_{i+}</tex> становится равен <tex>b-1</tex> при исправлении разряда <tex>d_{i-1}</tex>, устанавливаем указатель вперед разряда <tex>d_{i}</tex> на <tex>d_{i+1}</tex>, если <tex>d_{i+1}=b</tex>, либо копируем указатель вперед из <tex>d_{i+1}</tex> в <tex>d_{i}</tex>, если <tex>d_{i+1}=b-1</tex>.
При собственно добавлении единицы к разряду <math>d_i</math>, также необходимо обновлять его указатель вперед аналогичным образом,
+
При собственно добавлении единицы к разряду <tex>d_i</tex>, также необходимо обновлять его указатель вперед аналогичным образом,
если этот разряд становится равен <math>b-1</math>.
+
если этот разряд становится равен <tex>b-1</tex>.
  
== Литература ==
+
== Источники информации ==
 
* H. Kaplan и R. E. Tarjan. New heap data structures. 1998
 
* H. Kaplan и R. E. Tarjan. New heap data structures. 1998
 
* M. J. Clancy и D. E. Knuth. A programming and problem-solving seminar. Technical Report STAN-CS-77-606, Department of Computer Sciencr, Stanford University, Palo Alto, 1977.
 
* M. J. Clancy и D. E. Knuth. A programming and problem-solving seminar. Technical Report STAN-CS-77-606, Department of Computer Sciencr, Stanford University, Palo Alto, 1977.
 
* G. S. Brodal. Worst case priority queues. ''Proc. 7th annual ACM-SIAM Symposium on Discrete Algorithms (SODA 96)'', страницы 52-58. ACM Press, 1996.
 
* G. S. Brodal. Worst case priority queues. ''Proc. 7th annual ACM-SIAM Symposium on Discrete Algorithms (SODA 96)'', страницы 52-58. ACM Press, 1996.
 
* H. Kaplan и R. E. Tarjan. Purely functional representations of catenable sorted lists. ''Proceedings of the 28th Annual ACM Symposium of Computing'', страницы 202-211. ACM Press, 1996
 
* H. Kaplan и R. E. Tarjan. Purely functional representations of catenable sorted lists. ''Proceedings of the 28th Annual ACM Symposium of Computing'', страницы 202-211. ACM Press, 1996
 
+
* http://www.cphstl.dk/Paper/Numeral-systems/mfcs-13.pdf
== Смотрите также ==
 
 
* [[Амортизационный анализ]]
 
* [[Амортизационный анализ]]
 
* [[Представление целых чисел: прямой код, код со сдвигом, дополнительный код]]
 
* [[Представление целых чисел: прямой код, код со сдвигом, дополнительный код]]
 +
* [[Список]]
  
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Амортизационный анализ]]
 
[[Категория: Амортизационный анализ]]

Версия 22:57, 15 июня 2014

Счетчик Кнута (англ. Knuth's Counter) — структура данных, представленная избыточной двоичной системой счисления, где добавление единицы к числу выполняется за [math]O(1)[/math].

Неотрицательное целое число [math]N[/math] в избыточной двоичной системе счисления записывается в виде последовательности разрядов [math]d_n d_{n-1} \dotsc d_2 d_1[/math], где [math]n[/math] обозначает количество разрядов в числе, [math]d_i[/math][math]i[/math]–й разряд числа [math](1 \leqslant i \leqslant n)[/math], причем [math]d_i \in \{0,1,2\}[/math] и [math]\sum\limits_{i=1}^n d_i \cdot 2^i = N. [/math]

Алгоритм

Оригинальный алгоритм предложен в Кнутом, и состоит из двух правил:

  1. Найти младший разряд [math]d_i[/math] равный [math]2[/math] и, если таковой имеется, заменить последовательность [math]d_{i+1}d_i[/math] на [math](d_{i+1}+1)0[/math]
  2. Заменить [math]d_1[/math] на [math]d_1+1[/math].

Чтобы достичь необходимой оценки и не выполнять каждый раз поиск для первого правила, можно хранить односвязный список позиций двоек в числе. Тогда чтобы найти младший разряд равный двум нужно просто взять первый элемент списка. Также, непосредственно перед изменением значений разрядов в правилах необходимо выполнять следующее:

  1. Если [math]d_{i+1}=1[/math], то заменить первый элемент списка с [math]i[/math] на [math]i+1[/math], иначе удалить его.
  2. Если [math]d_1=1[/math], то добавить в начало списка [math]1[/math].

Проблемой может оказаться появление двух последовательных двоек, при этом перое правило может породить тройку. То есть недопустима следующая ситуация [math]\dotsc 22\dotsc \rightarrow \dotsc 30\dotsc[/math]. В свою очередь такая ситуация получается из этой [math]\dotsc 212\dotsc \rightarrow \dotsc 220\dotsc[/math]. Причем количество единиц между двойками может быть любое, в итоге это приведет к появлению тройки. Однако если между любой парой двоек всегда будет находится хотя бы один [math]0[/math], то такой ситуации не возникнет. Покажем, что что этот инвариант поддерживается после инкремента, рассмотрев возможные ситуации:

  1. Число двоек не изменяется
    1. [math]\dotsc 2\dotsc 0\dotsc 12\dotsc 0 \rightarrow \dotsc 2\dotsc 0\dotsc 20\dotsc 1[/math].
    2. [math]\dotsc 02\dotsc 1 \rightarrow \dotsc 10\dotsc 2[/math].
    3. [math]\dotsc 2\dotsc 02\dotsc 1 \rightarrow \dotsc 2\dotsc 10\dotsc 2[/math] (частный случай предыдущего).
  2. Проподает одна двойка
    1. [math] \dotsc 02\dotsc 0 \rightarrow \dotsc 10\dotsc 1[/math].
    2. [math] \dotsc 02 \rightarrow \dotsc 11[/math].
  3. Появление новой двойки
    1. [math]\dotsc 1 \rightarrow \dotsc 2[/math] (имеется в виду появление единственной двойки).
    2. [math]\dotsc 12\dotsc 1 \rightarrow \dotsc 20\dotsc 2[/math].
    3. [math]\dotsc 2\dotsc 0\dotsc 12\dotsc 1 \rightarrow \dotsc 2\dotsc 0\dotsc 20\dotsc 2[/math] (частный случай предыдущего).

Таким образом мы видим, что 0 всегда сохраняется.

В таблице можно увидеть как будет изменятья представление при применении данных правил десять раз к нулю (представления чисел от [math]0[/math] до [math]9[/math]):

Шаг Представление
0 0
1 1
2 2
3 11
4 12
5 21
6 102
7 111
8 112
9 121

Обобщение на системы с произвольным основанием

В общем случае подобные счётчики называются [math]b[/math]-ричными избыточными счетчиками (ИС), которые похожи на счетчик Кнута, но основание системы может быть произвольным, то есть [math]d_i \in \{0,1,\dotsc ,b\}[/math] и [math]\sum\limits_{i=1}^n d_i \cdot b^i = N[/math] , где [math]b[/math] — основание. Такие счетчики позволяют прибавить единицу к любому разряду, то есть увеличить число на [math]b^i[/math] за [math]O(1)[/math] Назовем такое представление регулярным, если между дувумя разрядами равными [math]b[/math] есть хотя бы один разряд отличный от [math]b-1[/math]. Операция исправления (fix) разряда [math]d_i=b[/math] в регулярной [math]b[/math]-ричного счетчика [math]d[/math] увеличивает [math]d_{i+1}[/math] на [math]1[/math] и устанавливает [math]d_i[/math] в [math]0[/math], образуая новый регулярный счетчик, представляющий то же число, что и [math]d[/math]. Чтобы добавить [math]1[/math] к разряду [math]d_i[/math] регулярного ИС [math]d[/math], нужно сделать следующее:

  1. Если [math]d_i=b[/math], исправить [math]d_i[/math].
  2. Если [math]d_i=b-1[/math] и самый младший значащий разряд [math]d_j[/math], такой, что [math]j\gt i[/math] и [math]d_j \ne b-1[/math], равен [math]b[/math] (т.е. [math]d_j=b[/math]), применить операцию исправления к разряду [math]d_j[/math].
  3. Добавить 1 к [math]d_i[/math].
  4. Если [math]d_i=b[/math], исправить [math]d_i[/math].

Для реализации данной схемы, мы используем односвязный список разрядов от младших к старшим. В дополнение каждый разряд [math]d_i[/math] равный [math]b-1[/math] будет иметь указатель на самый младший разряд [math]d_j[/math], такой, что [math]j\gt i[/math] и [math]d_j \ne b-1[/math], если он равен [math]b[/math], иначе этот указатель будет на произвольный разряд [math]d_j[/math] ([math]j\gt i[/math]). Теперь, во время увеличения разряда [math]d_i[/math] на 1, будем проверять разряд по указателю вперед (п. 2).

Такое представление позволяет увеличиать произвольный разряд за константное время. Обновление указателя вперед происходит следующим образом: когда [math]d_{i+}[/math] становится равен [math]b-1[/math] при исправлении разряда [math]d_{i-1}[/math], устанавливаем указатель вперед разряда [math]d_{i}[/math] на [math]d_{i+1}[/math], если [math]d_{i+1}=b[/math], либо копируем указатель вперед из [math]d_{i+1}[/math] в [math]d_{i}[/math], если [math]d_{i+1}=b-1[/math]. При собственно добавлении единицы к разряду [math]d_i[/math], также необходимо обновлять его указатель вперед аналогичным образом, если этот разряд становится равен [math]b-1[/math].

Источники информации