Сортировка выбора выбора v. Сортировка выбора

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

Каковы реальные различия между сортировкой выбора и заменой выбора?

Где я могу найти реализацию замены выбора сортировки (или руководство по ее написанию)?

Какие характеристики сортировки с заменой выбора делают ее более желательной, чем другие алгоритмы сортировки?

Известен ли этот алгоритм под другими именами?

2 ответа

Решение

Я не видел этот алгоритм, описанный более подробно ранее, и я основываю свой анализ на том, что я собрал, прочитав этот набор примечаний к лекции.

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

Выбор сортировки

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

  • Найдите наименьший элемент и поменяйте его местами в позиции 0 массива.
  • Найдите второй наименьший элемент и поменяйте его местами в позиции 1 массива.
  • Найдите третий самый маленький элемент и поменяйте его местами в позиции 2 массива
  • ...
  • Найдите n-й наименьший элемент и поменяйте его местами в позиции n - 1 массива.

Это предполагает, что массив может храниться полностью в памяти, и в этом случае этот алгоритм выполняется за время Θ (n2). Это не очень быстро, и для больших наборов данных не рекомендуется.

Выбор замены Сортировка

Этот алгоритм был описан в 1965 году Дональдом Кнутом, поэтому он предназначен для работы в совершенно иной вычислительной среде, чем та, к которой мы привыкли. Компьютеры имеют очень мало памяти (как правило, фиксированное количество регистров), но имеют доступ к большим внешним дискам. Обычно строят алгоритмы, которые загружают некоторые значения в регистры, обрабатывают их там, а затем сбрасывают прямо обратно во внешнее хранилище. (Интересно, что это похоже на то, как работают процессоры прямо сейчас, за исключением основной памяти вместо внешнего хранилища).

Давайте предположим, что у нас достаточно места в памяти для хранения двух массивов: первый массив Values размером n, который может содержать кучу значений и второй массив Active размера n, который содержит логические значения. Мы попытаемся взять большой входной поток несортированных значений и приложим все усилия, чтобы отсортировать их, учитывая, что у нас достаточно места в памяти для хранения Active а также Values массивы, плюс несколько дополнительных переменных для места хранения.

Идея алгоритма заключается в следующем. Сначала загрузите n значения из внешнего источника, содержащие несортированную последовательность непосредственно в Values массив. Затем установите все Active значения для true, Например, если n = 4мы могли бы иметь эту настройку:

Values:    4    1    0    3
Active:    Yes  Yes  Yes  Yes

Алгоритм сортировки выбора замены работает путем многократного поиска наименьшего значения в Values массив и запись его в выходной поток. В этом случае мы начнем с поиска значения 0 и записи его в поток. Это дает

Values:    4    1         3
Active:    Yes  Yes  Yes  Yes

Output:    0

Теперь у нас есть пустое место в нашем Values массив, так что мы можем получить другое значение из внешнего источника. Давайте предположим, что мы получаем 2. В этом случае у нас есть эта настройка:

Values:    4    1    2    3
Active:    Yes  Yes  Yes  Yes

Output:    0

Обратите внимание, что поскольку 2 > 0 и 0 были наименьшим элементом, мы гарантируем, что когда мы записали 0 в вывод, 2 не должно было предшествовать ему. Это хорошо. Поэтому мы переходим к следующему шагу алгоритма и снова находим здесь наименьший элемент. Это 1, поэтому мы отправляем его на устройство вывода:

Values:    4         2    3
Active:    Yes  Yes  Yes  Yes

Output:    0  1

Теперь прочитайте другое значение из внешнего источника:

Values:    4    -1   2    3
Active:    Yes  Yes  Yes  Yes

Output:    0  1

Теперь у нас проблемы. Это новое значение (-1) ниже, чем 1, что означает, что если мы действительно хотим, чтобы это значение входило в вывод в отсортированном порядке, оно должно предшествовать 1. Однако у нас недостаточно памяти, чтобы перечитать с Устройство вывода и исправить его. Вместо этого мы собираемся сделать следующее. А пока давайте сохраним -1 в памяти. Мы сделаем все возможное, чтобы отсортировать оставшиеся элементы, но когда мы закончим, мы выполним вторую итерацию генерации отсортированной последовательности и поместим -1 в эту последовательность. Другими словами, вместо того, чтобы производить одну отсортированную последовательность, мы собираемся создать две.

Чтобы указать в памяти, что мы еще не хотим выписывать -1, мы отметим слот с -1 как неактивный. Это показано здесь:

Values:    4    -1   2    3
Active:    Yes  NO   Yes  Yes

Output:    0  1

Отныне мы будем просто делать вид, что -1 здесь нет.

Давайте продолжим дальше. Теперь мы находим наименьшее значение в памяти, которое все еще активно (2), и записываем его на устройство:

Values:    4    -1        3
Active:    Yes  NO   Yes  Yes

Output:    0  1  2

Теперь мы извлекаем следующее значение из устройства ввода. Давайте предположим, что это 7:

Values:    4    -1   7    3
Active:    Yes  NO   Yes  Yes

Output:    0  1  2

