Быстрая сортировка

Из Википедии, бесплатной энциклопедии

Быстрая сортировка
Анимированная визуализация алгоритма быстрой сортировки. Горизонтальные линии — это значения поворота.
Сорт Алгоритм сортировки
Худшая производительность
Лучшая производительность (простой раздел)
или (трехстороннее разделение и равные ключи)
Средняя производительность
Наихудшая пространственная сложность вспомогательный (наивный)
вспомогательный (Хоар, 1962)
Оптимальный Нет

Быстрая сортировка общего назначения — это эффективный алгоритм сортировки . Быстрая сортировка была разработана британским ученым-компьютерщиком Тони Хоаром в 1959 году. [1] и опубликовано в 1961 г. [2] Это до сих пор широко используемый алгоритм сортировки. В целом, это немного быстрее, чем сортировка слиянием и пирамидальная сортировка для рандомизированных данных, особенно в больших распределениях. [3]

Быстрая сортировка — это алгоритм «разделяй и властвуй» . Он работает путем выбора «поворотного» элемента из массива и разделения других элементов на два подмассива в зависимости от того, меньше они или больше, чем опорный элемент. По этой причине ее иногда называют сортировкой по обмену разделами . [4] Затем подмассивы сортируются рекурсивно . Это можно сделать на месте , требуя небольшого дополнительного объема памяти для выполнения сортировки.

Быстрая сортировка — это сортировка сравнения отношение «меньше» (формально, общий порядок , что означает, что она может сортировать элементы любого типа, для которых определено ). Это сортировка на основе сравнения, поскольку элементы a и b меняются местами только в том случае, если их относительный порядок был получен при транзитивном замыкании предыдущих результатов сравнения. Большинство реализаций быстрой сортировки нестабильны , а это означает, что относительный порядок одинаковых элементов сортировки не сохраняется.

Математический анализ быстрой сортировки показывает, что в среднем алгоритм занимает сравнения для сортировки n элементов. В худшем случае это делает сравнения.

История [ править ]

Алгоритм быстрой сортировки был разработан в 1959 году Тони Хоаром, когда он был приглашенным студентом Московского государственного университета . В то время Хоар работал над проектом машинного перевода для Национальной физической лаборатории . В процессе перевода ему нужно было отсортировать слова в русских предложениях, прежде чем искать их в русско-английском словаре, который был записан на магнитной ленте в алфавитном порядке . [5] Поняв, что его первая идея — сортировка вставками — будет медленной, он придумал новую идею. Он написал часть раздела в Mercury Autocode , но у него возникли проблемы со списком несортированных сегментов. По возвращении в Англию его попросили написать код для Shellsort . Хоар упомянул своему боссу, что знает более быстрый алгоритм, и тот поспорил на шесть пенсов , что он этого не знает. Его босс в конце концов признал, что он проиграл пари. Хоар опубликовал статью о своем алгоритме в The Computer Journal Volume 5, Issue 1, 1962, страницы 10–16 . Позже Хоар узнал об АЛГОЛе и его способности выполнять рекурсию, что позволило ему опубликовать улучшенную версию алгоритма АЛГОЛА в журнале Communications of the Association for Computing Machinery , ведущем журнале по компьютерным наукам того времени. [2] [6] Код АЛГОЛА опубликован в журналах Communications of the ACM (CACM), том 4, выпуск 7 июля 1961 г., стр. 321. Алгоритм 63: разделение и Алгоритм 64: Быстрая сортировка .

Быстрая сортировка получила широкое распространение, появившись, например, в Unix как подпрограмма сортировки библиотеки по умолчанию. Следовательно, оно дало свое название подпрограмме библиотеки C. стандартной qсортировка [7] и в эталонной реализации Java .

Докторская диссертация Роберта Седжвика в 1975 году считается важной вехой в изучении быстрой сортировки, где он решил множество открытых проблем, связанных с анализом различных схем выбора опорных точек, включая Samplesort , адаптивное разделение Ван Эмдена. [8] а также получение ожидаемого количества сравнений и замен. [7] Джон Бентли и Дуг Макилрой в 1993 году включили различные улучшения для использования в библиотеках программирования, включая метод работы с равными элементами и схему поворота, известную как псевдомедиана из девяти, где выборка из девяти элементов делится на группы по три, а затем медиану. выбирается из трех медиан из трех групп. [7] Бентли описал другую, более простую и компактную схему разбиения в своей книге « Жемчужины программирования» , которую он приписал Нико Ломуто (бывшему итальянскому певцу 60-х годов). Позже Бентли написал, что он использовал версию Хоара в течение многих лет, но так и не понял ее, но версия Ломуто была достаточно простой, чтобы оказаться верной. [9] В том же эссе Бентли описал Quicksort как «самый красивый код, который я когда-либо писал». Схема разбиения Ломуто также была популяризирована учебником « Введение в алгоритмы», хотя она уступает схеме Хоара, поскольку в среднем делает в три раза больше перестановок и деградирует до O ( n 2 ) время выполнения, когда все элементы равны. [10] [ самостоятельно опубликованный источник? ] Макилрой в дальнейшем разработал AntiQuicksort ( aqsort ) в 1998 году, которая постоянно приводит даже его вариант быстрой сортировки 1993 года к квадратичному поведению, создавая состязательные данные на лету. [11]

Алгоритм [ править ]

Полный пример быстрой сортировки случайного набора чисел. Заштрихованный элемент — это ось. Он всегда выбирается в качестве последнего элемента раздела. Однако такой выбор всегда последнего элемента в разделе в качестве опорного приводит к низкой производительности ( O ( n 2 ) ) на уже отсортированных массивах или массивах одинаковых элементов. Поскольку подмассивы отсортированных/идентичных элементов часто возникают ближе к концу процедуры сортировки на большом наборе, версии алгоритма быстрой сортировки, в которых центральный элемент выбран в качестве среднего элемента, работают гораздо быстрее, чем алгоритм, описанный на этой диаграмме на большие наборы чисел.

Быстрая сортировка — это тип алгоритма «разделяй и властвуй» для сортировки массива, основанный на процедуре разделения; Детали этого разделения могут несколько различаться, так что быстрая сортировка на самом деле представляет собой семейство тесно связанных алгоритмов. Применительно к диапазону, состоящему как минимум из двух элементов, разделение приводит к разделению на два последовательных непустых поддиапазона таким образом, что ни один элемент первого поддиапазона не превышает любой элемент второго поддиапазона. После применения этого разделения быстрая сортировка затем рекурсивно сортирует поддиапазоны, возможно, после исключения из них элемента в точке разделения, который, как известно, в этот момент уже находится в своем конечном местоположении. Из-за своей рекурсивной природы быстрая сортировка (как и процедура разделения) должна быть сформулирована так, чтобы ее можно было вызывать для диапазона внутри более крупного массива, даже если конечной целью является сортировка всего массива. Шаги быстрой сортировки на месте :

  1. Если в диапазоне меньше двух элементов, вернитесь немедленно, поскольку делать нечего. Возможно, для других очень коротких длин применяется специальный метод сортировки, а оставшаяся часть этих шагов пропускается.
  2. В противном случае выберите значение, называемое опорной точкой , которое встречается в диапазоне (точный способ выбора зависит от процедуры разделения и может включать случайность).
  3. Разделите диапазон: измените порядок его элементов, определяя при этом точку разделения, так, чтобы все элементы со значениями меньшими, чем ось, шли до разделения, а все элементы со значениями, большими, чем ось, шли после него; элементы, равные опорной точке, могут идти в любом направлении. Поскольку присутствует по крайней мере один экземпляр опорной точки, большинство процедур разделения гарантируют, что значение, которое оказывается в точке деления, равно опорной точке и теперь находится в своем конечном положении (но завершение быстрой сортировки от этого не зависит, до тех пор, пока производятся поддиапазоны строго меньшие, чем исходные).
  4. Рекурсивно применить быструю сортировку к поддиапазону до точки деления и к поддиапазону после нее, по возможности исключая из обоих диапазонов элемент, равный опорной точке в точке деления. (Если разделение создает возможно больший поддиапазон вблизи границы, где известно, что все элементы равны опорной точке, их также можно исключить.)

