Сортировка подсчётом — различия между версиями
Nechaev (обсуждение | вклад) (→Сортировка целых чисел) |
м (rollbackEdits.php mass rollback) |
||
(не показаны 24 промежуточные версии 5 участников) | |||
Строка 1: | Строка 1: | ||
− | '''Сортировка подсчётом''' {{---}} алгоритм сортировки целых чисел в диапазоне от <tex>0</tex> до некоторой константы <tex>k</tex> или сложных объектов, работающий за линейное время. | + | '''Сортировка подсчётом''' (англ. ''counting sort'') {{---}} алгоритм сортировки целых чисел в диапазоне от <tex>0</tex> до некоторой константы <tex>k</tex> или сложных объектов, работающий за линейное время. |
== Сортировка целых чисел == | == Сортировка целых чисел == | ||
Это простейший вариант алгоритма. | Это простейший вариант алгоритма. | ||
=== Описание === | === Описание === | ||
− | Исходная последовательность чисел длины <tex>n</tex>, а в конце отсортированная, хранится в массиве <tex>A</tex>. Также используется вспомогательный массив <tex>C</tex> с индексами от <tex>0</tex> до <tex>k - 1</tex>, изначально заполняемый нулями. | + | Исходная последовательность чисел длины <tex>n</tex>, а в конце отсортированная, хранится в массиве <tex>A</tex>. Также используется вспомогательный массив <tex>C</tex> с индексами от <tex>0</tex> до <tex>\mathrm k - 1</tex>, изначально заполняемый нулями. |
− | Последовательно пройдём по массиву <tex>A</tex> и запишем в <tex>C[i]</tex> количество чисел, равных <tex>i</tex>. | + | * Последовательно пройдём по массиву <tex>A</tex> и запишем в <tex>C[i]</tex> количество чисел, равных <tex>i</tex>. |
− | Теперь достаточно пройти по массиву <tex>C</tex> и для каждого <tex>number \in \{0, ..., k - 1\}</tex> в массив <tex>A</tex> последовательно записать число <tex>number\</tex> <tex> C[number]</tex> раз. | + | * Теперь достаточно пройти по массиву <tex>C</tex> и для каждого <tex>number \in \{0, ..., \mathrm k - 1\}</tex> в массив <tex>A</tex> последовательно записать число <tex>number\</tex> <tex> C[number]</tex> раз. |
=== Псевдокод === | === Псевдокод === | ||
<code> | <code> | ||
− | + | '''function''' simpleCountingSort(A: '''int[n]'''): | |
− | for number = 0 to k - 1 | + | '''for''' number = 0 '''to''' k - 1 |
− | C[number] = 0 | + | C[number] = 0 |
− | + | '''for''' i = 0 '''to''' n - 1 | |
− | for i = 0 to | + | C[A[i]] = C[A[i]] + 1; |
− | C[A[i]] = C[A[i]] + 1; | ||
− | |||
pos = 0; | pos = 0; | ||
− | for number = 0 to k - 1 | + | '''for''' number = 0 '''to''' k - 1 |
− | for i = 0 to C[ | + | '''for''' i = 0 '''to''' C[number] - 1 |
A[pos] = number; | A[pos] = number; | ||
pos = pos + 1; | pos = pos + 1; | ||
Строка 26: | Строка 24: | ||
== Сортировка сложных объектов == | == Сортировка сложных объектов == | ||
− | Сортировка целых чисел за линейное время это хорошо, но недостаточно. Иногда бывает очень желательно применить быстрый алгоритм [[#Сортировка целых чисел|сортировки подсчетом]] для упорядочивания набора каких-либо "сложных" данных. Под "сложными объектами" здесь подразумеваются структуры, содержащие в себе несколько полей. Одно из них мы выделим и назовем ключом, сортировка будет идти именно по нему (предполагается, что значения, принимаемые ключом {{---}} целые числа в диапазоне от <tex>0</tex> до <tex>k-1</tex>). | + | Сортировка целых чисел за линейное время это хорошо, но недостаточно. Иногда бывает очень желательно применить быстрый алгоритм [[#Сортировка целых чисел|сортировки подсчетом]] для упорядочивания набора каких-либо "сложных" данных. Под "сложными объектами" здесь подразумеваются структуры, содержащие в себе несколько полей. Одно из них мы выделим и назовем ключом, сортировка будет идти именно по нему (предполагается, что значения, принимаемые ключом {{---}} целые числа в диапазоне от <tex>0</tex> до <tex>\mathrm k-1</tex>). |
Мы не сможем использовать здесь в точности тот же алгоритм, что и для сортировки подсчетом обычных целых чисел, потому что в наборе могут быть различные структуры, имеющие одинаковые ключи. Существует два способа справиться с этой проблемой {{---}} использовать списки для хранения структур в отсортированном массиве или заранее посчитать количество структур с одинаковыми ключами для каждого значения ключа. | Мы не сможем использовать здесь в точности тот же алгоритм, что и для сортировки подсчетом обычных целых чисел, потому что в наборе могут быть различные структуры, имеющие одинаковые ключи. Существует два способа справиться с этой проблемой {{---}} использовать списки для хранения структур в отсортированном массиве или заранее посчитать количество структур с одинаковыми ключами для каждого значения ключа. | ||
=== Описание === | === Описание === | ||
− | Исходная последовательность из <tex>n</tex> структур хранится в массиве <tex>A</tex>, а отсортированная {{---}} в массиве <tex>B</tex> того же размера. Кроме того, используется вспомогательный массив <tex>P</tex> с индексами от <tex>0</tex> до <tex>k-1</tex>. | + | Исходная последовательность из <tex>n</tex> структур хранится в массиве <tex>A</tex>, а отсортированная {{---}} в массиве <tex>B</tex> того же размера. Кроме того, используется вспомогательный массив <tex>P</tex> с индексами от <tex>0</tex> до <tex>\mathrm k-1</tex>. |
Идея алгоритма состоит в предварительном подсчете количества элементов с различными ключами в исходном массиве и разделении результирующего массива на части соответствующей длины (будем называть их блоками). Затем при повторном проходе исходного массива каждый его элемент копируется в специально отведенный его ключу блок, в первую свободную ячейку. Это осуществляется с помощью массива индексов <tex>P</tex>, в котором хранятся индексы начала блоков для различных ключей. <tex>P[key]</tex> {{---}} индекс в результирующем массиве, соответствующий первому элементу блока для ключа <tex>key</tex>. | Идея алгоритма состоит в предварительном подсчете количества элементов с различными ключами в исходном массиве и разделении результирующего массива на части соответствующей длины (будем называть их блоками). Затем при повторном проходе исходного массива каждый его элемент копируется в специально отведенный его ключу блок, в первую свободную ячейку. Это осуществляется с помощью массива индексов <tex>P</tex>, в котором хранятся индексы начала блоков для различных ключей. <tex>P[key]</tex> {{---}} индекс в результирующем массиве, соответствующий первому элементу блока для ключа <tex>key</tex>. | ||
Строка 38: | Строка 36: | ||
[[Файл:Building_P.png]] | [[Файл:Building_P.png]] | ||
− | * Мысленно разобьем массив <tex>B</tex> на <tex>k</tex> блоков, длина каждого из которых равна соответственно <tex>P[ | + | * Мысленно разобьем массив <tex>B</tex> на <tex>k</tex> блоков, длина каждого из которых равна соответственно <tex>P[0]</tex>, <tex>P[1]</tex>, ..., <tex>P[k]</tex>. |
[[Файл:Splitting_B_w_colors.png]] | [[Файл:Splitting_B_w_colors.png]] | ||
Строка 57: | Строка 55: | ||
Здесь <tex>A</tex> и <tex>B</tex> {{---}} массивы структур размера <tex>n</tex>, с индексами от <tex>0</tex> до <tex>n-1</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> {{---}} количество различных ключей. | <tex>P</tex> {{---}} целочисленный массив размера <tex>k</tex>, с индексами от <tex>0</tex> до <tex>k-1</tex>, где <tex>k</tex> {{---}} количество различных ключей. | ||
− | + | <code> | |
− | for i = 0 to k - 1 | + | '''function''' complexCountingSort(A: '''int[n]''', B: '''int[n]'''): |
− | P[i] = 0; | + | '''for''' i = 0 '''to''' k - 1 |
− | + | P[i] = 0; | |
− | for i = 0 to length[A] - 1 | + | '''for''' i = 0 '''to''' length[A] - 1 |
− | P[A[i].key] = P[A[i].key] + 1; | + | P[A[i].key] = P[A[i].key] + 1; |
− | |||
carry = 0; | carry = 0; | ||
− | for i = 0 to k - 1 | + | '''for''' i = 0 '''to''' k - 1 |
temporary = P[i]; | temporary = P[i]; | ||
P[i] = carry; | P[i] = carry; | ||
− | carry = carry + temporary; | + | carry = carry + temporary; |
− | + | '''for''' i = 0 '''to''' length[A] - 1 | |
− | |||
B[P[A[i].key]] = A[i]; | B[P[A[i].key]] = A[i]; | ||
P[A[i].key] = P[A[i].key] + 1; | P[A[i].key] = P[A[i].key] + 1; | ||
− | + | </code> | |
Здесь шаги 3 и 4 из описания объединены в один цикл. | Здесь шаги 3 и 4 из описания объединены в один цикл. | ||
Обратите внимание, что в последнем цикле инструкцией | Обратите внимание, что в последнем цикле инструкцией | ||
Строка 80: | Строка 76: | ||
== Анализ == | == Анализ == | ||
− | |||
В первом алгоритме первые два цикла работают за <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно; двойной цикл за <tex>\Theta(n + k)</tex>. Алгоритм имеет линейную временную трудоёмкость <tex>\Theta(n + k)</tex>. Используемая дополнительная память равна <tex>\Theta(k)</tex>. | В первом алгоритме первые два цикла работают за <tex>\Theta(k)</tex> и <tex>\Theta(n)</tex>, соответственно; двойной цикл за <tex>\Theta(n + k)</tex>. Алгоритм имеет линейную временную трудоёмкость <tex>\Theta(n + k)</tex>. Используемая дополнительная память равна <tex>\Theta(k)</tex>. | ||
Строка 86: | Строка 81: | ||
Его трудоемкость, таким образом, равна <tex>\Theta(n + k)</tex>. На практике сортировку подсчетом имеет смысл применять, если <tex>k = O(n)</tex>, поэтому можно считать время работы алгоритма равным <tex>\Theta(n)</tex>. <br> | Его трудоемкость, таким образом, равна <tex>\Theta(n + k)</tex>. На практике сортировку подсчетом имеет смысл применять, если <tex>k = O(n)</tex>, поэтому можно считать время работы алгоритма равным <tex>\Theta(n)</tex>. <br> | ||
Как и в обычной сортировке подсчетом, требуется <tex>\Theta(n + k)</tex> дополнительной памяти {{---}} на хранение массива <tex>B</tex> размера <tex>n</tex> и массива <tex>P</tex> размера <tex>k</tex>. | Как и в обычной сортировке подсчетом, требуется <tex>\Theta(n + k)</tex> дополнительной памяти {{---}} на хранение массива <tex>B</tex> размера <tex>n</tex> и массива <tex>P</tex> размера <tex>k</tex>. | ||
+ | |||
+ | Алгоритм работает за линейное время, но является псевдополиномиальным. | ||
== Поиск диапазона ключей == | == Поиск диапазона ключей == | ||
Строка 91: | Строка 88: | ||
Нужно учитывать, что минимум может быть отрицательным, в то время как в массиве <tex>P</tex> индексы от <tex>0</tex> до <tex>k-1</tex>. Поэтому при работе с массивом <tex>P</tex> из исходного <tex>A[i]</tex> необходимо вычитать минимум, а при обратной записи в <tex>B[i]</tex> прибавлять его. | Нужно учитывать, что минимум может быть отрицательным, в то время как в массиве <tex>P</tex> индексы от <tex>0</tex> до <tex>k-1</tex>. Поэтому при работе с массивом <tex>P</tex> из исходного <tex>A[i]</tex> необходимо вычитать минимум, а при обратной записи в <tex>B[i]</tex> прибавлять его. | ||
− | == Источники == | + | == Источники информации == |
− | * [http://ru.wikipedia.org/wiki/Сортировка_подсчётом | + | * [http://ru.wikipedia.org/wiki/Сортировка_подсчётом Сортировка подсчетом {{---}} Википедия] |
− | * [http://en.wikipedia.org/wiki/Counting_sort | + | * [http://en.wikipedia.org/wiki/Counting_sort Counting sort {{---}} Wikipedia] |
* Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 224{{---}}226. | * Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 224{{---}}226. | ||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Сортировки]] | [[Категория: Сортировки]] | ||
+ | [[Категория: Другие сортировки]] |
Текущая версия на 19:33, 4 сентября 2022
Сортировка подсчётом (англ. counting sort) — алгоритм сортировки целых чисел в диапазоне от
до некоторой константы или сложных объектов, работающий за линейное время.Содержание
Сортировка целых чисел
Это простейший вариант алгоритма.
Описание
Исходная последовательность чисел длины
, а в конце отсортированная, хранится в массиве . Также используется вспомогательный массив с индексами от до , изначально заполняемый нулями.- Последовательно пройдём по массиву и запишем в количество чисел, равных .
- Теперь достаточно пройти по массиву и для каждого в массив последовательно записать число раз.
Псевдокод
function simpleCountingSort(A: int[n]): for number = 0 to k - 1 C[number] = 0 for i = 0 to n - 1 C[A[i]] = C[A[i]] + 1; pos = 0; for number = 0 to k - 1 for i = 0 to C[number] - 1 A[pos] = number; pos = pos + 1;
Сортировка сложных объектов
Сортировка целых чисел за линейное время это хорошо, но недостаточно. Иногда бывает очень желательно применить быстрый алгоритм сортировки подсчетом для упорядочивания набора каких-либо "сложных" данных. Под "сложными объектами" здесь подразумеваются структуры, содержащие в себе несколько полей. Одно из них мы выделим и назовем ключом, сортировка будет идти именно по нему (предполагается, что значения, принимаемые ключом — целые числа в диапазоне от до ).
Мы не сможем использовать здесь в точности тот же алгоритм, что и для сортировки подсчетом обычных целых чисел, потому что в наборе могут быть различные структуры, имеющие одинаковые ключи. Существует два способа справиться с этой проблемой — использовать списки для хранения структур в отсортированном массиве или заранее посчитать количество структур с одинаковыми ключами для каждого значения ключа.
Описание
Исходная последовательность из
структур хранится в массиве , а отсортированная — в массиве того же размера. Кроме того, используется вспомогательный массив с индексами от до .Идея алгоритма состоит в предварительном подсчете количества элементов с различными ключами в исходном массиве и разделении результирующего массива на части соответствующей длины (будем называть их блоками). Затем при повторном проходе исходного массива каждый его элемент копируется в специально отведенный его ключу блок, в первую свободную ячейку. Это осуществляется с помощью массива индексов
, в котором хранятся индексы начала блоков для различных ключей. — индекс в результирующем массиве, соответствующий первому элементу блока для ключа .- Пройдем по исходному массиву и запишем в количество структур, ключ которых равен .
- Мысленно разобьем массив на блоков, длина каждого из которых равна соответственно , , ..., .
- Теперь массив нам больше не нужен. Превратим его в массив, хранящий в сумму элементов от до старого массива .
- Теперь "сдвинем" массив
Это можно сделать за один проход по массиву , причем одновременно с предыдущим шагом.
После этого действия в массиве будут хранится индексы массива . указывает на начало блока в , соответствующего ключу . на элемент вперед: в новом массиве , а для , где — старый массив .
- Произведем саму сортировку. Еще раз пройдем по исходному массиву и для всех будем помещать структуру в массив на место , а затем увеличивать на . Здесь — это ключ структуры, находящейся в массиве на -том месте.
Таким образом после завершения алгоритма в
будет содержаться исходная последовательность в отсортированном виде (так как блоки расположены по возрастанию соответствующих ключей).Стоит также отметить, что эта сортировка является устойчивой, так как два элемента с одинаковыми ключами будут добавлены в том же порядке, в каком просматривались в исходном массиве цифровая сортировка.
. Благодаря этому свойству существуетПсевдокод
Здесь
function complexCountingSort(A: int[n], B: int[n]): 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];
копируется структура
целиком, а не только её ключ.Анализ
В первом алгоритме первые два цикла работают за
и , соответственно; двойной цикл за . Алгоритм имеет линейную временную трудоёмкость . Используемая дополнительная память равна .Второй алгоритм состоит из двух проходов по массиву
Как и в обычной сортировке подсчетом, требуется дополнительной памяти — на хранение массива размера и массива размера .
Алгоритм работает за линейное время, но является псевдополиномиальным.
Поиск диапазона ключей
Если диапазон значений не известен заранее, то его можно найти с помощью линейного поиска минимума и максимума в исходном массиве, что не повлияет на асимптотику алгоритма.
Нужно учитывать, что минимум может быть отрицательным, в то время как в массиве индексы от до . Поэтому при работе с массивом из исходного необходимо вычитать минимум, а при обратной записи в прибавлять его.
Источники информации
- Сортировка подсчетом — Википедия
- Counting sort — Wikipedia
- Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 224—226.