Поскольку 7 > 2, он идет после 2 в выводе, поэтому мы ничего не делаем.

На следующей итерации мы находим наименьшее активное значение (3) и записываем его:

Values:    4    -1   7    
Active:    Yes  NO   Yes  Yes

Output:    0  1  2  3

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

Values:    4    -1   7    
Active:    Yes  NO   Yes  Yes

Output:    0  1  2  3  3

Теперь мы извлекаем следующее значение из устройства ввода. Предположим, что это 2. В этом случае, как и раньше, мы знаем, что 2 должно предшествовать 3. Так же, как и ранее -1, это означает, что нам нужно пока удерживать 2 в памяти; мы выпишем это позже. Теперь наша установка выглядит так:

Values:    4    -1   7    2
Active:    Yes  NO   Yes  NO

Output:    0  1  2  3  3

Теперь мы находим наименьшее активное значение (4) и записываем его в устройство вывода:

Values:         -1   7    2
Active:    Yes  NO   Yes  NO

Output:    0  1  2  3  3  4

Предположим, что теперь мы читаем в 1 как наш следующий ввод. Поэтому мы помещаем это в Values, но отметьте это как неактивное:

Values:    1    -1   7    2
Active:    NO   NO   Yes  NO

Output:    0  1  2  3  3  4

Есть только одно активное значение, которое равно 7, поэтому мы запишем его:

Values:    1    -1        2
Active:    NO   NO   Yes  NO

Output:    0  1  2  3  3  4  7

Давайте предположим, что теперь мы читаем 5. В этом случае, как и раньше, мы сохраняем его, но помечаем слот как неактивный:

Values:    1    -1   5    2
Active:    NO   NO   NO   NO

Output:    0  1  2  3  3  4  7

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

Values:    1    -1   5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7

-1 - наименьшее значение, поэтому мы выводим его:

Values:    1         5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7  -1

Предположим, что мы читаем 3. -1 < 3, поэтому мы загружаем его в Values массив.

Values:    1    3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1

1 - наименьшее значение здесь, поэтому мы удалим его:

Values:         3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1

Давайте предположим, что у нас нет входных значений. Мы отметим этот слот как выполненный:

Values:    ---  3    5    2
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1

2 идет дальше:

Values:    ---  3    5    ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2

Тогда 3:

Values:    ---  ---  5    ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2  3

Наконец, 5:

Values:    ---  ---  ---  ---
Active:    Yes  Yes  Yes  Yes

Output:    0  1  2  3  3  4  7 -1  1  2  3  5

И мы сделали! Обратите внимание, что результирующая последовательность не отсортирована, но она намного лучше, чем раньше. Теперь он состоит из двух нитей, которые находятся в отсортированном порядке. Объединение их вместе (так же, как мы выполняли бы слияние для сортировки слиянием), приведет к сортировке полученного массива. Этот алгоритм мог потенциально произвести гораздо больше нитей, но поскольку наш выборочный ввод был небольшим, у нас было только два.


Так как быстро это? Итак, каждая итерация цикла выполняет в общей сложности не более n сравнений (в памяти), одного чтения и одной записи. Таким образом, если в потоке N полных значений, алгоритм выполняет O(nN) сравнений и O(N) операций с памятью. Если операции с памятью дороги, это не так уж и плохо, хотя в конце вам понадобится второй проход, чтобы объединить все воедино.

В псевдокоде алгоритм выглядит так:

Make Values an array of n elements.
Make Active an array of n booleans, all initially true.

Read n values from memory into Values.
Until no values are left to process:
    Find the smallest value that is still active.
    Write it to the output device.
    Read from the input device into the slot where the old element was.
    If it was smaller than the old element, mark the old slot inactive.
    If all slots are inactive, mark them all active.

Я был бы шокирован, если бы была какая-то причина, чтобы кодировать этот алгоритм сейчас. Это имело смысл много десятилетий назад, когда память была действительно очень маленькой. В настоящее время доступны гораздо лучшие алгоритмы внешней сортировки, и они почти наверняка превзойдут этот алгоритм.

Надеюсь это поможет!

Заменяющая сортировка по-прежнему используется для внешней сортировки, поскольку она будет производить наименьшее количество строк для объединения, а объединение является самой дорогой частью сортировки. Используемый подход немного сложнее, чем предоставленный превосходный пример templatetypedef, но в основном он буферизует множество записей, сортирует их, собирает записи последовательности, такие как -1 1 и т. Д., И удерживает их в буфере, записывает младшую часть последовательности, заполняет пустое пространство, снова сортирует, объединяет из буфера любые записи, которые вписываются, и повторяется до тех пор, пока последовательность не может быть продолжена, затем выдает последовательность, удаляет буферизованные записи последовательности и новые входные записи, начиная со следующей строки. Повторите до конца ввода.

В некоторых приложениях объединение не требуется, поскольку сортировочная замена сметает записи последовательности и затем вставляет их позже в нужное место. Это стало неожиданностью для многих коммерческих клиентов в 1964 году, когда Honeywell и IBM представили этот вид.

Другие вопросы по тегам