Выбор процедуры разделения (включая выбор сводной точки) и другие детали, не полностью указанные выше, могут повлиять на производительность алгоритма, возможно, в значительной степени для конкретных входных массивов. Поэтому при обсуждении эффективности быстрой сортировки необходимо сначала указать эти варианты. Здесь мы упомянем два конкретных метода разделения.

Схема разделов Ломуто [ править ]

Эта схема приписывается Нико Ломуто и популяризируется Бентли в его книге « Жемчужины программирования». [12] и Кормен и др. в своей книге «Введение в алгоритмы» . [13] В большинстве формулировок эта схема выбирает в качестве опорного последний элемент массива. Алгоритм поддерживает индекс я , поскольку он сканирует массив, используя другой индекс j такой, что элементы в пройти через i-1 (включительно) меньше опорной точки, а элементы в я прошел j (включительно) равны опорной точке или превышают ее. Поскольку эта схема более компактна и проста для понимания, она часто используется во вводных материалах, хотя она менее эффективна, чем исходная схема Хоара, например, когда все элементы равны. [14] Сложность быстрой сортировки по этой схеме снижается до O ( n 2 ), когда массив уже в порядке, поскольку раздел является наихудшим из возможных. [10] Были предложены различные варианты повышения производительности, включая различные способы выбора опорной точки, работы с равными элементами, использования других алгоритмов сортировки, таких как сортировка вставками для небольших массивов и так далее. В псевдокоде — быстрая сортировка, которая сортирует элементы по пройти через hi (включительно) массива A можно выразить как: [13]

// Сортирует массив (часть массива), делит его на разделы, а затем сортирует их. 
 Алгоритм  быстрой сортировки (A, lo, hi)  is  
   // Убедитесь, что индексы находятся в правильном порядке, 
   если  lo >= hi ||   lo < 0  тогда  
      возвращаться 
    
    // Разделяем массив и получаем сводный индекс 
    p := раздел (A, вот, привет)  
      
    // Сортируем два раздела 
    быстрая сортировка(A, lo, p - 1)  // Левая часть опорной точки 
    быстрая сортировка(A, p + 1, hi)  // Правая часть сводной таблицы 

 // Делит массив на два раздела. 
 Алгоритм  раздела (A, lo, hi)  равен  
    Pivot := A[hi]  // Выбираем последний элемент в качестве опорного 

   // Временный индекс поворота 
    я := вот 

    for  j := lo  to  hi - 1  do  
     // Если текущий элемент меньше или равен опорному индексу 
     if  A[j] <= Pivot  then  
       // Поменяйте местами текущий элемент на элемент с временным опорным индексом 
        поменять местами A[i]  на  A[j] 
        // Перемещаем временный опорный индекс вперед 
        я := я + 1 

    // Меняем местами опорную точку с последним элементом 
    поменять местами A[i]  на  A[hi] 
    return  i  // сводный индекс 

Сортировка всего массива осуществляется путем быстрая сортировка(A, 0, длина(A) - 1) .

Схема разделов Хоара [ править ]

Анимированная демонстрация быстрой сортировки с использованием схемы разделов Хоара. Красные контуры показывают положения левого и правого указателей ( i и j соответственно), черные контуры показывают позиции отсортированных элементов, а закрашенный черный квадрат показывает значение, с которым сравнивается ( pivot).

Исходная схема секционирования, описанная Тони Хоаром, использует два указателя (индекса диапазона), которые начинаются с обоих концов разделяемого массива, затем движутся навстречу друг другу, пока не обнаружат инверсию: пара элементов, один из которых больше опорной точки. у первого указателя и на единицу меньше точки опоры у второго указателя; если в этот момент первый указатель все еще находится перед вторым, эти элементы находятся в неправильном порядке относительно друг друга, и затем они меняются местами. [15] После этого указатели перемещаются внутрь и поиск инверсии повторяется; когда в конечном итоге указатели пересекаются (первые точки следуют за вторыми), обмен не производится; найден действительный раздел с точкой разделения между скрещенными указателями (любые записи, которые могут находиться строго между скрещенными указателями, равны опорной точке и могут быть исключены из обоих сформированных поддиапазонов). При такой формулировке возможно, что один поддиапазон окажется всем исходным диапазоном, что помешает алгоритму продвигаться вперед. Поэтому Хоар предусматривает, что в конце поддиапазон, содержащий опорный элемент (который все еще находится в исходном положении), может быть уменьшен в размере путем исключения этого опорного элемента после (при необходимости) замены его на элемент поддиапазона, ближайший к разделение; таким образом, обеспечивается завершение быстрой сортировки.

Что касается этого исходного описания, реализации часто вносят незначительные, но важные изменения. Примечательно, что схема, представленная ниже, включает элементы, равные стержню среди кандидатов на инверсию (поэтому критерии «больше или равно» и «меньше или равно» используются вместо «больше» и «меньше» соответственно; поскольку в формулировке используется делать ... пока , а не повторять ... до тех пор , пока это не будет фактически отражено использованием операторов строгого сравнения [ нужны разъяснения ] ). Хотя нет причин обменивать элементы, равные опорной точке, это изменение позволяет опустить тесты самих указателей, которые в противном случае необходимы для обеспечения того, чтобы они не вышли за пределы диапазона. Действительно, поскольку в диапазоне присутствует по крайней мере один экземпляр опорного значения, первое перемещение любого указателя не может пройти через этот экземпляр, если используется инклюзивный тест; как только обмен выполнен, оба обмениваемых элемента теперь находятся строго впереди указателя, который их нашел, что предотвращает смещение этого указателя. (Последнее верно независимо от используемого теста, поэтому можно будет использовать инклюзивный тест только при поиске первой инверсии. Однако использование инклюзивного теста повсюду также гарантирует, что деление около середины будет найдено, когда все элементы в диапазоны равны, что дает важный выигрыш в эффективности сортировки массивов со многими одинаковыми элементами.) Риска непродвижения разделения можно избежать способом, отличным от описанного Хоаром. Такое разделение может произойти только в том случае, если не обнаружено никаких инверсий, когда оба указателя перемещаются к опорному элементу на первой итерации (тогда они считаются пересекшимися, и обмена не происходит).

В псевдокоде [13]

// Сортирует массив (часть массива), делит его на разделы, затем сортирует их. 
 Алгоритм  быстрой сортировки(A, lo, hi)  is  
   if  lo >= 0 && hi >= 0 && lo < hi  then 
      p := раздел (A, вот, привет)  
      Quicksort(A, lo, p) // Примечание: теперь включена опорная точка 
      быстрая сортировка (A, p + 1, привет)  

  // Делит массив на два раздела. 
 Алгоритм  раздела (A, lo, hi)  равен  
   // значению Pivot. 
    Pivot := A[lo]  // Выбираем первый элемент в качестве опорного 

   // Индекс слева 
    я := ло - 1  

    // Правый индекс 
    j := привет + 1 

    цикл навсегда  
     // Переместите левый индекс вправо хотя бы один раз, и пока элемент 
     // с левым индексом меньше опорной точки, 
     do  i := i + 1  while  A[i] < Pivot 
    
      // Переместите правый индекс влево хотя бы один раз, и пока элемент с 
     // правым индексом больше, чем опорный элемент, 
     do  j := j - 1  while  A[j] > Pivot 

      // Если индексы пересеклись, возвращаем 
     if  i >= j  , затем   возвращаем  j 
    
      // Меняем местами элементы с левым и правым индексами, 
     меняем  A[i]  на  A[j] 
 

