При внедрении быстрой сортировки вам необходимо выбрать точку поворота. Но когда я смотрю на псевдокод, подобный приведенному ниже, неясно, как мне выбрать точку опоры. Первый элемент списка? Что-то другое?
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array
if x ≤ pivot then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))
Может ли кто-нибудь помочь мне понять концепцию выбора точки разворота и понять, требуют ли разные сценарии разные стратегии.
algorithm
sorting
pseudocode
quicksort
Джейкоб Т. Нильсен
источник
источник
Ответы:
Выбор случайной оси сводит к минимуму вероятность того, что вы столкнетесь с наихудшей производительностью O (n 2 ) (всегда выбор первого или последнего приведет к наихудшей производительности для почти отсортированных или почти обратносортированных данных). Выбор среднего элемента также будет приемлемым в большинстве случаев.
Кроме того, если вы реализуете это самостоятельно, существуют версии алгоритма, которые работают на месте (то есть без создания двух новых списков с последующим их объединением).
источник
Это зависит от ваших требований. Выбор опоры в случайных делает его труднее создать набор данных, который генерирует производительность O (N ^ 2). «Медиана из трех» (первый, последний, средний) - это тоже способ избежать проблем. Однако остерегайтесь относительной производительности сравнений; если ваши сравнения дорогостоящие, то Mo3 выполняет больше сравнений, чем выбор (единственное значение поворота) наугад. Сравнение записей базы данных может быть дорогостоящим.
Обновление: добавление комментариев в ответ.
mdkess утверждал:
На что я ответил:
Анализ алгоритма поиска Хоара с разделением медианы трех (1997), авторы П. Киршенхофер, Х. Продингер, К. Мартинес поддерживают ваше утверждение (что «медиана трех» - это три случайных элемента).
На сайте portal.acm.org описана статья Ханну Эркио «Перестановка наихудшего случая для быстрой сортировки медианы трех», опубликованная в The Computer Journal, том 27, № 3, 1984 г. [Обновление 2012-02- 26: Получил текст для статьи . Раздел 2 «Алгоритм» начинается со слов: « Используя медиану первого, среднего и последнего элементов A [L: R], в большинстве практических ситуаций можно добиться эффективного разбиения на части довольно равных размеров. «Таким образом, обсуждается подход Mo3« первый-средний-последний ».]
Еще одна интересная короткая статья доктора медицины Макилроя «Противник-убийца для быстрой сортировки» , опубликованная в Software-Practice and Experience, Vol. 29 (0), 1–4 (0 1999). Он объясняет, как заставить почти любую Quicksort вести себя квадратично.
Технический журнал AT&T Bell Labs, октябрь 1984 г. «Теория и практика построения рабочей процедуры сортировки» заявляет: «Хоар предложил разбить несколько случайно выбранных линий вокруг медианы. [...] Седжвик рекомендовал выбрать медианное значение первых [. ..] последний [...] и средний ». Это указывает на то, что в литературе известны оба метода определения «медианы из трех». (Обновление 2014-11-23: статья, похоже, доступна в IEEE Xplore или в Wiley - если у вас есть членство или вы готовы заплатить взнос.)
«Разработка функции сортировки» Дж. Л. Бентли и М. Д. Макилроя, опубликованная в журнале Software Practice and Experience, Vol 23 (11), ноябрь 1993 г., подробно обсуждает проблемы, и они выбрали алгоритм адаптивного разделения, частично основанный на размер набора данных. Много обсуждают компромиссы для различных подходов.
Поиск в Google по запросу «среднее из трех» отлично подходит для дальнейшего отслеживания.
Спасибо за информацию; Раньше я сталкивался только с детерминированной «медианой из трех».
источник
Хех, я только что вел этот класс.
Есть несколько вариантов.
Просто: выберите первый или последний элемент диапазона. (плохо для частично отсортированного ввода) Лучше: выберите элемент в середине диапазона. (лучше для частично отсортированного ввода)
Однако выбор любого произвольного элемента рискует плохо разбить массив размера n на два массива размером 1 и n-1. Если вы делаете это достаточно часто, ваша быстрая сортировка рискует стать O (n ^ 2).
Одно улучшение, которое я заметил, - это выбор медианы (первая, последняя, средняя); В худшем случае он все еще может перейти к O (n ^ 2), но, вероятно, это редкий случай.
Для большинства данных достаточно выбрать первый или последний. Но если вы обнаружите, что часто сталкиваетесь с наихудшими сценариями (частично отсортированный ввод), первым вариантом будет выбор центрального значения (что является статистически хорошей точкой опоры для частично отсортированных данных).
Если вы все еще сталкиваетесь с проблемами, выбирайте средний путь.
источник
Никогда не выбирайте фиксированный поворот - это может быть атаковано, чтобы использовать время выполнения вашего алгоритма O (n ^ 2) в худшем случае, которое просто напрашивается на проблемы. Наихудший случай выполнения Quicksort возникает, когда в результате разбиения получается один массив из 1 элемента и один массив из n-1 элементов. Предположим, вы выбрали первый элемент в качестве раздела. Если кто-то скармливает вашему алгоритму массив, который находится в порядке убывания, ваша первая точка поворота будет самой большой, поэтому все остальное в массиве переместится влево от него. Затем, когда вы выполняете рекурсию, первый элемент снова будет самым большим, поэтому вы еще раз поместите все слева от него и так далее.
Лучшим методом является метод медианы из трех, при котором вы выбираете три элемента наугад и выбираете середину. Вы знаете, что выбранный вами элемент не будет первым или последним, но также, согласно центральной предельной теореме, распределение среднего элемента будет нормальным, что означает, что вы будете стремиться к среднему (и, следовательно, , n lg n время).
Если вы абсолютно хотите гарантировать время выполнения алгоритма O (nlgn), метод столбцов из 5 для поиска медианы массива выполняется за время O (n), что означает, что рекуррентное уравнение для быстрой сортировки в худшем случае будет быть T (n) = O (n) (найти медиану) + O (n) (разбиение) + 2T (n / 2) (рекурсивно влево и вправо). По основной теореме это O (n lg n) . Однако постоянный коэффициент будет огромным, и если производительность в худшем случае является вашей основной задачей, используйте вместо этого сортировку слиянием, которая в среднем лишь немного медленнее, чем быстрая сортировка, и гарантирует время O (nlgn) (и будет намного быстрее чем эта хромая медианная быстрая сортировка).
Объяснение алгоритма медианы медиан
источник
Не пытайтесь стать слишком умным и комбинировать стратегии поворота. Если в сочетании медианы 3 со случайным шарниром, выбирая медианы первого, последнего и случайного индекс в середине, то вы все равно будете уязвимы для многих распределений, которые посылают медиану 3 квадратичных (так его на самом деле хуже, чем простой случайный поворот)
Например, при распределении органа трубы (1,2,3 ... N / 2..3,2,1) первый и последний оба будут равны 1, а случайный индекс будет некоторым числом больше 1, взятие медианы дает 1 ( либо первое, либо последнее), и вы получите крайне несбалансированное разбиение.
источник
Так проще разбить быструю сортировку на три части.
Это ненамного неэффективнее, чем одна длинная функция, но ее намного легче понять.
Код следующий:
источник
Это полностью зависит от того, как изначально сортируются ваши данные. Если вы думаете, что он будет псевдослучайным, то лучше всего выбрать случайный выбор или выбрать середину.
источник
Если вы сортируете коллекцию с произвольным доступом (например, массив), лучше всего выбрать физический средний элемент. При этом, если массив полностью отсортирован (или почти отсортирован), два раздела будут близки к четным, и вы получите лучшую скорость.
Если вы сортируете что-то только с линейным доступом (например, связанный список), то лучше выбрать первый элемент, потому что это самый быстрый элемент для доступа. Однако здесь, если список уже отсортирован, вы облажались - один раздел всегда будет нулевым, а другой - всем, что приведет к худшему времени.
Однако для связанного списка выбор чего-либо, кроме первого, только усугубит ситуацию. Он выбирает средний элемент в списке-списке, вам нужно будет проходить его на каждом шаге раздела - добавляя операцию O (N / 2), которая выполняется logN раз, что составляет общее время O (1,5 N * log N) и это если мы знаем, сколько длится список, прежде чем мы начнем - обычно мы этого не делаем, поэтому нам пришлось бы пройти весь путь, чтобы пересчитать их, затем пройти половину пути, чтобы найти середину, затем пройти через третий раз, чтобы сделать фактический раздел: O (2.5N * log N)
источник
В идеале точка поворота должна быть средним значением во всем массиве. Это снизит шансы получить худшую производительность.
источник
Сложность быстрой сортировки сильно зависит от выбора значения сводной таблицы. например, если вы всегда выбираете первый элемент в качестве стержня, сложность алгоритма становится такой же худшей, как O (n ^ 2). вот умный метод выбора сводного элемента: 1. выберите первый, средний и последний элемент массива. 2. Сравните эти три числа и найдите число, которое больше одного и меньше другого, т.е. медиана. 3. Сделайте этот элемент поворотным.
выбирая стержень с помощью этого метода разбивает массив почти две половины и, следовательно, сложность сводится к O (Nlog (п)).
источник
В среднем, медиана 3 подходит для малых n. Медиана 5 немного лучше для большего n. Девятое, которое представляет собой «среднее из трех медиан из трех», даже лучше для очень больших n.
Чем выше вы поднимаете выборку, тем лучше вы получаете при увеличении n, но улучшение резко замедляется при увеличении выборки. И вы несете накладные расходы на отбор и сортировку образцов.
источник
Я рекомендую использовать средний индекс, так как он легко рассчитывается.
Вы можете рассчитать его округлением (array.length / 2).
источник
В действительно оптимизированной реализации метод выбора точки поворота должен зависеть от размера массива - для большого массива стоит потратить больше времени на выбор хорошей точки поворота. Не проводя полного анализа, я бы предположил, что «середина из O (log (n)) элементов» - хорошее начало, и это дает дополнительный бонус в виде отсутствия дополнительной памяти: использование хвостового вызова на большем разделе и в- При разделении на разделы мы используем одну и ту же дополнительную память O (log (n)) почти на каждом этапе алгоритма.
источник