Цифровая сортировка — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 17: Строка 17:
  
 
Такой подход к алгоритму называют '''LSD-сортировкой''' (Least Significant Digit radix sort). Существует модификация алгоритма цифровой сортировки, анализирующая значения разрядов, начиная слева, с наиболее значащих разрядов. Данный алгоритм известен, как '''MSD-сортировка''' (Most Significant Digit radix sort).
 
Такой подход к алгоритму называют '''LSD-сортировкой''' (Least Significant Digit radix sort). Существует модификация алгоритма цифровой сортировки, анализирующая значения разрядов, начиная слева, с наиболее значащих разрядов. Данный алгоритм известен, как '''MSD-сортировка''' (Most Significant Digit radix sort).
=== Корректность алгоритма ===
+
=== Корректность алгоритма LSD-сортировки ===
 
Докажем, что данный алгоритм работает верно, используя метод математической индукции по номеру разряда. Пусть <tex> n </tex> {{---}} количество разрядов в сортируемых объектах.
 
Докажем, что данный алгоритм работает верно, используя метод математической индукции по номеру разряда. Пусть <tex> n </tex> {{---}} количество разрядов в сортируемых объектах.
  
Строка 29: Строка 29:
 
=== LSD-сортировка ===
 
=== LSD-сортировка ===
 
В качестве примера рассмотрим сортировку чисел. Как говорилось выше, в такой ситуации в качестве устойчивой сортировки применяют сортировку подсчетом, так как обычно количество различных значений разрядов не превосходит количества сортируемых элементов. Ниже приведен псевдокод цифровой сортировки, которой подается массив <tex> A </tex> <tex> m </tex>-разрядных чисел размера <tex> n </tex>. Сам по себе алгоритм представляет собой цикл по номеру разряда, на каждой итерации которого элементы массива <tex> A </tex> размещаются в нужном порядке во вспомогательном массиве <tex> B </tex>. Для подсчета количества объектов, <tex> i </tex>-й разряд которых одинаковый, а затем и для определения положения объектов в массиве <tex> B </tex> используется вспомогательный массив <tex> C </tex>. Функция <tex> digit(x, i) </tex> возвращает <tex> i </tex>-й разряд числа <tex> x </tex>. Также считаем, что значения разрядов меньше <tex> k </tex>.
 
В качестве примера рассмотрим сортировку чисел. Как говорилось выше, в такой ситуации в качестве устойчивой сортировки применяют сортировку подсчетом, так как обычно количество различных значений разрядов не превосходит количества сортируемых элементов. Ниже приведен псевдокод цифровой сортировки, которой подается массив <tex> A </tex> <tex> m </tex>-разрядных чисел размера <tex> n </tex>. Сам по себе алгоритм представляет собой цикл по номеру разряда, на каждой итерации которого элементы массива <tex> A </tex> размещаются в нужном порядке во вспомогательном массиве <tex> B </tex>. Для подсчета количества объектов, <tex> i </tex>-й разряд которых одинаковый, а затем и для определения положения объектов в массиве <tex> B </tex> используется вспомогательный массив <tex> C </tex>. Функция <tex> digit(x, i) </tex> возвращает <tex> i </tex>-й разряд числа <tex> x </tex>. Также считаем, что значения разрядов меньше <tex> k </tex>.
   radixSort(A)
+
   '''function''' radixSort(int[] A)
 
       '''for''' i = 1 '''to''' m               
 
       '''for''' i = 1 '''to''' m               
 
           '''for''' j = 0 '''to''' k - 1                               
 
           '''for''' j = 0 '''to''' k - 1                               
Строка 35: Строка 35:
 
           '''for''' j = 0 '''to''' n - 1
 
           '''for''' j = 0 '''to''' n - 1
 
               d = digit(A[j], i)
 
               d = digit(A[j], i)
               C[d] += 1
+
               C[d]++
 
           count = 0
 
           count = 0
 
           '''for''' j = 0 '''to''' k - 1
 
           '''for''' j = 0 '''to''' k - 1
 
               tmp = C[j]
 
               tmp = C[j]
 
               C[j] = count
 
               C[j] = count
               count = count + tmp