Весь массив сортируется по быстрая сортировка(A, 0, длина(A) - 1) .

Схема Хоара более эффективна, чем схема разбиения Ломуто, поскольку в среднем она выполняет в три раза меньше операций подкачки. Кроме того, как уже упоминалось, данная реализация создает сбалансированный раздел, даже если все значения равны. [10] [ самостоятельно опубликованный источник? ] , чего нет в схеме Ломуто. Как и схема разбиения Ломуто, разбиение Хоара также приведет к ухудшению качества быстрой сортировки до O ( n 2 ) для уже отсортированных входных данных, если точка поворота была выбрана в качестве первого или последнего элемента. Однако при использовании среднего элемента в качестве опорного элемента результаты сортировки данных практически без свопов в разделах одинакового размера, что приводит к наилучшему поведению быстрой сортировки, т. е. O ( n log( n ) ) . Как и другие, разделение Хоара не обеспечивает стабильной сортировки. В этой схеме окончательное местоположение опорной точки не обязательно совпадает с возвращаемым индексом, поскольку опорная точка и элементы, равные опорной точке, могут оказаться в любом месте внутри раздела после шага разделения и не могут быть отсортированы до тех пор, пока не будет выполнен базовый случай раздел с одним элементом достигается с помощью рекурсии. Следовательно, следующие два сегмента, на которых повторяется основной алгоритм: (lo..p) (элементы ≤ поворот) и (p+1..hi) (элементы ≥ опорная точка) в отличие от (ло..п-1) и (p+1..hi) как в схеме Ломуто.

Последующие рекурсии (расширение предыдущего абзаца)

Давайте немного остановимся на следующих двух сегментах, на которых повторяется основной алгоритм. Поскольку мы используем строгие компараторы (>, <) в «do... while», чтобы не допустить выхода за пределы диапазона, есть вероятность, что сам поворотный элемент поменяется местами с другими элементами в функции секционирования. Таким образом, индекс, возвращаемый функцией секционирования, не обязательно находится там, где находится фактическая опорная точка. Рассмотрим пример [5, 2, 3, 1, 0] , следуя схеме, после первого разбиения массив становится [0, 2, 1, 3, 5] , возвращаемый «индекс» равен 2, что соответствует числу 1, когда реальным центром, с которого мы решили начать раздел, было число 3. В этом примере мы посмотрите, как необходимо включать возвращаемый индекс функции секционирования в наши последующие рекурсии. В результате нам предоставляется выбор: либо рекурсия по (ло..п) и (p+1..привет) , или (ло..п - 1) и (п..привет) . Какой из двух вариантов мы выберем, зависит от того, какой индекс ( i или j ) мы возвращаем в функции статистического распределения, когда индексы пересекаются, и от того, как мы выбираем опорную точку в функции статистического распределения ( пол или потолок ).

Давайте сначала рассмотрим выбор рекурсии на (ло..п) и (p+1..hi) на примере сортировки массива, в котором существует несколько одинаковых элементов. [0, 0] . Если индекс i («последний» индекс) возвращается после пересечения индексов в функции секционирования, индекс 1 будет возвращен после первого секционирования. Последующая рекурсия на (lo..p) будет находиться в (0, 1), что соответствует точно такому же массиву [0, 0] . Производится непродвигающееся разделение, вызывающее бесконечную рекурсию. Таким образом, очевидно, что при рекурсии (ло..п) и (p+1..hi) , поскольку левая половина рекурсии включает возвращаемый индекс, задача функции секционирования заключается в исключении «хвоста» в непродвигающихся сценариях. То есть индекс j («бывший» индекс при пересечении индексов) должен быть возвращен вместо i. Следуя аналогичной логике, рассмотрим пример уже отсортированного массива. [0, 1] , выбор опорной точки должен быть «нижним», чтобы гарантировать, что указатели останавливаются на «бывшей», а не на «последней» (с «потолком» в качестве опорной точки индекс 1 будет возвращен и включен в (lo..p), вызывающий бесконечную рекурсию). По той же причине следует избегать выбора последнего элемента в качестве опорного.

Выбор рекурсии на (ло..п - 1) и (p..hi) следует той же логике, что и выше. Поскольку правая половина рекурсии включает возвращаемый индекс, задача функции секционирования заключается в исключении «головы» в непродвигающихся сценариях. Индекс i («последний» индекс после пересечения индексов) в статистической сумме необходимо вернуть, а «потолок» необходимо выбрать в качестве опорной точки. Два нюанса снова становятся понятны, если рассмотреть примеры сортировки массива, в котором существует несколько одинаковых элементов ( [0, 0] ) и уже отсортированный массив [0, 1] соответственно. Примечательно, что при использовании версии рекурсии по той же причине следует избегать выбора первого элемента в качестве опорного.

Проблемы реализации [ править ]

Выбор опоры [ править ]

В самых ранних версиях быстрой сортировки в качестве опорного элемента часто выбирался самый левый элемент раздела. К сожалению, это приводит к наихудшему поведению уже отсортированных массивов, что является довольно распространенным вариантом использования. [16] Проблема была легко решена путем выбора случайного индекса для опорной точки, выбора среднего индекса раздела или (особенно для более длинных разделов) выбора медианы первого , среднего и последнего элемента раздела для опорной точки (как рекомендовано Седжвик ). [17] Это правило «медианы из трех» учитывает случай сортировки (или обратной сортировки) входных данных и дает лучшую оценку оптимального опорного элемента (истинной медианы), чем выбор любого отдельного элемента, когда нет информации о порядке ввода. вход известен.

Фрагмент кода медианы из трех для раздела Lomuto:

середина := ⌊(ло + привет) / 2⌋ 
  если  A[mid] < A[lo] 
      поменять местами A[lo] на A[mid] 
  если  A[hi] < A[lo] 
      поменять местами A[lo] на A[hi] 
  если  A[mid] < A[hi] 
      поменять местами A[mid] на A[hi] 
  стержень := A[привет] 
 

Он помещает медиану в A[hi] сначала, затем это новое значение A[hi] используется для поворота, как в базовом алгоритме, представленном выше.

В частности, ожидаемое количество сравнений, необходимое для сортировки n элементов (см. § Анализ рандомизированной быстрой сортировки ) со случайным выбором сводного элемента, составляет 1,386 n log n . Поворот медианы из трех снижает это значение до C n , 2 ≈ 1,188 n log n , за счет трехпроцентного увеличения ожидаемого количества свопов. [7] Еще более строгое правило поворота для больших массивов — выбрать девятый , рекурсивную медиану трех (Mo3), определяемую как [7]

девятый( a ) = медиана(Mo3(первый 1/3 средний а ) , Мо3 ( 1/3 конечный а ) ( , Мо3 1/3 а ) )

Выбор опорного элемента также осложняется наличием целочисленного переполнения . Если граничные индексы сортируемого подмассива достаточно велики, наивное выражение для среднего индекса ( lo + hi )/2 вызовет переполнение и предоставит недопустимый сводный индекс. Этого можно избежать, используя, например, lo + ( hi lo )/2 для индексации среднего элемента за счет более сложной арифметики. Аналогичные проблемы возникают и при некоторых других методах выбора поворотного элемента.

Повторяющиеся элементы [ править ]

