Сортировка подсчётом — различия между версиями
Nechaev (обсуждение | вклад) м (→Реализация) |
Nechaev (обсуждение | вклад) |
||
| Строка 1: | Строка 1: | ||
| − | '''Сортировка подсчётом''' {{---}} алгоритм сортировки целых чисел в диапазоне от <tex>0</tex> до некоторой константы <tex>k</tex>, работающий за линейное время. | + | '''Сортировка подсчётом''' {{---}} алгоритм сортировки целых чисел в диапазоне от <tex>0</tex> до некоторой константы <tex>k</tex> или сложных объектов, работающий за линейное время. |
| − | + | == Сортировка целочисленных значение == | |
| − | == Простой алгоритм == | + | === Простой алгоритм === |
Это простейший вариант алгоритма. Создать вспомогательный массив <tex>C[0..k - 1]</tex>, состоящий из нулей, затем последовательно прочитать элементы входного массива <tex>A</tex> и для каждого <tex>A[i]</tex> увеличить <tex>C[A[i]]</tex> на единицу. Теперь достаточно пройти по массиву <tex>C</tex> и для каждого <tex>number \in \{0, ..., k - 1\}</tex> в массив <tex>A</tex> последовательно записать число <tex>number\</tex> <tex> C[number]</tex> раз. | Это простейший вариант алгоритма. Создать вспомогательный массив <tex>C[0..k - 1]</tex>, состоящий из нулей, затем последовательно прочитать элементы входного массива <tex>A</tex> и для каждого <tex>A[i]</tex> увеличить <tex>C[A[i]]</tex> на единицу. Теперь достаточно пройти по массиву <tex>C</tex> и для каждого <tex>number \in \{0, ..., k - 1\}</tex> в массив <tex>A</tex> последовательно записать число <tex>number\</tex> <tex> C[number]</tex> раз. | ||
<code> | <code> | ||
| Строка 16: | Строка 16: | ||
</code> | </code> | ||
| − | == Устойчивый алгоритм == | + | === Устойчивый алгоритм === |
==== Идея ==== | ==== Идея ==== | ||
| Строка 38: | Строка 38: | ||
</code> | </code> | ||
| − | == Обобщение на произвольный целочисленный диапазон == | + | === Обобщение на произвольный целочисленный диапазон === |
Если диапазон значений (минимимум и максимум) заранее не известен, можно найти их с помощью линейного поиска, что не повлияет на асимптотику алгоритма. При работе с массивом <tex>C</tex> из <tex>A[i]</tex> необходимо вычитать минимум, а при обратной записи прибавлять. | Если диапазон значений (минимимум и максимум) заранее не известен, можно найти их с помощью линейного поиска, что не повлияет на асимптотику алгоритма. При работе с массивом <tex>C</tex> из <tex>A[i]</tex> необходимо вычитать минимум, а при обратной записи прибавлять. | ||
| − | == Анализ == | + | === Анализ === |
В первом алгоритме первые два цикла работают за <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно; двойной цикл за <tex>\Theta(n + k)</tex>. Во втором алгоритме циклы занимают <tex>\Theta(k)</tex>, <tex>\Theta(n)</tex>, <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно. Итого оба алгоритма имеют линейную временную трудоёмкость <tex>\Theta(n + k)</tex>. Используемая память в первом алгоритме равна <tex>\Theta(k)</tex>, а во втором <tex>\Theta(n + k)</tex>. | В первом алгоритме первые два цикла работают за <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно; двойной цикл за <tex>\Theta(n + k)</tex>. Во втором алгоритме циклы занимают <tex>\Theta(k)</tex>, <tex>\Theta(n)</tex>, <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно. Итого оба алгоритма имеют линейную временную трудоёмкость <tex>\Theta(n + k)</tex>. Используемая память в первом алгоритме равна <tex>\Theta(k)</tex>, а во втором <tex>\Theta(n + k)</tex>. | ||
Использование сортировки подсчётом целесообразно, когда диапазон возможных значений входных данных достаточно мал по сравнению с количеством элементов в сортируемом множестве, например, если <tex>n = 1000000</tex> и все элементы натуральные числа меньшие <tex>1000</tex>, то время работы алгоритма равно <tex>\Theta(n)</tex>. Эффективность алгоритма падает, когда необходимо сортировать различные элементы, попавшие в одну ячейку. | Использование сортировки подсчётом целесообразно, когда диапазон возможных значений входных данных достаточно мал по сравнению с количеством элементов в сортируемом множестве, например, если <tex>n = 1000000</tex> и все элементы натуральные числа меньшие <tex>1000</tex>, то время работы алгоритма равно <tex>\Theta(n)</tex>. Эффективность алгоритма падает, когда необходимо сортировать различные элементы, попавшие в одну ячейку. | ||
| + | |||
| + | == Сортировка сложных объектов == | ||
| + | === Постановка задачи === | ||
| + | Иногда бывает очень желательно применить быстрый алгоритм [[Сортировка подсчетом|сортировки подсчетом]] для упорядочивания набора каких-либо "сложных" данных. Под "сложными объектами" здесь подразумеваются структуры, содержащие в себе несколько полей. Одно из них мы выделим и назовем ключом, сортировка будет идти именно по нему (предполагается, что значения, принимаемые ключом {{---}} целые числа в диапазоне от <tex>0</tex> до <tex>k-1</tex>). | ||
| + | |||
| + | Мы не сможем использовать здесь в точности тот же алгоритм, что и для сортировки подсчетом обычных целых чисел, потому что в наборе могут быть различные структуры, имеющие одинаковые ключи. Существует два способа справиться с этой проблемой {{---}} использовать списки для хранения структур в отсортированном массиве или заранее посчитать количество структур с одинаковыми ключами для каждого значения ключа. | ||
| + | |||
| + | === Подсчет числа различных ключей === | ||
| + | ==== Описание ==== | ||
| + | Исходная последовательность из <tex>n</tex> структур хранится в массиве <tex>A</tex>, а отсортированная {{---}} в массиве <tex>B</tex> того же размера. Кроме того, используется вспомогательный массив <tex>P</tex> с индексами от <tex>0</tex> до <tex>k-1</tex>. | ||
| + | |||
| + | Идея алгоритма состоит в предварительном подсчете количества элементов с различными ключами в исходном массиве и разделении результирующего массива на части соответствующей длины (будем называть их блоками). Затем при повторном проходе исходного массива каждый его элемент копируется в специально отведенный его ключу блок, в первую свободную ячейку. Это осуществляется с помощью массива индексов <tex>P</tex>, в котором хранятся индексы начала блоков для различных ключей. <tex>P[key]</tex> {{---}} индекс в результирующем массиве, соответствующий первому элементу блока для ключа <tex>key</tex>. | ||
| + | |||
| + | * Пройдем по исходному массиву <tex>A</tex> и запишем в <tex>P[i]</tex> количество структур, ключ которых равен <tex>i</tex>. | ||
| + | [[Файл:Building_P.png]] | ||
| + | |||
| + | * Мысленно разобьем массив <tex>B</tex> на <tex>k</tex> блоков, длина каждого из которых равна соответственно <tex>P[1]</tex>, <tex>P[2]</tex>, ..., <tex>P[k]</tex>. | ||
| + | [[Файл:Splitting_B_w_colors.png]] | ||
| + | |||
| + | * Теперь массив <tex>P</tex> нам больше не нужен. Превратим его в массив, хранящий в <tex>P[i]</tex> сумму элементов от <tex>0</tex> до <tex>i-1</tex> старого массива <tex>P</tex>. | ||
| + | [[Файл:P_after_adding.png]] | ||
| + | |||
| + | * Теперь "сдвинем" массив <tex>P</tex> на элемент вперед: в новом массиве <tex>P[0] = 0</tex>, а для <tex>i > 0</tex> <tex>P[i] = P_{old}[i-1]</tex>, где <tex>P_{old}</tex> {{---}} старый массив <tex>P</tex>. <br> Это можно сделать за один проход по массиву <tex>P</tex>, причем одновременно с предыдущим шагом. <br> После этого действия в массиве <tex>P</tex> будут хранится индексы массива <tex>B</tex>. <tex>P[key]</tex> указывает на начало блока в <tex>B</tex>, соответствующего ключу <tex>key</tex>. | ||
| + | [[Файл:P_as_array_of_pointers.png]] | ||
| + | |||
| + | * Произведем саму сортировку. Еще раз пройдем по исходному массиву <tex>A</tex> и для всех <tex>i \in [0, n-1]</tex> будем помещать структуру <tex>A[i]</tex> в массив <tex>B</tex> на место <tex>P[A[i].key]</tex>, а затем увеличивать <tex>P[A[i].key]</tex> на <tex>1</tex>. Здесь <tex>A[i].key</tex> {{---}} это ключ структуры, находящейся в массиве <tex>A</tex> на <tex>i</tex>-том месте. | ||
| + | [[Файл:Sorting_A.png]] | ||
| + | |||
| + | Таким образом после завершения алгоритма в <tex>B</tex> будет содержаться исходная последовательность в отсортированном виде (так как блоки расположены по возрастанию соответствующих ключей). | ||
| + | |||
| + | Стоит также отметить, что эта сортировка является устойчивой, так как два элемента с одинаковыми ключами будут добавлены в том же порядке, в каком просматривались в исходном массиве <tex>A</tex>. | ||
| + | |||
| + | === Псевдокод === | ||
| + | |||
| + | Здесь <tex>A</tex> и <tex>B</tex> {{---}} массивы структур размера <tex>n</tex>, с индексами от <tex>0</tex> до <tex>n-1</tex>. | ||
| + | <tex>P</tex> {{---}} целочисленный массив размера <tex>k</tex>, с индексами от <tex>0</tex> до <tex>k-1</tex>, где <tex>k</tex> {{---}} количество различных ключей. | ||
| + | ComplexCountingSort | ||
| + | for i = 0 to k - 1 | ||
| + | P[i] = 0; | ||
| + | |||
| + | for i = 0 to length[A] - 1 | ||
| + | P[A[i].key] = P[A[i].key] + 1; | ||
| + | |||
| + | carry = 0; | ||
| + | for i = 0 to k - 1 | ||
| + | temporary = P[i]; | ||
| + | P[i] = carry; | ||
| + | carry = carry + temporary; | ||
| + | |||
| + | for i = 0 to length[A] - 1 | ||
| + | B[P[A[i].key]] = A[i]; | ||
| + | P[A[i].key] = P[A[i].key] + 1; | ||
| + | |||
| + | Здесь шаги 3 и 4 из описания объединены в один цикл. | ||
| + | Обратите внимание, что в последнем цикле инструкцией | ||
| + | B[P[A[i].key]] = A[i]; | ||
| + | копируется структура <tex>A[i]</tex> целиком, а не только её ключ. | ||
| + | |||
| + | === Анализ === | ||
| + | Весь алгоритм состоит из двух проходов по массиву <tex>A</tex> размера <tex>n</tex> и одного прохода по массиву <tex>P</tex> размера <tex>k</tex>. | ||
| + | Его трудоемкость, таким образом, равна <tex> O(n + k)</tex>. На практике сортировку подсчетом имеет смысл применять, если <tex>k = O(n)</tex>, поэтому можно считать время работы алгоритма равным <tex> O(n)</tex>. <br> | ||
| + | Как и в обычной сортировке подсчетом, требуется <tex> O(n + k)</tex> дополнительной памяти {{---}} на хранение массива <tex>B</tex> размера <tex>n</tex> и массива <tex>P</tex> размера <tex>k</tex>. | ||
== Источники == | == Источники == | ||
* Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. «Алгоритмы. Построение и анализ» {{---}} «Вильямс», 2011 г. {{---}} 1296 стр. {{---}} ISBN 978-5-8459-0857-5, 5-8459-0857-4, 0-07-013151-1 | * Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. «Алгоритмы. Построение и анализ» {{---}} «Вильямс», 2011 г. {{---}} 1296 стр. {{---}} ISBN 978-5-8459-0857-5, 5-8459-0857-4, 0-07-013151-1 | ||
* [http://ru.wikipedia.org/wiki/Сортировка_подсчётом Сортировка подсчетом {{---}} Википедия] | * [http://ru.wikipedia.org/wiki/Сортировка_подсчётом Сортировка подсчетом {{---}} Википедия] | ||
| + | * [http://en.wikipedia.org/wiki/Counting_sort Wikipedia {{---}} Counting sort] | ||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Сортировки]] | [[Категория: Сортировки]] | ||
Версия 13:29, 12 июня 2012
Сортировка подсчётом — алгоритм сортировки целых чисел в диапазоне от до некоторой константы или сложных объектов, работающий за линейное время.
Сортировка целочисленных значение
Простой алгоритм
Это простейший вариант алгоритма. Создать вспомогательный массив , состоящий из нулей, затем последовательно прочитать элементы входного массива и для каждого увеличить на единицу. Теперь достаточно пройти по массиву и для каждого в массив последовательно записать число раз.
SimpleCountingSort
for number = 0 to k - 1
C[number] = 0;
for i = 0 to length[A] - 1
C[A[i]] = C[A[i]] + 1;
pos = 0;
for number = 0 to k - 1
for i = 0 to C[j] - 1
A[pos] = number;
pos = pos + 1;
Устойчивый алгоритм
Идея
Основная идея состоит в том, чтобы для каждого элемента входного массива подсчитать количество элементов, меньших данного. Эта информация будет указывать на позиции элементов в отсортированном массиве. Например, если для элемента количество таких элементов будет , то будет занимать -ю позицию в отсортированном массиве. Если элементы могут иметь одинаковые значения, то необходимо модифицировать алгоритм, так как нельзя разместить все такие элементы в одну позицию.
Реализация
В этом варианте помимо входного массива потребуется два вспомогательных массива — для счётчика и для отсортированного массива. Сначала следует заполнить массив нулями, и, пройдя по массиву , записать количество чисел равных в массив (строки 1 - 4). Далее подсчитывается число элементов меньше или равных текущему (строки 5 - 6). На последнем шаге алгоритма читается входной массив с конца, а в массив записываются элементы на те позиции, где они должны стоять; эта информация хранится в массиве (строки 7 - 9). Алгоритм устойчив. Устойчивость может потребоваться при сортировке сложных структур данных.
StableCountingSort
for number = 0 to k - 1
C[number] = 0;
for i = 0 to length[A] - 1
C[A[i]] = C[A[i]] + 1;
for number = 1 to k - 1
C[j] = C[j] + C[j - 1];
for i = length[A] - 1 to 0
B[C[A[i]]] = A[i];
C[A[i]] = C[A[i]] - 1;
Обобщение на произвольный целочисленный диапазон
Если диапазон значений (минимимум и максимум) заранее не известен, можно найти их с помощью линейного поиска, что не повлияет на асимптотику алгоритма. При работе с массивом из необходимо вычитать минимум, а при обратной записи прибавлять.
Анализ
В первом алгоритме первые два цикла работают за и , соответственно; двойной цикл за . Во втором алгоритме циклы занимают , , и , соответственно. Итого оба алгоритма имеют линейную временную трудоёмкость . Используемая память в первом алгоритме равна , а во втором .
Использование сортировки подсчётом целесообразно, когда диапазон возможных значений входных данных достаточно мал по сравнению с количеством элементов в сортируемом множестве, например, если и все элементы натуральные числа меньшие , то время работы алгоритма равно . Эффективность алгоритма падает, когда необходимо сортировать различные элементы, попавшие в одну ячейку.
Сортировка сложных объектов
Постановка задачи
Иногда бывает очень желательно применить быстрый алгоритм сортировки подсчетом для упорядочивания набора каких-либо "сложных" данных. Под "сложными объектами" здесь подразумеваются структуры, содержащие в себе несколько полей. Одно из них мы выделим и назовем ключом, сортировка будет идти именно по нему (предполагается, что значения, принимаемые ключом — целые числа в диапазоне от до ).
Мы не сможем использовать здесь в точности тот же алгоритм, что и для сортировки подсчетом обычных целых чисел, потому что в наборе могут быть различные структуры, имеющие одинаковые ключи. Существует два способа справиться с этой проблемой — использовать списки для хранения структур в отсортированном массиве или заранее посчитать количество структур с одинаковыми ключами для каждого значения ключа.
Подсчет числа различных ключей
Описание
Исходная последовательность из структур хранится в массиве , а отсортированная — в массиве того же размера. Кроме того, используется вспомогательный массив с индексами от до .
Идея алгоритма состоит в предварительном подсчете количества элементов с различными ключами в исходном массиве и разделении результирующего массива на части соответствующей длины (будем называть их блоками). Затем при повторном проходе исходного массива каждый его элемент копируется в специально отведенный его ключу блок, в первую свободную ячейку. Это осуществляется с помощью массива индексов , в котором хранятся индексы начала блоков для различных ключей. — индекс в результирующем массиве, соответствующий первому элементу блока для ключа .
- Пройдем по исходному массиву и запишем в количество структур, ключ которых равен .
- Мысленно разобьем массив на блоков, длина каждого из которых равна соответственно , , ..., .
- Теперь массив нам больше не нужен. Превратим его в массив, хранящий в сумму элементов от до старого массива .
- Теперь "сдвинем" массив на элемент вперед: в новом массиве , а для , где — старый массив .
Это можно сделать за один проход по массиву , причем одновременно с предыдущим шагом.
После этого действия в массиве будут хранится индексы массива . указывает на начало блока в , соответствующего ключу .
- Произведем саму сортировку. Еще раз пройдем по исходному массиву и для всех будем помещать структуру в массив на место , а затем увеличивать на . Здесь — это ключ структуры, находящейся в массиве на -том месте.
Таким образом после завершения алгоритма в будет содержаться исходная последовательность в отсортированном виде (так как блоки расположены по возрастанию соответствующих ключей).
Стоит также отметить, что эта сортировка является устойчивой, так как два элемента с одинаковыми ключами будут добавлены в том же порядке, в каком просматривались в исходном массиве .
Псевдокод
Здесь и — массивы структур размера , с индексами от до . — целочисленный массив размера , с индексами от до , где — количество различных ключей.
ComplexCountingSort
for i = 0 to k - 1
P[i] = 0;
for i = 0 to length[A] - 1
P[A[i].key] = P[A[i].key] + 1;
carry = 0;
for i = 0 to k - 1
temporary = P[i];
P[i] = carry;
carry = carry + temporary;
for i = 0 to length[A] - 1
B[P[A[i].key]] = A[i];
P[A[i].key] = P[A[i].key] + 1;
Здесь шаги 3 и 4 из описания объединены в один цикл. Обратите внимание, что в последнем цикле инструкцией
B[P[A[i].key]] = A[i];
копируется структура целиком, а не только её ключ.
Анализ
Весь алгоритм состоит из двух проходов по массиву размера и одного прохода по массиву размера .
Его трудоемкость, таким образом, равна . На практике сортировку подсчетом имеет смысл применять, если , поэтому можно считать время работы алгоритма равным .
Как и в обычной сортировке подсчетом, требуется дополнительной памяти — на хранение массива размера и массива размера .
Источники
- Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. «Алгоритмы. Построение и анализ» — «Вильямс», 2011 г. — 1296 стр. — ISBN 978-5-8459-0857-5, 5-8459-0857-4, 0-07-013151-1
- Сортировка подсчетом — Википедия
- Wikipedia — Counting sort