+
               count += tmp
 
           '''for''' j = 0 '''to''' n - 1
 
           '''for''' j = 0 '''to''' n - 1
 
               d = digit(A[j], i)                             
 
               d = digit(A[j], i)                             
 
               B[C[d]] = A[j]             
 
               B[C[d]] = A[j]             
               C[d] += 1
+
               C[d]++
 
           A = B
 
           A = B
  
 
=== MSD-сортировка ===
 
=== MSD-сортировка ===
Сначала исходный массив делится на <tex>k</tex> частей, где <tex>k</tex> — основание, выбранное для представления сортируемых объектов. Эти части принято называть "корзинами" или "карманами". В первую корзину попадают элементы, у которых старший разряд с номером <tex>d = 0</tex> имеет значение <tex>0</tex>. Во вторую корзину попадают элементы, у которых старший разряд с номером <tex>d = 0</tex> имеет значение <tex>1</tex> и так далее. Затем элементы, попавшие в разные корзины, подвергаются рекурсивному разделению по следующему разряду с номером <tex>d = 1</tex>. Рекурсивный процесс разделения продолжается, пока не будут перебраны все разряды сортируемых объектов.  
+
Будем считать, что у всех элементов одинаковое число разрядов. Если это не так, то положим на более старших разрядах элементы с самым маленьким значением — для чисел это 0. Сначала исходный массив делится на <tex>k</tex> частей, где <tex>k</tex> — основание, выбранное для представления сортируемых объектов. Эти части принято называть "корзинами" или "карманами". В первую корзину попадают элементы, у которых старший разряд с номером <tex>d = 0</tex> имеет значение <tex>0</tex>. Во вторую корзину попадают элементы, у которых старший разряд с номером <tex>d = 0</tex> имеет значение <tex>1</tex> и так далее. Затем элементы, попавшие в разные корзины, подвергаются рекурсивному разделению по следующему разряду с номером <tex>d = 1</tex>. Рекурсивный процесс разделения продолжается, пока не будут перебраны все разряды сортируемых объектов и пока размер корзины больше единицы. (то есть останавливаемся когда <tex>d > m</tex> или <tex>l \geq r</tex>, где m — максимальное число разрядов в сортируемых объектах, <tex>l</tex>, <tex>r</tex> — левая и правая границы отрезка массива <tex>A</tex>).
 +
 
 
В основу распределения элементов по корзинам положен метод распределяющего подсчета элементов с одинаковыми значениями в сортируемом разряде. Для этого выполняется просмотр массива и подсчет количества элементов с различными значениями в сортируемом разряде. Эти счетчики фиксируются во вспомогательном массиве счетчиков <tex>cnt</tex>. Затем счетчики используются для вычисления размеров корзин и определения границ разделения массива. В соответствии с этими границами сортируемые объекты переносятся во вспомогательный массив <tex>c</tex>, в котором размещены корзины.
 
В основу распределения элементов по корзинам положен метод распределяющего подсчета элементов с одинаковыми значениями в сортируемом разряде. Для этого выполняется просмотр массива и подсчет количества элементов с различными значениями в сортируемом разряде. Эти счетчики фиксируются во вспомогательном массиве счетчиков <tex>cnt</tex>. Затем счетчики используются для вычисления размеров корзин и определения границ разделения массива. В соответствии с этими границами сортируемые объекты переносятся во вспомогательный массив <tex>c</tex>, в котором размещены корзины.
 
После того как корзины сформированы, содержимое вспомогательного массива <tex>c</tex> переносится обратно в исходный массив <tex>A</tex> и выполняется рекурсивное разделение новых частей по следующему разряду в пределах границ корзин, полученных на предыдущем шаге.
 
После того как корзины сформированы, содержимое вспомогательного массива <tex>c</tex> переносится обратно в исходный массив <tex>A</tex> и выполняется рекурсивное разделение новых частей по следующему разряду в пределах границ корзин, полученных на предыдущем шаге.
  
<tex>m</tex> — максимальное количество разрядов в сортируемых объектах
+
Изначально запускаем функцию так <tex>radixSort(A, 1, A.length, 1)</tex>
  