При использовании алгоритма разделения, такого как схема разделения Ломуто, описанная выше (даже такого, который выбирает хорошие значения поворота), быстрая сортировка демонстрирует плохую производительность для входных данных, которые содержат много повторяющихся элементов. Проблема становится очевидной, когда все входные элементы равны: при каждой рекурсии левый раздел пуст (ни одно входное значение не меньше опорного), а правый раздел уменьшился только на один элемент (ось удалена). Следовательно, схема разделения Ломуто требует квадратичного времени для сортировки массива равных значений. Однако при использовании алгоритма секционирования, такого как схема разделения Хоара, повторяющиеся элементы обычно приводят к лучшему секционированию, и хотя могут произойти ненужные замены элементов, равных опорному элементу, время работы обычно уменьшается по мере увеличения количества повторяющихся элементов (при использовании кэша памяти). сокращение накладных расходов на обмен). В случае, когда все элементы равны, схема разделения Хоара без необходимости меняет местами элементы, но в лучшем случае само разделение является лучшим случаем, как отмечено в разделе разделения Хоара выше.

Чтобы решить проблему схемы раздела Ломуто (иногда называемую проблемой голландского национального флага). [7] ), можно использовать альтернативную процедуру разделения с линейным временем, которая разделяет значения на три группы: значения меньше опорной точки, значения, равные опорной точке, и значения, превышающие опорную точку. (Бентли и Макилрой называют это «толстым разделом», и это уже было реализовано в qsort версии 7 Unix . [7] ) Значения, равные опорной точке, уже отсортированы, поэтому рекурсивно сортировать необходимо только разделы «меньше» и «больше». В псевдокоде алгоритм быстрой сортировки выглядит следующим образом:

// Сортирует массив (часть массива), делит его на разделы, затем сортирует их. 
 Алгоритм  быстрой сортировки(A, lo, hi)  is 
   if  lo >= 0 && lo < hi  then 
      lt, gt := раздел(A, lo, hi)  // Несколько возвращаемых значений 
      быстрая сортировка (A, lo, lt - 1) 
      быстрая сортировка (A, GT + 1, привет) 

  // Делит массив на три части. 
 Алгоритм  раздела (A, lo, hi)  равен 
   // значению Pivot. 
    Pivot := A[(lo + hi) / 2]  // Выбираем средний элемент в качестве опорного (целочисленное деление) 
  
   // Меньший, равный и больший индекс 
    lt := вот 
    экв := вот 
    гт := привет 
  
    // Перебираем и сравниваем все элементы с опорной точкой 
   while  eq <= gt  do 
     if  A[eq] < Pivot  then 
       // Поменяйте местами элементы с равными и меньшими индексами, 
       поменяйте местами  A[eq]  на  A[lt] 
        // Увеличиваем меньший индекс 
        л := л + 1 
        // Увеличиваем равный индекс 
        экв := экв + 1 
      else   if  A[eq] > Pivot  then 
       // Поменяйте местами элементы с равными и большими индексами, 
       поменяйте местами  A[eq]  на  A[gt] 
        // Уменьшаем больший индекс 
        гт := гт - 1 
      else   //  если  A[eq] = опорная точка  , то 
       // Увеличить равный индекс 
        экв := экв + 1 
  
   // Возвращаем меньший и больший индексы 
   return  lt, gt
 

The partitionАлгоритм возвращает индексы к первому («самому левому») и последнему («крайнему правому») элементу среднего раздела. Любой другой элемент раздела равен опорному элементу и поэтому сортируется. Следовательно, элементы раздела не обязательно включать в рекурсивные вызовы quicksort.

Наилучший случай для алгоритма теперь возникает, когда все элементы равны (или выбираются из небольшого набора из k n элементов). В случае, если все элементы равны, модифицированная быстрая сортировка выполнит только два рекурсивных вызова для пустых подмассивов и, таким образом, завершится за линейное время (при условии, что partition подпрограмма занимает не больше линейного времени).

Оптимизации [ править ]

Другими важными оптимизациями, также предложенными Седжвиком и широко используемыми на практике, являются: [18] [19]

  • Чтобы убедиться, что не более O (log n ) используется пространства, сначала повторите меньшую сторону раздела, затем используйте хвостовой вызов , чтобы вернуться к другой, или обновите параметры, чтобы они больше не включали теперь отсортированную меньшую сторону, и повторите, чтобы отсортировать большую сторону.
  • Когда количество элементов ниже некоторого порога (например, десяти элементов), переключитесь на нерекурсивный алгоритм сортировки, такой как сортировка вставками , который выполняет меньше замен, сравнений или других операций с такими небольшими массивами. Идеальный «порог» будет варьироваться в зависимости от деталей конкретной реализации.
  • Более старый вариант предыдущей оптимизации: когда количество элементов меньше порога k , просто останавливаемся; затем, после обработки всего массива, выполните для него сортировку вставкой. Досрочная остановка рекурсии оставляет массив k -отсортированным, что означает, что каждый элемент находится не более чем на k позициях от своей окончательной отсортированной позиции. В этом случае O ( kn ) для завершения сортировки вставкой требуется время , которое является линейным, если k является константой. [20] [12] : 117  По сравнению с оптимизацией «множества мелких сортировок», эта версия может выполнять меньше инструкций, но она неоптимально использует кэш-память современных компьютеров. [21]

Распараллеливание [ править ]

Формулировка быстрой сортировки «разделяй и властвуй» делает ее поддающейся распараллеливанию с использованием параллелизма задач . Этап секционирования выполняется с помощью параллельного алгоритма суммирования префиксов для вычисления индекса для каждого элемента массива в его секции секционированного массива. [22] [23] Учитывая массив размером n , шаг разделения выполняет O( n ) работу за время O (log n ) и требует O( n ) дополнительного рабочего пространства. После разделения массива два раздела можно рекурсивно сортировать параллельно. Предполагая идеальный выбор опорных точек, параллельная быстрая сортировка сортирует массив размера n за O( n log n ) , а затем за O(log 2 n ) время с использованием O( n ) дополнительного пространства.

Быстрая сортировка имеет некоторые недостатки по сравнению с альтернативными алгоритмами сортировки, такими как сортировка слиянием , которые усложняют ее эффективное распараллеливание. Глубина дерева «разделяй и властвуй» быстрой сортировки напрямую влияет на масштабируемость алгоритма, и эта глубина сильно зависит от выбора алгоритмом опорной точки. Кроме того, сложно эффективно распараллелить этап секционирования на месте. Использование рабочего пространства упрощает этап разделения, но увеличивает объем памяти алгоритма и постоянные накладные расходы.

Другие, более сложные алгоритмы параллельной сортировки могут обеспечить еще лучшие временные рамки. [24] Например, в 1991 году Дэвид М.В. Пауэрс описал параллельную быструю сортировку (и связанную с ней поразрядную сортировку ), которая может работать за O (log n ) время в CRCW (параллельное чтение и параллельная запись) PRAM (параллельная машина с произвольным доступом) с n процессорами. путем неявного выполнения секционирования. [25]

Формальный анализ [ править ]

Анализ наихудшего случая [ править ]

Самый несбалансированный раздел возникает, когда один из подсписков, возвращаемых процедурой разделения, имеет размер n - 1 . [26] Это может произойти, если опорным элементом является самый маленький или самый большой элемент в списке, или в некоторых реализациях (например, схема разделения Lomuto, как описано выше), когда все элементы равны.

Если это происходит неоднократно в каждом разделе, то каждый рекурсивный вызов обрабатывает список размером на один меньше, чем предыдущий список. Следовательно, мы можем сделать n - 1 вложенных вызовов, прежде чем достигнем списка размером 1. Это означает, что дерево вызовов представляет собой линейную цепочку из n - 1 вложенных вызовов. i вызов выполняет O ( n i ) работы для разделения, и , поэтому в этом случае быстрая сортировка занимает O ( n 2 ) время.

наилучшего Анализ случая

В наиболее сбалансированном случае каждый раз, когда мы выполняем разбиение, мы делим список на две почти равные части. Это означает, что каждый рекурсивный вызов обрабатывает список вдвое меньшего размера. Следовательно, мы можем сделать только log 2 n вложенных вызовов, прежде чем достигнем списка размером 1. Это означает, что глубина дерева вызовов равна log 2 n . Но никакие два вызова на одном уровне дерева вызовов не обрабатывают одну и ту же часть исходного списка; таким образом, для каждого уровня вызовов требуется только O ( n ) времени (каждый вызов имеет некоторые постоянные накладные расходы, но поскольку на каждом уровне имеется только O ( n ) вызовов, это учитывается в коэффициенте O ( n ) . В результате алгоритм использует только время O ( n log n ) .

Анализ среднего случая [ править ]

Чтобы отсортировать массив из n различных элементов, быстрая сортировка занимает O ( n log n ) время ожидания , усредненное по всем n ! перестановки n элементов с равной вероятностью . В качестве альтернативы, если алгоритм равномерно случайным образом выбирает опорную точку из входного массива, тот же анализ можно использовать для ограничения ожидаемого времени выполнения для любой входной последовательности; затем ожидание принимается за случайный выбор, сделанный алгоритмом (Кормен и др. , Введение в алгоритмы , [13] раздел 7.3).

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

Использование процентилей [ править ]

Если каждый центральный элемент имеет рейтинг где-то посередине 50 процентов, то есть между 25-м и 75-м процентилем, то он разделяет элементы как минимум с 25% и максимум с 75% с каждой стороны. Если бы мы могли последовательно выбирать такие точки поворота, нам пришлось бы только разделить список максимум раз до достижения списков размера 1, что дает алгоритм O ( n log n ) .

Когда входные данные представляют собой случайную перестановку, ведущая точка имеет случайный ранг, поэтому не гарантируется, что она будет находиться в середине 50 процентов. Однако когда мы начинаем со случайной перестановки, при каждом рекурсивном вызове ведущая точка имеет случайный ранг в своем списке, и поэтому примерно в половине случаев она находится в середине 50 процентов. Это достаточно хорошо. Представьте себе, что подбрасывается монета: орел означает, что ранг опорной точки находится в середине 50 процентов, решка означает, что это не так. Теперь представьте, что монету подбрасывают снова и снова, пока на ней не выпадет k орлов. Хотя это может занять много времени, в среднем всего 2 тыс. требуется подбрасываний, и вероятность того, что на монете не выпадет k орлов после 100 тыс . подбрасываний, крайне мала (это можно сделать строгим, используя границы Чернова ). По тому же аргументу рекурсия быстрой сортировки завершится в среднем при глубине вызова всего лишь . Но если его средняя глубина вызовов равна O (log n ) , и каждый уровень дерева вызовов обрабатывает не более n элементов, общий объем выполненной работы в среднем равен продукту O ( n log n ) . Алгоритму не нужно проверять, что точка поворота находится в средней половине — если мы попадаем в нее какую-то постоянную долю раз, этого достаточно для желаемой сложности.

Использование повторений [ править ]

Альтернативный подход — установить рекуррентное соотношение для фактора T ( n ) , времени, необходимого для сортировки списка размера n . В наиболее несбалансированном случае один вызов быстрой сортировки требует работы O ( n ) плюс два рекурсивных вызова для списков размера 0 и n −1 , поэтому рекуррентное соотношение имеет вид

Это то же соотношение, что и для сортировки вставкой и сортировки выбором , и оно решается в худшем случае T ( n ) = O ( n 2 ) .

В наиболее сбалансированном случае один вызов быстрой сортировки включает в себя работу O ( n ) плюс два рекурсивных вызова для списков размера n /2 , поэтому рекуррентное соотношение имеет вид

Основная теорема для повторений по принципу «разделяй и властвуй» говорит нам, что T ( n ) = O ( n log n ) .

Ниже приводится схема формального доказательства O ( n log n ) ожидаемой временной сложности . Предположим, что дубликатов нет, поскольку дубликаты можно обрабатывать с помощью предварительной и постобработки с линейным временем, или рассматривать случаи проще, чем анализируемые. Когда входные данные представляют собой случайную перестановку, ранг опорной точки является равномерным случайным от 0 до n - 1 . Тогда результирующие части разбиения имеют размеры i и n i −1 , а i является равномерно случайным от 0 до n −1 . Итак, усредняя по всем возможным разбиениям и отмечая, что количество сравнений для раздела равно n - 1 , среднее количество сравнений по всем перестановкам входной последовательности можно точно оценить, решив рекуррентное соотношение:

Решение рекуррентного уравнения дает C ( n ) знак равно 2 n ln n ≈ 1,39 n log 2 n .

Это означает, что в среднем быстрая сортировка работает примерно на 39% хуже, чем в лучшем случае. В этом смысле он ближе к лучшему, чем к худшему. Сортировка сравнением чем log 2 ( n !) сравнений для сортировки n элементов (как описано в статье «Сортировка сравнения» ), а в случае больших n не может использовать в среднем меньше , аппроксимация Стирлинга дает log 2 ( n !) ≈ n (log 2 n − log 2 e ) , поэтому быстрая сортировка не намного хуже идеальной сортировки сравнением. Такое быстрое среднее время выполнения является еще одной причиной практического превосходства быстрой сортировки над другими алгоритмами сортировки.

Использование двоичного дерева поиска [ править ]

Следующее двоичное дерево поиска (BST) соответствует каждому выполнению быстрой сортировки: начальный опорный узел является корневым узлом; ось левой половины — это корень левого поддерева, ось правой половины — это корень правого поддерева и так далее. Количество сравнений при выполнении быстрой сортировки равно количеству сравнений при построении BST последовательностью вставок. Таким образом, среднее количество сравнений для рандомизированной быстрой сортировки равно средней стоимости построения BST, когда вставленные значения образуют случайную перестановку.

Рассмотрим BST, созданный путем вставки последовательности значений, образующих случайную перестановку. Обозначим через C стоимость создания BST. У нас есть , где — двоичная случайная величина, показывающая, было ли во время вставки было сравнение с .

Согласно линейности ожидания , ожидаемое значение из C .

Исправьте i и j < i . Ценности , после сортировки определите j +1 интервалов. Основное структурное наблюдение состоит в том, что сравнивается с в алгоритме тогда и только тогда, когда попадает в один из двух интервалов, прилегающих к .

Заметьте, что поскольку это случайная перестановка, также является случайной перестановкой, поэтому вероятность того, что находится рядом с это точно .

Закончим кратким расчетом:

Пространственная сложность [ править ]

Пространство, используемое быстрой сортировкой, зависит от используемой версии.

Версия быстрой сортировки на месте имеет пространственную сложность O (log n ) даже в худшем случае, если она тщательно реализована с использованием следующих стратегий.

  • Используется локальное секционирование. Этот нестабильный раздел требует O (1) пространства.
  • После разделения сначала (рекурсивно) сортируется раздел с наименьшим количеством элементов, требующий не более O (log n ) пространства. Затем другой раздел сортируется с использованием хвостовой рекурсии или итерации, которая не добавляется в стек вызовов. Эта идея, как обсуждалось выше, была описана Р. Седжвиком и сохраняет глубину стека ограниченной O (log n ) . [17] [20]

Быстрая сортировка с разделением на месте и нестабильным секционированием использует только постоянное дополнительное пространство перед выполнением любого рекурсивного вызова. Быстрая сортировка должна хранить постоянный объем информации для каждого вложенного рекурсивного вызова. Поскольку в лучшем случае выполняется не более O (log n ) вложенных рекурсивных вызовов, он использует пространство O (log n ) . Однако без трюка Седжвика по ограничению рекурсивных вызовов в худшем случае быстрая сортировка могла бы сделать O ( n ) вложенных рекурсивных вызовов и потребовать O ( n ) вспомогательного пространства.

С точки зрения некоторой сложности, такие переменные, как lo и hi , не используют постоянное пространство; требуется O (log n ) для индексации списка из n элементов бит. Поскольку такие переменные есть в каждом кадре стека, для быстрой сортировки с использованием трюка Седжвика требуется O ((log n ) 2 ) кусочки пространства. Однако это требование к пространству не так уж и страшно, поскольку, если бы список содержал отдельные элементы, ему потребовалось бы как минимум O ( n log n ) бит пространства.

Другая, менее распространенная версия быстрой сортировки «не на месте» использует пространство O ( n ) для рабочей памяти и может реализовать стабильную сортировку. Рабочая память позволяет легко и стабильно разбить входной массив на разделы, а затем скопировать его обратно во входной массив для последующих рекурсивных вызовов. Оптимизация Седжвика по-прежнему актуальна.

Связь с другими алгоритмами [ править ]

Быстрая сортировка — это оптимизированная по пространству версия сортировки двоичного дерева . Вместо последовательной вставки элементов в явное дерево быстрая сортировка организует их одновременно в дерево, которое подразумевается рекурсивными вызовами. Алгоритмы выполняют точно такие же сравнения, но в другом порядке. Часто желательным свойством алгоритма сортировки является стабильность – то есть порядок элементов, которые сравниваются равными, не изменяется, что позволяет естественным образом контролировать порядок многоключевых таблиц (например, списков каталогов или папок). Это свойство сложно поддерживать для быстрой сортировки на месте (которая использует только постоянное дополнительное пространство для указателей и буферов и дополнительное пространство O (log n ) для управления явной или неявной рекурсией). Для вариантов быстрой сортировки, требующих дополнительной памяти из-за представлений с использованием указателей (например, списков или деревьев) или файлов (фактически списков), поддерживать стабильность тривиально. Более сложные или привязанные к диску структуры данных имеют тенденцию увеличивать временные затраты, как правило, увеличивая использование виртуальной памяти или диска.

Самым прямым конкурентом быстрой сортировки является пирамидальная сортировка . Преимущество пирамидальной сортировки состоит в простоте и времени выполнения в худшем случае O ( n log n ) время работы пирамидальной сортировки , но среднее обычно считается более медленным, чем быстрая сортировка на месте, в первую очередь из-за худшей локальности ссылки . [27] Этот результат является спорным; некоторые публикации указывают на обратное. [28] [29] Основным недостатком быстрой сортировки является сложность реализации, необходимая для того, чтобы избежать неправильного выбора сводной таблицы и результирующего результата O ( n 2 ) производительность. Интросорт — это вариант быстрой сортировки, который решает эту проблему путем переключения на пирамидальную сортировку при обнаружении плохого случая. Основные языки программирования, такие как C++ (в реализациях GNU и LLVM), используют интросортировку. [30]

Быстрая сортировка также конкурирует с сортировкой слиянием , другим O ( n log n ) алгоритмом сортировки . Основные преимущества сортировки слиянием заключаются в том, что она является стабильной сортировкой и имеет отличную производительность в худшем случае. Основным недостатком сортировки слиянием является то, что это алгоритм неуместности, поэтому при работе с массивами эффективные реализации требуют O ( n ) вспомогательного пространства (по сравнению с O (log n ) для быстрой сортировки с разделением на месте и хвостовым разделением). рекурсия или O (1) для пирамидальной сортировки).

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

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