<tex>l</tex>, <tex>r</tex> — левая и правая границы части массива <tex>A</tex>
+
   '''function''' radixSort(int[] A, int l, int r, int d)
   radixSort(A, l, r, d)
+
       '''if''' d > m '''or''' l >= r  
       '''if''' d > m '''or''' l >= r '''return'''
+
          '''return'''
 
       '''for''' j = 0 '''to''' k + 1  
 
       '''for''' j = 0 '''to''' k + 1  
 
           cnt[j] = 0
 
           cnt[j] = 0
 
       '''for''' i = l '''to''' r                               
 
       '''for''' i = l '''to''' r                               
 
           j = digit(A[i], d)
 
           j = digit(A[i], d)
           cnt[j + 1] = cnt[j + 1] + 1
+
           cnt[j + 1]++
 
       '''for''' j = 2 '''to''' k
 
       '''for''' j = 2 '''to''' k
           cnt[j] = cnt[j] + cnt[j - 1]
+
           cnt[j] += cnt[j - 1]
 
       '''for''' i = l '''to''' r
 
       '''for''' i = l '''to''' r
 
           j = digit(A[i], d)
 
           j = digit(A[i], d)
 
           c[l + cnt[j]] = A[i]
 
           c[l + cnt[j]] = A[i]
           cnt[j] = cnt[j] + 1
+
           cnt[j]++
 
       '''for''' i = l '''to''' r
 
       '''for''' i = l '''to''' r
 
           A[i] = c[i]
 
           A[i] = c[i]
Строка 81: Строка 82:
 
Если количество разрядов {{---}} константа, а <tex> k = O(n) </tex>, то сложность цифровой сортировки составляет <tex> O(n) </tex>, то есть она линейно зависит от количества сортируемых чисел.
 
Если количество разрядов {{---}} константа, а <tex> k = O(n) </tex>, то сложность цифровой сортировки составляет <tex> O(n) </tex>, то есть она линейно зависит от количества сортируемых чисел.
  
== Литература ==
+
Сложность MSD-сортировки оценивается величиной <tex>O(n \log_k n)</tex>. При больших k величина <tex>\log_k n</tex> становится малой и сложность становится линейной функцией <tex>O(n)</tex>
 +
 
 +
Существует так же модификация MSD-сортировки, при которой рекурсивный процесс останавливается при небольших размерах текущего кармана и вызывается более быстрая сортировка основанная на сравнениях (например, сортировка вставками).
 +
 
 +
Таким образом, если k небольшое, то лучше использовать LSD-сортировку, при больших k {{---}} MSD-сортировку.
 +
== Источники информации ==
 
* Дональд Кнут Искусство программирования, том 3. Сортировка и поиск
 
* Дональд Кнут Искусство программирования, том 3. Сортировка и поиск
 
* Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ
 
* Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ
 