Поворот на основе выбора [ править ]

Алгоритм выбора выбирает k- е наименьшее число из списка; в целом это более простая задача, чем сортировка. Один простой, но эффективный алгоритм выбора работает почти так же, как быстрая сортировка, и поэтому известен как быстрый выбор . Разница в том, что вместо рекурсивных вызовов обоих подсписков он выполняет только один хвостовой рекурсивный вызов для подсписка, содержащего нужный элемент. Это изменение снижает среднюю сложность до линейного времени или времени O ( n ) , что оптимально для выбора, но алгоритм выбора по-прежнему остается O ( n) . 2 ) в худшем случае.

Вариант быстрого выбора, алгоритм медианы медиан , выбирает опорные точки более тщательно, гарантируя, что опорные точки находятся рядом с серединой данных (между 30-м и 70-м процентилями), и, таким образом, имеет гарантированное линейное время — O ( n ) . Эту же стратегию поворота можно использовать для построения варианта быстрой сортировки (быстрая сортировка по медиане медиан) за время O ( n log n ) . Однако накладные расходы на выбор точки поворота значительны, поэтому на практике это обычно не используется.

Более абстрактно, учитывая алгоритм выбора O ( n ) , можно использовать его для поиска идеального центра (медианы) на каждом этапе быстрой сортировки и, таким образом, создать алгоритм сортировки со O ( n log n ) временем работы . Практическая реализация этого варианта в среднем происходит значительно медленнее, но представляет теоретический интерес, поскольку показывает, что оптимальный алгоритм выбора может привести к оптимальному алгоритму сортировки.

Варианты [ править ]

Многоповоротная быстрая сортировка [ править ]

Вместо разделения на два подмассива с использованием одной опорной точки можно использовать быструю сортировку с несколькими опорными точками (также мультибыструю сортировку). [21] ) разделяет входные данные на некоторое количество подмассивов, используя s - 1 поворотов. Хотя случай с двумя поворотами ( s = 3 ) рассматривался Седжвиком и другими еще в середине 1970-х годов, полученные алгоритмы на практике были не быстрее, чем «классическая» быстрая сортировка. [31] Оценка мультибыстрой сортировки с переменным числом поворотов, проведенная в 1999 году и настроенная на эффективное использование кэша процессора, показала, что она увеличивает количество инструкций примерно на 20%, но результаты моделирования показали, что она будет более эффективной при очень больших входных данных. [21] Версия быстрой сортировки с двумя точками, разработанная Ярославским в 2009 году. [32] оказалось достаточно быстрым [33] для гарантированной реализации в Java 7 в качестве стандартного алгоритма сортировки массивов примитивов (сортировка массивов объектов выполняется с помощью Timsort ). [34] Впоследствии выяснилось, что преимущество этого алгоритма в производительности в основном связано с производительностью кэша. [35] Результаты экспериментов показывают, что вариант с тремя шарнирами может работать еще лучше на современных машинах. [36] [37]

Внешняя быстрая сортировка [ править ]

Для дисковых файлов возможна внешняя сортировка на основе разделения, аналогичная быстрой сортировке. Это медленнее, чем внешняя сортировка слиянием, но не требует дополнительного дискового пространства. Используются 4 буфера, 2 на ввод, 2 на вывод. Пусть N = количество записей в файле, B = количество записей в буфере и M = N/B = количество сегментов буфера в файле. Данные читаются (и записываются) с обоих концов файла внутрь. Пусть X представляет сегменты, которые начинаются в начале файла, а Y представляют сегменты, которые начинаются в конце файла. Данные считываются в буферы чтения X и Y. Выбирается сводная запись, и записи в буферах X и Y, кроме сводной записи, копируются в буфер записи X в возрастающем порядке и в буфер записи Y в порядке убывания на основе сравнения со сводной записью. Как только буфер X или Y заполнен, он записывается в файл, а следующий буфер X или Y считывается из файла. Процесс продолжается до тех пор, пока все сегменты не будут прочитаны и не останется один буфер записи. Если этот буфер является буфером записи X, к нему добавляется сводная запись и записывается буфер X. Если этот буфер является буфером записи Y, сводная запись добавляется к буферу Y и записывается в буфер Y. Это составляет один шаг разделения файла, и теперь файл состоит из двух подфайлов. Начальная и конечная позиции каждого подфайла помещаются/извлекаются в автономный стек или основной стек посредством рекурсии. Чтобы ограничить пространство стека до O(log2(n)), сначала обрабатывается меньший подфайл. Для автономного стека поместите в стек параметры большего подфайла и выполните итерацию по меньшему подфайлу. Для рекурсии сначала выполните рекурсию по меньшему подфайлу, а затем выполните итерацию для обработки большего подфайла. Если количество записей в подфайле меньше или равно 4 B, он сортируется на месте с помощью быстрой сортировки и записывается. Теперь этот подфайл отсортирован и находится на своем месте в файле. Процесс продолжается до тех пор, пока все подфайлы не будут отсортированы и размещены на своих местах. Среднее количество проходов по файлу составляет примерно 1 + ln(N+1)/(4 B), но шаблон в худшем случае — N проходов (что эквивалентно O(n^2) для внутренней сортировки в худшем случае). [38]

Трехсторонняя сортировка поразрядная быстрая

Этот алгоритм представляет собой комбинацию поразрядной сортировки и быстрой сортировки. Выберите элемент из массива (центральную точку) и рассмотрите первый символ (ключ) строки (многоклавишный). Разделите оставшиеся элементы на три набора: те, чей соответствующий символ меньше, равен или больше символа опорной точки. Рекурсивно сортируйте разделы «меньше» и «больше» по одному и тому же символу. Рекурсивно сортируйте раздел «равно» по следующему символу (ключу). Учитывая, что мы сортируем с использованием байтов или слов длиной W бит, лучший случай — O ( KN ) , а худший — O (2 К N ) или хотя бы O ( N 2 ) как для стандартной быстрой сортировки, задано для уникальных ключей N <2 К , а K — скрытая константа во всех стандартных алгоритмах сортировки сравнением, включая быструю сортировку. Это своего рода трехсторонняя быстрая сортировка, в которой средний раздел представляет (тривиально) отсортированный подмассив элементов, которые точно равны опорному.

Быстрая поразрядная сортировка [ править ]

Также разработан Пауэрсом как O ( K ) параллельный алгоритм PRAM . Это снова комбинация поразрядной сортировки и быстрой сортировки, но решение о разделении влево/вправо при быстрой сортировке принимается для последовательных битов ключа и, таким образом, равно O ( KN ) для N K -битных ключей. Все алгоритмы сортировки сравнением неявно предполагают трансдихотомическую модель с K в Θ (log N ) , так как если K меньше, мы можем сортировать за время O ( N ) , используя хеш-таблицу или целочисленную сортировку . Если K ≫ log N , но элементы уникальны в пределах O (log N ) бит, оставшиеся биты не будут проверяться ни быстрой сортировкой, ни быстрой поразрядной сортировкой. В противном случае все алгоритмы сортировки сравнения также будут иметь одинаковые накладные расходы на просмотр O ( K ) относительно бесполезных битов, но быстрая поразрядная сортировка позволит избежать худшего случая O ( N 2 ) поведение стандартной быстрой сортировки и быстрой сортировки по основанию, и будет быстрее даже в лучшем случае этих алгоритмов сравнения при этих условиях uniqueprefix( K ) ≫ log N . См. Силы [39] для дальнейшего обсуждения скрытых накладных расходов при сравнении, поразрядной и параллельной сортировке.

Блочная быстрая сортировка [ править ]

В любом алгоритме сортировки, основанном на сравнении, минимизация количества сравнений требует максимизации объема информации, получаемой от каждого сравнения, а это означает, что результаты сравнения непредсказуемы. Это приводит к частым неправильным предсказаниям ветвей , ограничивая производительность. [40] Блочная быстрая сортировка [41] перестраивает вычисления быстрой сортировки для преобразования непредсказуемых ветвей в зависимости данных . При секционировании входные данные разбиваются на блоки среднего размера (которые легко помещаются в кэш данных ), а два массива заполняются позициями элементов для замены. (Чтобы избежать условных ветвей, позиция безоговорочно сохраняется в конце массива, а индекс конца увеличивается, если требуется замена.) Второй проход заменяет элементы в позициях, указанных в массивах. Оба цикла имеют только один условный переход — тест на завершение, который обычно и выполняется.

Метод BlockQuicksort включен в . реализацию STL C++ LLVM, libcxx, что обеспечивает улучшение на 50% при обработке случайных целочисленных последовательностей Быстрая сортировка с обходом шаблонов ( pdqsort ), версия интросортировки, также включает этот метод. [30]

Частичная и сортировка инкрементная быстрая

Существует несколько вариантов быстрой сортировки, которые отделяют k наименьших или наибольших элементов от остальной части входных данных.

Обобщение [ править ]

Ричард Коул и Дэвид К. Кандатил в 2004 году открыли семейство однопараметрических алгоритмов сортировки, называемых сортировками разделов, которые в среднем (при равновероятном порядке всех входных данных) работают не более сравнения (близко к нижней границе теории информации) и операции; в худшем случае они выступают сравнения (а также операции); они находятся на месте и требуют только дополнительных космос. Практическая эффективность и меньшая разница в производительности были продемонстрированы при использовании оптимизированных быстрых сортировок ( Sedgewick и Bentley - McIlroy ). [42]

См. также [ править ]

  • Introsort — гибридный алгоритм сортировки