== Ссылки ==
 
 
* [http://rain.ifmo.ru/cat/view.php/vis/sorts/linear-2005 Визуализатор 1] — Java-аплет.
 
* [http://rain.ifmo.ru/cat/view.php/vis/sorts/linear-2005 Визуализатор 1] — Java-аплет.
 
* [http://rain.ifmo.ru/cat/view.php/vis/sorts/linear-2001 Визуализатор 2] — Java-аплет.
 
* [http://rain.ifmo.ru/cat/view.php/vis/sorts/linear-2001 Визуализатор 2] — Java-аплет.
 +
== Смотри также ==
 +
* [[Сортировка подсчетом]]
 +
* [[Сортировка вставками]]
 +
* [[wikipedia:ru:Поразрядная сортировка|Википедия {{---}} Цифровая сортировка]]
  
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Сортировки]]
 
[[Категория: Сортировки]]

Версия 18:39, 20 мая 2014

Цифровая сортировка — один из алгоритмов сортировки, использующих внутреннюю структуру сортируемых объектов.

Алгоритм

Пример цифровой сортировки трехзначных чисел, начиная с младших разрядов
Пример цифровой сортировки трехзначных чисел, начиная со старших разрядов

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

По аналогии с разрядами чисел будем называть элементы, из которых состоят сортируемые объекты, разрядами. Сам алгоритм состоит в последовательной сортировке объектов какой-либо устойчивой сортировкой по каждому разряду, в порядке от младшего разряда к старшему, после чего последовательности будут расположены в требуемом порядке.

Примерами объектов, которые удобно разбивать на разряды и сортировать по ним, являются числа и строки.

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

Для вышеперечисленных объектов наиболее часто в качестве устойчивой сортировки применяют сортировку подсчетом.

Такой подход к алгоритму называют LSD-сортировкой (Least Significant Digit radix sort). Существует модификация алгоритма цифровой сортировки, анализирующая значения разрядов, начиная слева, с наиболее значащих разрядов. Данный алгоритм известен, как MSD-сортировка (Most Significant Digit radix sort).

Корректность алгоритма LSD-сортировки

Докажем, что данный алгоритм работает верно, используя метод математической индукции по номеру разряда. Пусть [math] n [/math] — количество разрядов в сортируемых объектах.

База: [math] n = 1 [/math]. Очевидно, что алгоритм работает верно, потому что в таком случае мы просто сортируем младшие разряды какой-то заранее выбранной устойчивой сортировкой.

Переход: Пусть для [math] n = k [/math] алгоритм правильно отсортировал последовательности по [math] k [/math] младшим разрядам. Покажем, что в таком случае, при сортировке по [math] (k + 1) [/math]-му разряду, последовательности также будут отсортированы в правильном порядке.

Вспомогательная сортировка разобьет все объекты на группы, в которых [math] (k + 1) [/math]-й разряд объектов одинаковый. Рассмотрим такие группы. Для сортировки по отдельным разрядам мы используем устойчивую сортировку, следовательно порядок объектов с одинаковым [math] (k + 1) [/math]-м разрядом не изменился. Но по предположению индукции по предыдущим [math] k [/math] разрядам последовательности были отсортированы правильно, и поэтому в каждой такой группе они будут отсортированы верно. Также верно, что сами группы находятся в правильном относительно друг друга порядке, а, следовательно, и все объекты отсортированы правильно по [math] (k + 1) [/math]-м младшим разрядам.

Псевдокод

LSD-сортировка

В качестве примера рассмотрим сортировку чисел. Как говорилось выше, в такой ситуации в качестве устойчивой сортировки применяют сортировку подсчетом, так как обычно количество различных значений разрядов не превосходит количества сортируемых элементов. Ниже приведен псевдокод цифровой сортировки, которой подается массив [math] A [/math] [math] m [/math]-разрядных чисел размера [math] n [/math]. Сам по себе алгоритм представляет собой цикл по номеру разряда, на каждой итерации которого элементы массива [math] A [/math] размещаются в нужном порядке во вспомогательном массиве [math] B [/math]. Для подсчета количества объектов, [math] i [/math]-й разряд которых одинаковый, а затем и для определения положения объектов в массиве [math] B [/math] используется вспомогательный массив [math] C [/math]. Функция [math] digit(x, i) [/math] возвращает [math] i [/math]-й разряд числа [math] x [/math]. Также считаем, что значения разрядов меньше [math] k [/math].

 function radixSort(int[] A)
     for i = 1 to m               
         for j = 0 to k - 1                              
             C[j] = 0                                  
         for j = 0 to n - 1
             d = digit(A[j], i)
             C[d]++
         count = 0
         for j = 0 to k - 1
             tmp = C[j]
             C[j] = count
             count += tmp
         for j = 0 to n - 1
             d = digit(A[j], i)                             
             B[C[d]] = A[j]            
             C[d]++
         A = B

MSD-сортировка

Будем считать, что у всех элементов одинаковое число разрядов. Если это не так, то положим на более старших разрядах элементы с самым маленьким значением — для чисел это 0. Сначала исходный массив делится на [math]k[/math] частей, где [math]k[/math] — основание, выбранное для представления сортируемых объектов. Эти части принято называть "корзинами" или "карманами". В первую корзину попадают элементы, у которых старший разряд с номером [math]d = 0[/math] имеет значение [math]0[/math]. Во вторую корзину попадают элементы, у которых старший разряд с номером [math]d = 0[/math] имеет значение [math]1[/math] и так далее. Затем элементы, попавшие в разные корзины, подвергаются рекурсивному разделению по следующему разряду с номером [math]d = 1[/math]. Рекурсивный процесс разделения продолжается, пока не будут перебраны все разряды сортируемых объектов и пока размер корзины больше единицы. (то есть останавливаемся когда [math]d \gt m[/math] или [math]l \geq r[/math], где m — максимальное число разрядов в сортируемых объектах, [math]l[/math], [math]r[/math] — левая и правая границы отрезка массива [math]A[/math]).

В основу распределения элементов по корзинам положен метод распределяющего подсчета элементов с одинаковыми значениями в сортируемом разряде. Для этого выполняется просмотр массива и подсчет количества элементов с различными значениями в сортируемом разряде. Эти счетчики фиксируются во вспомогательном массиве счетчиков [math]cnt[/math]. Затем счетчики используются для вычисления размеров корзин и определения границ разделения массива. В соответствии с этими границами сортируемые объекты переносятся во вспомогательный массив [math]c[/math], в котором размещены корзины. После того как корзины сформированы, содержимое вспомогательного массива [math]c[/math] переносится обратно в исходный массив [math]A[/math] и выполняется рекурсивное разделение новых частей по следующему разряду в пределах границ корзин, полученных на предыдущем шаге.

Изначально запускаем функцию так [math]radixSort(A, 1, A.length, 1)[/math]

 function radixSort(int[] A, int l, int r, int d)
     if d > m or l >= r 
         return
     for j = 0 to k + 1 
         cnt[j] = 0
     for i = l to r                              
         j = digit(A[i], d)
         cnt[j + 1]++
     for j = 2 to k
         cnt[j] += cnt[j - 1]
     for i = l to r
         j = digit(A[i], d)
         c[l + cnt[j]] = A[i]
         cnt[j]++
     for i = l to r
         A[i] = c[i]
     radixSort(A, l, l + cnt[0] - 1, d + 1)
     for i = 1 to k
         radixSort(A, l + cnt[i - 1], l + cnt[i] - 1, d + 1)

Сложность

Пусть [math] m [/math] — количество разрядов, [math] n [/math] — количество объектов, которые нужно отсортировать, [math] T(n) [/math] — время работы устойчивой сортировки. Цифровая сортировка выполняет [math] k [/math] итераций, на каждой из которой выполняется устойчивая сортировка и не более [math] O(1) [/math] других операций. Следовательно время работы цифровой сортировки — [math] O(k T(n)) [/math].

Рассмотрим отдельно случай сортировки чисел. Пусть в качестве аргумента сортировке передается массив, в котором содержатся [math] n [/math] [math] m [/math]-значных чисел, и каждая цифра может принимать значения от [math] 0 [/math] до [math] k - 1 [/math]. Тогда цифровая сортировка позволяет отсортировать данный массив за время [math] O(m (n + k)) [/math], если устойчивая сортировка имеет время работы [math] O(n + k) [/math]. Если [math] k [/math] небольшое, то оптимально выбирать в качестве устойчивой сортировки сортировку подсчетом.

Если количество разрядов — константа, а [math] k = O(n) [/math], то сложность цифровой сортировки составляет [math] O(n) [/math], то есть она линейно зависит от количества сортируемых чисел.

Сложность MSD-сортировки оценивается величиной [math]O(n \log_k n)[/math]. При больших k величина [math]\log_k n[/math] становится малой и сложность становится линейной функцией [math]O(n)[/math]

Существует так же модификация MSD-сортировки, при которой рекурсивный процесс останавливается при небольших размерах текущего кармана и вызывается более быстрая сортировка основанная на сравнениях (например, сортировка вставками).

Таким образом, если k небольшое, то лучше использовать LSD-сортировку, при больших k — MSD-сортировку.

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

  • Дональд Кнут Искусство программирования, том 3. Сортировка и поиск
  • Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ
  • Визуализатор 1 — Java-аплет.
  • Визуализатор 2 — Java-аплет.

Смотри также