Примечания [ править ]

  1. ^ «Сэр Энтони Хоар» . Музей истории компьютеров. Архивировано из оригинала 3 апреля 2015 года . Проверено 22 апреля 2015 г.
  2. ^ Перейти обратно: а б Хоар, ЦАР (1961). «Алгоритм 64: Быстрая сортировка». Комм. АКМ . 4 (7): 321. дои : 10.1145/366622.366644 .
  3. ^ Скиена, Стивен С. (2008). Руководство по проектированию алгоритмов . Спрингер. п. 129. ИСБН  978-1-84800-069-8 .
  4. ^ К. Л. Фостер, Алгоритмы, абстракция и реализация , 1992, ISBN   0122626605 , с. 98
  5. ^ Шустек, Л. (2009). «Интервью: Интервью с CAR Hoare». Комм. АКМ . 52 (3): 38–41. дои : 10.1145/1467247.1467261 . S2CID   1868477 .
  6. ^ «Мое короткое интервью с сэром Тони Хоаром, изобретателем быстрой сортировки» . Марсело М Де Баррос. 15 марта 2015 г.
  7. ^ Перейти обратно: а б с д Это ж г Бентли, Джон Л.; Макилрой, М. Дуглас (1993). «Разработка функции сортировки» . Программное обеспечение: практика и опыт . 23 (11): 1249–1265. CiteSeerX   10.1.1.14.8162 . дои : 10.1002/спе.4380231105 . S2CID   8822797 .
  8. ^ Ван Эмден, Миннесота (1 ноября 1970 г.). «Алгоритмы 402: Повышение эффективности быстрой сортировки» . Коммун. АКМ . 13 (11): 693–694. дои : 10.1145/362790.362803 . ISSN   0001-0782 . S2CID   4774719 .
  9. ^ Бентли, Джон (2007). «Самый красивый код, который я никогда не писал». В Ораме, Энди; Уилсон, Грег (ред.). Красивый код: ведущие программисты объясняют, как они думают . О'Рейли Медиа. п. 30. ISBN  978-0-596-51004-6 .
  10. ^ Перейти обратно: а б с «Разделение быстрой сортировки: Хоар против Ломуто» . cs.stackexchange.com . Проверено 3 августа 2015 г.
  11. ^ Макилрой, доктор медицины (10 апреля 1999 г.). «Смертельный противник быстрой сортировки» (PDF) . Программное обеспечение: практика и опыт . 29 (4): 341–344. doi : 10.1002/(SICI)1097-024X(19990410)29:4<341::AID-SPE237>3.0.CO;2-R . S2CID   35935409 .
  12. ^ Перейти обратно: а б Джон Бентли (1999). Жемчуг программирования . Аддисон-Уэсли Профессионал.
  13. ^ Перейти обратно: а б с д Кормен, Томас Х .; Лейзерсон, Чарльз Э .; Ривест, Рональд Л .; Штейн, Клиффорд (2009) [1990]. «Быстрая сортировка». Введение в алгоритмы (3-е изд.). MIT Press и McGraw-Hill. стр. 170–190. ISBN  0-262-03384-4 .
  14. ^ Дикий, Себастьян (2012). Быстрая сортировка Dual Pivot в Java 7 (Диссертация). Технический университет Кайзерслаутерна.
  15. ^ Хоар, ЦАР (1 января 1962 г.). «Быстрая сортировка» . Компьютерный журнал . 5 (1): 10–16. дои : 10.1093/comjnl/5.1.10 . ISSN   0010-4620 .
  16. ^ Чандрамули, Бадриш; Гольдштейн, Джонатан (18 июня 2014 г.). «Терпение – добродетель» . Материалы Международной конференции ACM SIGMOD 2014 по управлению данными . Сигмод '14. Сноуберд, Юта, США: ACM. стр. 731–742. дои : 10.1145/2588555.2593662 . ISBN  978-1-4503-2376-5 . S2CID   7830071 .
  17. ^ Перейти обратно: а б Седжвик, Роберт (1 сентября 1998 г.). Алгоритмы на C: основы, структуры данных, сортировка, поиск, части 1–4 (3-е изд.). Пирсон Образование. ISBN  978-81-317-1291-7 .
  18. ^ qsort.c в GNU libc : [1] , [2]
  19. ^ http://www.ugrad.cs.ubc.ca/~cs260/chnotes/ch6/Ch6CovCompiled.html [ постоянная мертвая ссылка ]
  20. ^ Перейти обратно: а б Седжвик, Р. (1978). «Реализация программ быстрой сортировки». Комм. АКМ . 21 (10): 847–857. дои : 10.1145/359619.359631 . S2CID   10020756 .
  21. ^ Перейти обратно: а б с ЛаМарка, Энтони; Ладнер, Ричард Э. (1999). «Влияние кешей на производительность сортировки». Журнал алгоритмов . 31 (1): 66–104. CiteSeerX   10.1.1.27.1788 . дои : 10.1006/jagm.1998.0985 . S2CID   206567217 . Хотя сохранение небольших подмассивов до конца имеет смысл с точки зрения количества инструкций, это совершенно неправильно с точки зрения производительности кэша.
  22. ^ Умут А. Акар, Гай Э. Блеллок, Маргарет Рид-Миллер и Канат Тангвонгсан, Быстрая сортировка и сортировка нижних границ , Параллельные и последовательные структуры данных и алгоритмы . 2013.
  23. ^ Бреширс, Клэй (2012). «Быстрая сортировка раздела посредством сканирования префиксов» . Доктор Добб .
  24. ^ Миллер, Расс; Боксер, Лоуренс (2000). Алгоритмы последовательные и параллельные: единый подход . Прентис Холл. ISBN  978-0-13-086373-7 .
  25. ^ Пауэрс, Дэвид М.В. (1991). Параллелизованные быстрая сортировка и поразрядная сортировка с оптимальным ускорением . Учеб. Международная конференция. по параллельным вычислительным технологиям. CiteSeerX   10.1.1.57.9071 .
  26. ^ Другой может либо иметь 1 элемент, либо быть пустым (иметь 0 элементов), в зависимости от того, включен ли стержень в один из подразделов, как в процедуре разделения Хоара, или исключен из них обоих, как в процедуре Ломуто. .
  27. ^ Эделькамп, Стефан; Вайс, Армин (7–8 января 2019 г.). Эффективная сортировка в наихудшем случае с помощью QuickMergesort . ALENEX 2019: 21-й семинар по алгоритмической разработке и экспериментам. Сан Диего. arXiv : 1811.99833 . дои : 10.1137/1.9781611975499.1 . ISBN  978-1-61197-549-9 . на небольших экземплярах пирамидальная сортировка уже значительно медленнее быстрой (в наших экспериментах более 30% при n = 2). 10 ), а в более крупных экземплярах он страдает от плохого поведения кэша (в наших экспериментах более чем в восемь раз медленнее, чем быстрая сортировка для сортировки 2 28 элементы).
  28. ^ Се, Пол (2004). «Возвращение к сортировке» . azillionmonkeys.com.
  29. ^ Маккей, Дэвид (декабрь 2005 г.). «Групповая сортировка, быстрая сортировка и энтропия» . Архивировано из оригинала 1 апреля 2009 года.
  30. ^ Перейти обратно: а б Кутенин Данила (20 апреля 2022 г.). «Изменение std::sort в масштабе Google и за его пределами» . Экспериментальный холод .
  31. ^ Уайлд, Себастьян; Небель, Маркус Э. (2012). Анализ среднего случая быстрой сортировки с двойным поворотом в Java 7 . Европейский симпозиум по алгоритмам. arXiv : 1310.7409 . Бибкод : 2013arXiv1310.7409W .
  32. ^ Ярославский, Владимир (2009). «Быстрая сортировка с двумя поворотами» (PDF) . Архивировано из оригинала (PDF) 2 октября 2015 года.
  33. ^ Уайлд, С.; Небель, М.; Райциг, Р.; Лаубе, У. (7 января 2013 г.). Разработка быстрой сортировки Dual Pivot в Java 7 с использованием MaLiJAn . Слушания. Общество промышленной и прикладной математики. стр. 55–69. дои : 10.1137/1.9781611972931.5 . ISBN  978-1-61197-253-5 .
  34. ^ «Массивы» . Платформа Java SE 7 . Оракул . Проверено 4 сентября 2014 г.
  35. ^ Уайлд, Себастьян (3 ноября 2015 г.). «Почему быстрая сортировка с двойным поворотом?». arXiv : 1511.01138 [ cs.DS ].
  36. ^ Кушагра, Шрину; Лопес-Ортис, Алехандро; Цяо, Аурик; Манро, Дж. Ян (2014). Многоповоротная быстрая сортировка: теория и эксперименты . Учеб. Семинар по разработке алгоритмов и экспериментам (ALENEX). дои : 10.1137/1.9781611973198.6 .
  37. ^ Кушагра, Шрину; Лопес-Ортис, Алехандро; Манро, Дж. Ян; Цяо, Аурик (7 февраля 2014 г.). Multi-Pivot Quicksort: Theory and Experiments (PDF) (презентация семинара). Ватерлоо, Онтарио .
  38. ^ Моцкин, Д.; Хансен, CL (1982), «Эффективная внешняя сортировка с минимальными требованиями к пространству», Международный журнал компьютерных и информационных наук , 11 (6): 381–396, doi : 10.1007/BF00996816 , S2CID   6829805
  39. ^ Дэвид М.В. Пауэрс, Параллельное объединение: практическая сложность , Австралазийский семинар по компьютерной архитектуре, Университет Флиндерса, январь 1995 г.
  40. ^ Калигоси, Канела; Сандерс, Питер (11–13 сентября 2006 г.). Как неверные прогнозы ветвей влияют на быструю сортировку (PDF) . ЕКА 2006: 14-й ежегодный европейский симпозиум по алгоритмам. Цюрих . дои : 10.1007/11841036_69 .
  41. ^ Эделькамп, Стефан; Вайс, Армин (22 апреля 2016 г.). «BlockQuicksort: как неверные прогнозы ветвей не влияют на быструю сортировку». arXiv : 1604.06697 [ cs.DS ].
  42. ^ Ричард Коул, Дэвид К. Кандатил: «Анализ среднего случая сортировки разделов» , Европейский симпозиум по алгоритмам, 14–17 сентября 2004 г., Берген, Норвегия. Опубликовано: Конспекты лекций по информатике 3221, Springer Verlag, стр. 240–251.

Ссылки [ править ]

Внешние ссылки [ править ]