Этот вопрос мне задали во время интервью. Они оба O (nlogn), и все же большинство людей используют Quicksort вместо Mergesort. Это почему?
@ Джейсон Орендорф: Почему это "easier to hack a mergesort to do it than a quicksort"? Вы можете процитировать какой-нибудь конкретный пример?
@eSKay Сортировка слиянием начинается с группировки исходных данных в отсортированные подмассивы. Если массив изначально содержит некоторые уже отсортированные регионы, вы можете сэкономить много времени, просто обнаружив их наличие до начала. И вы можете сделать это за O (n) раз. Конкретные примеры см. В исходном коде трех упомянутых мной проектов! Лучшим примером может быть Python Timsort, подробно описанный здесь: svn.python.org/view/python/trunk/Objects/… и реализованный в svn.python.org/view/python/trunk/Objects/….
@JasonOrendorff: Не уверен, что я согласен с вашим аргументом в пользу того, что сортировку слиянием легче изменить, чтобы воспользоваться преимуществами уже отсортированных разделов. Шаг разделения быстрой сортировки можно тривиально изменить, чтобы впоследствии проверить, отсортированы ли оба результирующих раздела, и остановить рекурсию, если это так. Это потенциально удваивает количество сравнений, но не меняет временную сложность этого шага O (n).
@j_random_hacker: Если на этапе разбиения проверяется, отсортировано ли оно как после, вероятно, уже слишком поздно и частичный порядок уже нарушен.
@MooingDuck: Я не понимаю. После выбора точки поворота и разбиения вокруг нее во время быстрой сортировки (время O (n)) вы можете затем сравнить каждый элемент с предыдущим элементом, чтобы увидеть, отсортирован ли уже этот (под) массив, снова за время O (n). Если это так, вам не нужно продолжать рекурсию - все просто!
@MooingDuck: Вместо этого вы можете выполнить уже отсортированную проверку перед разделением. Я не могу сказать, что будет быстрее, но оба способа верны.
@j_random_hacker: да, я имел в виду именно это. Но учтите: {10, 2, 3, 4, 5, 6, 7, 8, 1, 9} Несмотря на то, что они уже почти полностью отсортированы, проверка ни перед разделом, ни после него не найдет его. И раздел испортит это до того, как последующие вызовы это проверит. Между тем, сортировка слиянием проверяет отсортированные последовательности на этапах деления до того, как какая-либо из них будет перемещена, а умные будут искать такие прогоны специально на этапе деления (см .: Сортировка по Тиму)
@MooingDuck: А, значит, вы имеете в виду, что quicksort может с пользой пропустить длинные уже отсортированные прогоны - да, я согласен. Посмотрев на страницу Timsort Wikipedia, я понял, что вы (и Джейсон) имели в виду сейчас. Это выглядит очень эффективно, чтобы очистить почти весь существующий частичный порядок.
Анимированные алгоритмы сортировки показывает ряд алгоритмов для 4 различных начальных условий (случайный, почти отсортированный, обратный, несколько уникальных) и может помочь.





На самом деле QuickSort - это O (n2). Его время выполнения средний случай равно O (nlog (n)), но его худший случай равно O (n2), что происходит, когда вы запускаете его в списке, содержащем несколько уникальных элементов. Рандомизация занимает O (n). Конечно, это не меняет худшего случая, это просто не позволяет злоумышленнику заставить вашу сортировку занять много времени.
QuickSort более популярен, потому что он:
На самом деле, есть реализация QuickSort, которая в худшем случае равна O (n * log (n)), а не O (n ^ 2).
Это также зависит от архитектуры компьютера. Quicksort выигрывает от кеширования, а MergeSort - нет.
@ J.F. Себастьян: Скорее всего, это реализации интросорта, а не быстрой сортировки (интросорт запускается как быстрая сортировка и переключается на динамическую сортировку, если он собирается перестать быть n * log (n)).
Вы можете реализовать сортировку слиянием на месте.
@Dark Shikari, я внес некоторые исправления, отредактировав ваш ответ напрямую. См. Эту страницу для наглядной демонстрации пояснений: sorting-algorithms.com/quick-sort В итоге быстрая сортировка по умолчанию хуже всех в списке с несколькими уникальными элементами, а не в списке с обратной сортировкой. Следовательно, этого нельзя избежать, обеспечив случайный порядок списка.
@Dark Shikari, почему вы говорите, что сортировка слиянием требует больше памяти по сравнению с быстрой сортировкой?
Я бы не сказал, что сортировка слиянием использует тонны дополнительной памяти, она использует пространство O (n) ... поскольку она использует вспомогательный массив.
Сортировка слиянием может быть реализована способом, требующим только O (1) дополнительного хранилища, но большинство этих реализаций сильно страдают с точки зрения производительности.
@CristianCiupitu Разве не должно быть наоборот? Сортировка слиянием обращается к данным последовательно, в результате чего строки кэша (как целевого, так и целевого) остаются в памяти. С другой стороны, Quicksort может быть повсюду. Нельзя сказать, что сортировка слиянием лучше, но я не думаю, что это один из ее недостатков.
@Aidiakapi, см. Почему быстрая сортировка на практике лучше других алгоритмов сортировки? и Как быстрая сортировка связана с кешем?.
@CristianCiupitu Я знаю, что Quicksort использует кеш, но я не согласен с вашим утверждением, что сортировка слиянием не работает. Сортировка слиянием обычно сохраняет оба массива в кеше, и он в значительной степени получает доступ к данным исключительно последовательно, что является лучшим случаем для кеша. Быстрая сортировка имеет преимущество перед сортировкой слиянием из-за многих факторов, таких как отсутствие необходимости во вторичном массиве, сценарии двойного поворота. Но локальность кеша - сильная сторона обоих алгоритмов.
@Marcin Реализация сортировки слиянием на месте, как известно, сложна и часто приводит к большему количеству свопов, которые перекрывают повышение эффективности за счет уменьшения использования памяти см. penguin.ewu.edu/cscd300/Topic/AdvSorting/MergeSorts/…
быстрая сортировка НЕ на месте. на месте означает O (1) дополнительной памяти. для быстрой сортировки требуется рекурсия или стек и поэтому используется дополнительная память O (log n)
«На самом деле QuickSort - это O (n2)» - на самом деле это неверно.
@piotrek, да, он есть, потому что дополнительная память предназначена для рекурсии, а не данных. А на 64-битной машине QS никогда не требует больше 64 слов дополнительной памяти, которая выделяется заранее. (Более 64 не будут использоваться даже для плохого набора данных, если сначала будет отсортирована более короткая подпоследовательность, что и делают все практические реализации.)
Какова наихудшая среда выполнения быстрой сортировки при использовании медианы 3 для выбора точки поворота? Будет ли это по-прежнему O (n ^ 2)? Я вижу предложения о том, что версии чистой быстрой сортировки являются O (nlogn). Я предполагаю, что это было бы возможно, только если каждый раз выбирать правильную опору.
См. Здесь сортировку слиянием на месте, которая выполняется за O (n ^ 2) stackoverflow.com/questions/2571049/…
@nfgallimore при использовании медианы 3 для выбора точки поворота? Будет ли это по-прежнему O (n ^ 2)? - Да, конечно ... должно быть ясно, что какой элемент выбран в качестве точки поворота, не может изменить регистр наихудший. версии чистой быстрой сортировки являются O (nlogn) - такого зверька нет. Но нечистые версии, такие как IntroSort и pdqsort (github.com/orlp/pdqsort), имеют наихудший случай O (nlogn).
@jimbalter, можете ли вы привести пример набора данных, где медиана 3 находится в O (nlogn)
@nfgallimore quicksort со средней точкой 3 в упорядоченном списке, конечно же, O (nlogn). Возможно, вы имели в виду не O (nlogn) или это O (n ^ 2) ... Я не буду приводить пример, потому что его создание требует усилий. Примеры не актуальны ... логика есть.
Да, я имел в виду не в O (nlogn)
@JimBalter «да, это на месте, потому что дополнительная память предназначена для рекурсии, а не для данных» - это ложная дихотомия. Рекурсия используется для хранения данных (параметров функции). Если вы не можете выполнить рекурсию, вам все равно понадобится явный стек. Потребляется такой же объем памяти.
^ Это утомительно. Как известно каждому честному человеку, быстрая сортировка «на месте», потому что она записывает отсортированный результат в исходный буфер. Абсурдное заявление piotek о том, что он не на месте, потому что он использует дополнительную память, неверно как семантически, так и на практике. Как я уже сказал, «на 64-битной машине QS никогда не требует более 64 слов дополнительной памяти, которая выделяется заранее». Это нерекурсивная функция, которая использует 64 слова в стеке. Пространство стека уже выделено ... дополнительная память не используется и код выделения отсутствует; указатель стека просто перемещается на 64 слова.
Рандомизация занимает O (n)? O (n log n) в ожидании.
Быстрая сортировка - это самый быстрый алгоритм сортировки на практике, но имеет ряд патологических случаев, из-за которых он может работать так же плохо, как O (n2).
Heapsort гарантированно запускается за O (n * ln (n)) и требует только ограниченного дополнительного хранилища. Но есть много цитат из реальных тестов, которые показывают, что в среднем heapsort значительно медленнее, чем quicksort.
Quicksort имеет лучшую среднюю сложность случая, но в некоторых приложениях это неправильный выбор. Quicksort уязвим для атак типа "отказ в обслуживании". Если злоумышленник может выбрать входные данные для сортировки, он может легко построить набор, который принимает наихудшую временную сложность o (n ^ 2).
Средняя сложность Mergesort и сложность наихудшего случая одинаковы и, как таковые, не страдают той же проблемой. Это свойство сортировки слиянием также делает ее лучшим выбором для систем реального времени - именно потому, что нет патологических случаев, которые заставляли бы ее работать намного, намного медленнее.
По этим причинам я больше поклонник Mergesort, чем Quicksort.
Как Quicksort имеет лучшую среднюю сложность дела? Они оба O (nlgn). Я бы сказал, что злоумышленник не будет предоставлять входные данные для какого-либо алгоритма сортировки ... но, чтобы не предполагать безопасность посредством неизвестности, предположим, что он мог. Хотя время работы n ^ 2 хуже, чем nlgn, это не настолько хуже, что веб-сервер выйдет из строя из-за одной атаки. Фактически, аргумент DOS практически равен нулю, потому что любой веб-сервер уязвим для DDOS-атаки, и для злоумышленника более вероятно, что злоумышленник будет использовать распределенную сеть хостов, все TCP SYN-лавинная.
«Quicksort имеет лучшую среднюю сложность дела» - нет, это не так.
От запись в Википедии о Quicksort:
Quicksort also competes with mergesort, another recursive sort algorithm but with the benefit of worst-case Θ(nlogn) running time. Mergesort is a stable sort, unlike quicksort and heapsort, and can be easily adapted to operate on linked lists and very large lists stored on slow-to-access media such as disk storage or network attached storage. Although quicksort can be written to operate on linked lists, it will often suffer from poor pivot choices without random access. The main disadvantage of mergesort is that, when operating on arrays, it requires Θ(n) auxiliary space in the best case, whereas the variant of quicksort with in-place partitioning and tail recursion uses only Θ(logn) space. (Note that when operating on linked lists, mergesort only requires a small, constant amount of auxiliary storage.)
Объяснение Википедии:
Typically, quicksort is significantly faster in practice than other Θ(nlogn) algorithms, because its inner loop can be efficiently implemented on most architectures, and in most real-world data it is possible to make design choices which minimize the probability of requiring quadratic time.
Я думаю, что есть проблемы с объемом памяти, необходимым для Mergesort (то есть Ω (n)), которых нет в реализациях быстрой сортировки. В худшем случае они занимают одинаковое количество алгоритмического времени, но для сортировки слиянием требуется больше памяти.
Худший случай быстрой сортировки - O (n), mergesort O (n log n) - так что здесь большая разница.
в худшем случае быстрая сортировка - O (n ^ 2) - не могу отредактировать мой предыдущий комментарий и допустил опечатку
Комментарии @ paul23 можно удалять. Кроме того, ответ уже был направлен на вашу точку зрения: «в большинстве реальных данных можно сделать выбор дизайна, который минимизирует вероятность того, что потребуется квадратичное время»
Хотя они оба принадлежат к одному классу сложности, это не означает, что у них обоих одинаковая среда выполнения. Быстрая сортировка обычно быстрее, чем сортировка слиянием, просто потому, что проще написать точную реализацию, а операции, которые она выполняет, могут выполняться быстрее. Это потому, что эта быстрая сортировка обычно быстрее, люди используют ее вместо сортировки слиянием.
Тем не мение! Я лично часто использую сортировку слиянием или вариант быстрой сортировки, который ухудшается до сортировки слиянием, когда быстрая сортировка работает плохо. Помните. Быстрая сортировка - это только O (n log n) на средний. Худший случай - O (n ^ 2)! Сортировка слиянием всегда O (n log n). В случаях, когда производительность или скорость реагирования в реальном времени являются обязательными и ваши входные данные могут поступать из злонамеренного источника, вам не следует использовать обычную быструю сортировку.
Я хотел бы добавить, что из трех упомянутых до сих пор алгоритмов (сортировка слиянием, быстрая сортировка и сортировка кучей) стабильной является только сортировка слиянием. То есть порядок не меняется для значений, имеющих одинаковый ключ. В некоторых случаях это желательно.
Но, по правде говоря, в практических ситуациях большинству людей нужна только хорошая средняя производительность, а быстрая сортировка ... быстрая =)
У всех алгоритмов сортировки есть свои плюсы и минусы. См. Статья в Википедии об алгоритмах сортировки для хорошего обзора.
Му! Quicksort не лучше, чем mergesort, он больше подходит для других приложений.
Mergesort is worth considering if speed is of the essence, bad worst-case performance cannot be tolerated, and extra space is available.1
Вы заявили, что они «Они оба О (нлогн) […]». Это не правильно. «В худшем случае Quicksort использует примерно n ^ 2/2 сравнений». 1.
Однако наиболее важным свойством, согласно моему опыту, является простая реализация последовательного доступа, которую вы можете использовать при сортировке при использовании языков программирования с императивной парадигмой.
1 Седжвик, Алгоритмы
Сортировку слиянием можно реализовать на месте, так что не требуется дополнительное пространство. Например, с двусвязным списком: stackoverflow.com/questions/2938495/…
Quicksort имеет время выполнения для худшего случая O (п2) и среднее время выполнения O (пlogп). Однако сортировка слиянием лучше во многих сценариях, потому что многие факторы влияют на время выполнения алгоритма, и, если взять их все вместе, быстрая сортировка побеждает.
В частности, часто цитируемая среда выполнения алгоритмов сортировки относится к количеству сравнений или количеству свопов, необходимых для выполнения сортировки данных. Это действительно хороший показатель производительности, тем более, что он не зависит от базовой конструкции оборудования. Однако другие вещи, такие как местонахождение ссылки (т.е. читаем ли мы много элементов, которые, вероятно, находятся в кеше?), Также играют важную роль на текущем оборудовании. В частности, быстрая сортировка требует небольшого дополнительного места и демонстрирует хорошую локальность кеша, что во многих случаях делает ее быстрее, чем сортировка слиянием.
Кроме того, очень легко избежать наихудшего времени выполнения быстрой сортировки, равного O (п2), почти полностью, используя соответствующий выбор точки поворота - например, выбор ее наугад (это отличная стратегия).
На практике многие современные реализации быстрой сортировки (в частности, std::sort из libstdC++) на самом деле являются интросорт, теоретический худший случай которого равен O (пlogп), как и сортировка слиянием. Это достигается за счет ограничения глубины рекурсии и переключения на другой алгоритм (heapsort), когда он превышает logп.
В статье в Википедии говорится, что он переключается на heapsort, а не mergesort ... просто к сведению.
@Sev:… как и в оригинальной статье. Спасибо, что указали на ошибку. - Не то чтобы это действительно важно, поскольку их асимптотическое время работы одинаково.
почему этот ответ выбран как правильный? Все, что он объясняет, - это то, как быстро устранять проблемы. Он до сих пор не объясняет, почему быстрая сортировка используется чаще, чем другие? Ответ: «Быстрая сортировка используется чаще, чем другая, потому что после одной глубины вы можете переключиться на heapsort»? .. почему бы тогда не использовать heapsort в первую очередь? .. просто пытаюсь понять ...
@ p1 Хороший вопрос. Реальный ответ заключается в том, что в среднем для средних данных быстрая сортировка быстрее, чем сортировка слиянием (и сортировка кучи, если на то пошло), и хотя худший случай быстрой сортировки медленнее, чем сортировка слиянием, этот худший случай можно очень легко смягчить. (отсюда и мой ответ).
@KonradRudolph Что вы имеете в виду, когда говорите, что "быстрая сортировка быстрее, чем сортировка слиянием"? Вы говорите о теоретическом анализе или о реализации на практике? Насколько я понимаю, сортировка слиянием лучше с точки зрения анализа, но из-за кеширования быстрая сортировка часто предпочтительнее (без учета всех других факторов).
@ user1520427 Я говорил о производительности на практике. Я не проводил тщательного анализа (то есть не только с точки зрения большого О) количества сравнений, необходимых при сортировке слиянием, - я подозреваю, что оно может быть даже меньше, чем в среднем при быстрой сортировке.
Quicksort также лучше с точки зрения памяти.
@Shashwat Это действительно так по сравнению с сортировкой слиянием, но мой ответ применяется в более общем плане по сравнению с другими методами сортировки, и там аспект памяти больше не соответствует действительности. Например, heapsort использует меньше памяти, чем quicksort (O (1) vs O (log n)).
По крайней мере, два человека здесь упомянули, что быстрая сортировка лучше, чем сортировка слиянием для кешей. Я чувствую, что это неправильно. Последний вызов метода разделения в быстрой сортировке может обмениваться элементами в массиве, которые нигде не находятся рядом друг с другом, что вызывает промахи кеша.
В одном я не согласен: случайный выбор опорных точек - не лучшая стратегия. Во-первых, потому что выбор любого отдельного элемента в качестве точки поворота в среднем не даст очень хороших результатов (статистически вы получите лучшее поведение, выбрав медианное значение из трех элементов), а во-вторых, потому что создание случайного числа дорого. При интросорте вам не нужна рандомизация, чтобы защититься от убийственных последовательностей в среднем из трех.
@Julian Настоящий статистический анализ немного сложнее, и я забыл подробности, но, если у вас нет хорошей ссылки, я не верю, что медиана из трех лучше, чем случайная точка поворота (вероятность получения супер-O (n log n ) время выполнения можно доказать экспоненциально низким). Фактически, на практике случайный поворот выполняет отлично. Его главный недостаток (и почему он редко используется в стандартных библиотеках) заключается в том, что он изменяет глобальное случайное состояние. Да, эффективность ГСЧ - это проблема, но есть очень эффективные ГПСЧ.
Вот ваша ссылка: citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.8162. Честно говоря, я считаю очевидным, что медиана трех элементов с большей вероятностью будет ближе к середине диапазона, чем любой отдельный элемент.
@Julian Это просто только если вы выберете эти три элемента наугад! И ваша ссылка (которую я очень хорошо знаю) не упоминает ожидаемое время выполнения рандомизированной быстрой сортировки. Фактически, здесь вообще не обсуждается рандомизированная быстрая сортировка, за исключением того, что отмечается то же предостережение, упомянутое в моем предыдущем комментарии.
Это так, см. «Выбор элемента разделения» на странице 1254. И данные рандомизируются для начала (в противном случае не было бы необходимости сортировать их), поэтому выбираете ли вы первые три элемента, три случайных элемента или первый-средний -в последнем случае вы получите такое же социастическое поведение. Однако первый-средний-последний лучше справляется с данными, которые уже несколько отсортированы или отсортированы обратным образом.
@ Джулиан «несортированный» ≠ «случайный». Раздел, который вы цитируете, не сравнивает время выполнения, он сравнивает количество сравнений. Очевидно, что здесь мы можем добиться большего успеха, чем случайный - простой выбор истинной медианы каждый раз дает лучший результат, но это непомерно дорого (как объясняется в том же разделе). Вычисление медианы трех случайных элементов - в общем случае - абсолютно не обладает такими же стохастическими свойствами, как выбор трех фиксированных элементов (хотя я признаю, что на практике разница очень незначительна).
Быстрая сортировка НЕ лучше сортировки слиянием. При O (n ^ 2) (худший случай, который случается редко) быстрая сортировка потенциально намного медленнее, чем O (nlogn) сортировки слиянием. Quicksort имеет меньше накладных расходов, поэтому для малых n и медленных компьютеров это лучше. Но сегодня компьютеры настолько быстры, что дополнительные накладные расходы на сортировку слиянием пренебрежимо малы, а риск очень медленной быстрой сортировки намного перевешивает незначительные накладные расходы на сортировку слиянием в большинстве случаев.
Кроме того, сортировка слиянием оставляет элементы с одинаковыми ключами в их исходном порядке, что является полезным атрибутом.
Во втором предложении говорится: «... сортировка слиянием потенциально намного медленнее, чем ... сортировка слиянием». Предположительно, первая ссылка должна быть на быструю сортировку.
Сортировка слиянием стабильна только в том случае, если алгоритм слияния стабилен; это не гарантируется.
@Clearer Если для сравнения используется <=, а не <, то это гарантировано, и нет причин не делать этого.
@JimBalter Я мог бы легко придумать нестабильный алгоритм слияния (например, быстрая сортировка будет выполнять эту роль). Причина, по которой быстрая сортировка во многих случаях быстрее, чем сортировка слиянием, - это нет из-за уменьшения накладных расходов, но из-за того, как быстрая сортировка получает доступ к данным, что намного удобнее для кеширования, чем стандартная сортировка слиянием.
@Clearer quicksort - это не сортировка слиянием ... ваше утверждение от 21 декабря 2014 года, на которое я ответил, было строго о сортировке слиянием и о том, стабильна ли она. быстрая сортировка и то, что быстрее, совершенно не имеет отношения к вашему комментарию или моему ответу. Конец обсуждения для меня ... снова и снова.
С помощью быстрой сортировки можно легко объединить два массива в один (скопировать массивы в массив и отсортировать его - готово). Это очень плохой способ слияния, но он показывает, что можно сделать алгоритм слияния нестабильным (или эффективным). Мой комментарий о том, почему быстрая сортировка может быть быстрее, чем сортировка слиянием, был нацелен на исходный ответ.
В мире c / C++, когда я не использую контейнеры stl, я обычно использую быструю сортировку, потому что она построена во время выполнения, а сортировка слиянием - нет.
Поэтому я считаю, что во многих случаях это просто путь наименьшего сопротивления.
Кроме того, производительность может быть намного выше с помощью быстрой сортировки в случаях, когда весь набор данных не помещается в рабочий набор.
На самом деле, если вы говорите о библиотечной функции qsort (), она может быть реализована или не реализована как быстрая сортировка.
Конрад, извините за то, что я немного задолбался, но где вы найдете эту гарантию? Я не могу найти его в стандарте ISO C или в стандарте C++.
qsort в GNU libc - это сортировка слиянием, за исключением случаев, когда количество элементов действительно гигантское или временная память не может быть выделена. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
Как отмечали другие, наихудший случай Quicksort - O (n ^ 2), в то время как mergesort и heapsort остаются на O (nlogn). В среднем, однако, все три - O (nlogn); так что в подавляющем большинстве случаев они сопоставимы.
Что делает Quicksort лучше в среднем, так это то, что внутренний цикл подразумевает сравнение нескольких значений с одним, в то время как для двух других оба термина различны для каждого сравнения. Другими словами, Quicksort выполняет вдвое меньше операций чтения, чем два других алгоритма. На современных процессорах производительность в значительной степени зависит от времени доступа, поэтому в конечном итоге Quicksort оказывается отличным выбором.
Как отмечали многие, в среднем производительность быстрой сортировки выше, чем сортировки слиянием. Но это верно, только если вы предполагаете постоянное время для доступа к любой части памяти по запросу.
В RAM это предположение, как правило, неплохо (не всегда верно из-за кешей, но не так уж и плохо). Однако, если ваша структура данных достаточно велика, чтобы жить на диске, тогда быстрая сортировка получает убит из-за того, что ваш средний диск выполняет что-то вроде 200 случайных поисков в секунду. Но тот же самый диск не имеет проблем с последовательным чтением или записью мегабайт данных в секунду. Именно это и делает mergesort.
Поэтому, если данные должны быть отсортированы на диске, вы действительно хотите использовать некоторые варианты сортировки слиянием. (Обычно вы быстро сортируете подсписки, а затем начинаете объединять их вместе выше некоторого порога размера.)
Более того, если вам нужно выполнить что-либо с наборами данных такого размера, хорошо подумайте, как избежать обращений к диску. Например, поэтому стандартным советом является отбрасывать индексы перед загрузкой больших объемов данных в базы данных, а затем перестраивать индекс позже. Поддержание индекса во время загрузки означает постоянный поиск диска. Напротив, если вы отбрасываете индексы, то база данных может перестроить индекс, сначала отсортировав информацию, с которой нужно работать (конечно, используя сортировку слиянием!), А затем загрузив ее в структуру данных BTREE для индекса. (BTREE, естественно, хранятся в порядке, поэтому вы можете загрузить один из отсортированного набора данных, сделав несколько попыток на диск.)
Было несколько случаев, когда понимание того, как избежать обращений к диску, позволяло мне делать работу по обработке данных часами, а не днями или неделями.
Очень хорошо, не задумывался о предположениях, сделанных для доступа к структуре данных. Хорошее понимание :)
Можете ли вы объяснить, что вы подразумеваете под «поиском на диск», означает ли это поиск какого-то единственного значения, когда данные хранятся на диске?
@JamesWierzba Я понял из контекста, что он означает «поиск места на диске». «Поиск» на вращающемся дисковом устройстве означает захват считывающей головки и перемещение ее на новый абсолютный адрес, что является заведомо медленной операцией. Когда вы обращаетесь к данным в том порядке, в котором они были сохранены, аппаратному обеспечению диска не нужно искать, оно просто перемещается с высокой скоростью, последовательно считывая элементы.
Не совсем так. Quicksort проверяет линейность входных данных и позволяет кэшировать больше доступа к диску, чем Mergesort. Этот ответ говорит об обратном.
Кто-нибудь может объяснить это поподробнее? Вот как я это вижу: Быстрая сортировка: если мы идем со случайным поворотом, стек вызовов имеет фрагменты массива, разделенные случайным образом. Для этого требуется произвольный доступ. Однако для каждого вызова в стеке левый и правый указатели перемещаются последовательно. Я предполагаю, что они будут храниться в кеше. Свопы - это снова операции с информацией, которая находится в кеше (и в конечном итоге записывается на диск). (продолжение в моем следующем комментарии)
MergeSort: стек вызовов строится путем логарифмического деления массива в глубину. И, слияние снизу (самая левая часть массива) вверх. Разделить часть массива можно только с помощью индексов. Таким образом, нет необходимости случайным образом перемещаться по массиву. Однако при слиянии дополнительный / результирующий массив будет построен / выгружен при последовательной записи. Это правильно?
Просто вклад избегая накладных расходов на чтение / запись диска дорогостоящий: при сортировке очень больших данных, требующих доступа к диску, выгодно переключать направление сортировки для каждого прохода. То есть на самом верхнем уровне петли, когда вы переходите от 0 к n и в следующий раз, когда вы переходите от n к 0. Это дает преимущество отступления (сортировки) блоков данных, которые уже доступны в памяти (кэше), и двойной атаки только для одного доступа к диску. Я думаю, что большинство СУБД используют этот метод оптимизации.
@anujpradhan "Это то, чему книга не может научить" - О, правда? Это закон физики? Потому что я узнал это из книг.
Кто-нибудь доработает термин «падение индексов»?
При прочих равных, я ожидаю, что большинство людей будут использовать то, что наиболее удобно, и это, как правило, qsort (3). В остальном быстрая сортировка, как известно, очень быстрая для массивов, точно так же, как сортировка слиянием является обычным выбором для списков.
Мне интересно, почему так редко можно увидеть основание или сортировку по корзинам. Они O (n), по крайней мере, в связанных списках, и все, что требуется, - это какой-то метод преобразования ключа в порядковое число. (строки и числа с плавающей запятой работают нормально.)
Я думаю, причина в том, как преподают информатику. Мне даже пришлось продемонстрировать моему лектору по анализу алгоритмов, что действительно можно сортировать быстрее, чем O (n log (n)). (У него было доказательство того, что вы не можете сортировать сравнение быстрее, чем O (n log (n)), что верно.)
В других новостях числа с плавающей запятой можно отсортировать как целые числа, но после этого вам нужно будет перевернуть отрицательные числа.
Редактировать: На самом деле, вот еще более опасный способ сортировки чисел с плавающей запятой как целых чисел: http://www.stereopsis.com/radix.html. Обратите внимание, что трюк с переворачиванием битов можно использовать независимо от того, какой алгоритм сортировки вы действительно используете ...
Я видел свою долю видов системы счисления. Но его довольно сложно использовать, потому что при правильном анализе его время выполнения будет нет O (n), так как оно зависит не только от количества входных элементов. В общем, очень сложно сделать такие сильные прогнозы, что радииксная сортировка должна быть эффективной в отношении входных данных.
Это является O (n), где n - размер ввода общий, то есть включая размер элементов. Это правда, что вы можете реализовать это так, что вам придется заполнять множеством нулей, но использовать плохую реализацию для сравнения - это ерунда. (Тем не менее, реализация может быть сложной, ymmv.)
Обратите внимание, что если вы используете GNU libc, qsort является сортировкой слиянием.
Эээ, если быть точным, это сортировка слиянием, если не может быть выделена необходимая временная память. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
"и все же большинство людей используют Quicksort вместо Mergesort. Почему это так?"
Одна психологическая причина, которая не была указана, заключается в том, что Quicksort имеет более умное название. т.е. хороший маркетинг.
Да, быстрая сортировка с тройным разбиением, вероятно, является одним из лучших алгоритмов сортировки общего назначения, но нельзя не признать тот факт, что «быстрая» сортировка кажется намного более мощной, чем сортировка «слиянием».
Не отвечает на вопрос, что лучше. Название алгоритма не имеет значения, чтобы определить, какой из них лучше.
Трудно сказать. Худший из MergeSort - это n (log2n) -n + 1, что верно, если n равно 2 ^ k (я уже доказал это). И для любого n это между (n lg n - n + 1) и (n lg n + n + O (lg n)). Но для quickSort лучше всего nlog2n (также n равно 2 ^ k). Если вы разделите Mergesort на quickSort, он будет равен единице, когда n бесконечно. это как если бы наихудший вариант MergeSort лучше, чем лучший вариант QuickSort, почему мы используем быструю сортировку? Но помните, что MergeSort не на месте, для этого требуется 2n мемройского пространства. не включайте в анализ алгоритма. Одним словом, MergeSort действительно быстрее, чем быстрая сортировка, но на самом деле вам нужно учитывать пространство памяти, стоимость копирования массива, слияние происходит медленнее, чем быстрая сортировка. Я однажды сделал эксперимент, в котором мне дали 1000000 цифр в java классом Random, и потребовалось 2610 мс при сортировке слиянием, 1370 мс при быстрой сортировке.
Ответ был бы слегка склонен к быстрой сортировке по отношению к изменениям, внесенным с помощью DualPivotQuickSort для примитивных значений. Он используется в JAVA 7 для сортировки в java.util.Arrays
It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.
Вы можете найти внедрение JAVA7 здесь - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java
Дальнейшее замечательное чтение по DualPivotQuickSort - http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628
Быстрая сортировка - это наихудший случай O (n ^ 2), однако средний случай последовательно выполняет сортировку слиянием. Каждый алгоритм - O (nlogn), но вы должны помнить, что, говоря о Big O, мы опускаем факторы более низкой сложности. Быстрая сортировка имеет значительные улучшения по сравнению с сортировкой слиянием, когда дело доходит до постоянных факторов.
Сортировка слиянием также требует O (2n) памяти, в то время как быстрая сортировка может быть выполнена на месте (требуется только O (n)). Это еще одна причина того, что быстрая сортировка обычно предпочтительнее сортировки слиянием.
Дополнительная информация:
Худший случай быстрой сортировки происходит, когда точка поворота выбрана неправильно. Рассмотрим следующий пример:
[5, 4, 3, 2, 1]
Если точка поворота выбрана как наименьшее или наибольшее число в группе, тогда быстрая сортировка будет выполняться за O (n ^ 2). Вероятность выбора элемента, который находится в 25% наибольшего или наименьшего списка, составляет 0,5. Это дает алгоритму 0,5 шанса быть хорошим поворотом. Если мы используем типичный алгоритм поворота выбора (скажем, выбирая случайный элемент), мы имеем 0,5 шанса выбрать хороший стержень для каждого выбора оси. Для коллекций большого размера вероятность всегда выбрать плохой пивот составляет 0,5 * n. На основе этой вероятности быстрая сортировка эффективна для среднего (и типичного) случая.
O (2n) == O (n). Правильное утверждение состоит в том, что Mergesort требует O (n) дополнительной памяти (точнее, ей требуется n / 2 вспомогательной памяти). А это не относится к связанным спискам.
@JimBalter Сэр, не могли бы вы поделиться с нами своими блестящими и стоящими идеями об их выступлениях в качестве ответа на вопрос? Заранее спасибо.
Чем хорош Quicksort?
Всегда ли Quicksort лучше Mergesort?
Не совсем.
Примечание: В java функция Arrays.sort () использует Quicksort для примитивных типов данных и Mergesort для типов данных объекта. Поскольку объекты потребляют накладные расходы памяти, добавленные небольшие накладные расходы для Mergesort не могут быть проблемой с точки зрения производительности.
Ссылка: Посмотрите видеоролики QuickSort Неделя 3, Принстонский курс алгоритмов на Coursera
«Это можно смягчить путем случайного перемешивания перед запуском сортировки» - нет, это было бы дорого. Вместо этого используйте случайные точки поворота.
Небольшие дополнения к сортировкам быстрого и слияния.
Также это может зависеть от вида сортировки элементов. Если доступ к элементам, своп и сравнение не являются простыми операциями, такими как сравнение целых чисел в памяти плоскости, тогда сортировка слиянием может быть предпочтительным алгоритмом.
Например, мы сортируем элементы по сетевому протоколу на удаленном сервере.
Кроме того, в настраиваемых контейнерах, таких как «связанный список», нет преимуществ быстрой сортировки. 1. Сортировка слиянием в связанном списке, дополнительная память не требуется. 2. Доступ к элементам в быстрой сортировке не последовательный (в памяти)
Общий алгоритм сортировки слиянием:
На верхнем уровне слияние 2 отсортированных подмассивов включает работу с N элементами.
На один уровень ниже каждая итерация шага 3 включает в себя работу с N / 2 элементами, но вам придется повторить этот процесс дважды. Итак, вы все еще имеете дело с 2 * N / 2 == N элементами.
На один уровень ниже вы объединяете 4 * N / 4 == N элементов и так далее. Каждая глубина в рекурсивном стеке включает в себя слияние одинакового количества элементов для всех вызовов этой глубины.
Вместо этого рассмотрим алгоритм быстрой сортировки:
На верхнем уровне вы имеете дело с массивом размера N. Затем вы выбираете одну точку поворота, помещаете ее в правильное положение и затем можете полностью игнорировать ее для остальной части алгоритма.
На один уровень ниже вы имеете дело с двумя подмассивами, которые имеют общий размер N-1 (то есть за вычетом более ранней точки поворота). Вы выбираете точку поворота для каждого подмассива, что дает до 2 дополнительных точек поворота.
На один уровень ниже вы имеете дело с 4 подмассивами с комбинированным размером N-3 по тем же причинам, что и выше.
Потом Н-7 ... Потом Н-15 ... Потом Н-32 ...
Глубина вашего рекурсивного стека остается примерно такой же (logN). С сортировкой слиянием вы всегда имеете дело со слиянием N элементов на каждом уровне рекурсивного стека. Однако при быстрой сортировке количество элементов, с которыми вы имеете дело, уменьшается по мере того, как вы спускаетесь вниз по стеку. Например, если вы посмотрите на глубину в середине рекурсивного стека, количество элементов, с которыми вы имеете дело, равно N - 2 ^ ((logN) / 2)) == N - sqrt (N).
Отказ от ответственности: при сортировке слиянием, поскольку вы каждый раз делите массив на 2 точно равных части, рекурсивная глубина точно равна logN. При быстрой сортировке, поскольку ваша точка поворота вряд ли будет точно в середине массива, глубина вашего рекурсивного стека может быть немного больше, чем logN. Я не проводил математических расчетов, чтобы увидеть, насколько большую роль этот фактор и фактор, описанный выше, на самом деле играют в сложности алгоритма.
То, что повороты не являются частью сортировок на следующем уровне, не является причиной большей производительности QS. См. Другие ответы для получения дополнительной информации.
@JimBalter Какие «другие ответы» вы имеете в виду? Верхний ответ просто говорит, что QS «требует немного дополнительного места и демонстрирует хорошую локальность кеша», но не дает никаких объяснений, почему это так, и не дает никаких ссылок. Второй ответ просто говорит, что сортировка слиянием лучше для больших наборов данных.
Вы перемещаете столбы ворот от того, почему QS более эффективен, к объяснению основных фактов о том, как он работает. Для этого нужны ответы на другие вопросы: stackoverflow.com/questions/9444714/… ... Надеюсь, вам хватит; Я не буду отвечать дальше.
Быстрая сортировка - это алгоритм сортировки на месте, поэтому он лучше подходит для массивов. С другой стороны, сортировка слиянием требует дополнительного хранилища O (N) и больше подходит для связанных списков.
В отличие от массивов, в список понравившихся мы можем вставлять элементы посередине с пробелом O (1) и временем O (1), поэтому операцию слияния в сортировке слиянием можно реализовать без лишнего пробела. Однако выделение и освобождение дополнительного пространства для массивов отрицательно сказывается на времени выполнения сортировки слиянием. Сортировка слиянием также отдает предпочтение связному списку, поскольку доступ к данным осуществляется последовательно, без особого произвольного доступа к памяти.
С другой стороны, быстрая сортировка требует большого количества случайного доступа к памяти, и с массивом мы можем напрямую обращаться к памяти без какого-либо обхода, как того требуют связанные списки. Кроме того, быстрая сортировка при использовании для массивов имеет хорошую локальность ссылок, поскольку массивы хранятся в памяти непрерывно.
Несмотря на то, что средняя сложность обоих алгоритмов сортировки составляет O (NlogN), обычно люди для обычных задач используют массив для хранения, и по этой причине быстрая сортировка должна быть алгоритмом выбора.
Обновлено: я только что обнаружил, что худший / лучший / средний случай сортировки слиянием всегда nlogn, но быстрая сортировка может варьироваться от n2 (худший случай, когда элементы уже отсортированы) до nlogn (avg / лучший случай, когда pivot всегда делит массив на два половинки).
Это довольно старый вопрос, но, поскольку я недавно имел дело с обоими, вот мои 2c:
Для сортировки слиянием в среднем требуется ~ N log N. Для уже (почти) отсортированных отсортированных массивов это сокращается до 1/2 N log N, поскольку при объединении мы (почти) всегда выбираем «левую» часть 1/2 N раз, а затем просто копируем правые 1/2 N элементов. Кроме того, я могу предположить, что уже отсортированный ввод заставляет предсказатель ветвления процессора сиять, но правильно угадывает почти все ветки, что предотвращает остановку конвейера.
Для быстрой сортировки в среднем требуется ~ 1,38 N log N. Он не сильно выигрывает от уже отсортированного массива с точки зрения сравнений (однако он делает это с точки зрения свопов и, вероятно, с точки зрения прогнозов ветвлений внутри ЦП).
Мои тесты на достаточно современном процессоре показывают следующее:
Когда функция сравнения является функцией обратного вызова (как в реализации qsort () libc), быстрая сортировка медленнее, чем сортировка слиянием, на 15% при случайном вводе и на 30% для уже отсортированного массива для 64-битных целых чисел.
С другой стороны, если сравнение не является обратным вызовом, мой опыт показывает, что быстрая сортировка превосходит сортировку слиянием до 25%.
Однако, если в вашем (большом) массиве очень мало уникальных значений, сортировка слиянием в любом случае начинает выигрывать по сравнению с быстрой сортировкой.
Так что, возможно, суть в следующем: если сравнение обходится дорого (например, функция обратного вызова, сравнение строк, сравнение многих частей структуры, в основном доходящих до второго-третьего-четвертого «если», чтобы иметь значение) - скорее всего, вы станете лучше с сортировкой слиянием. Для более простых задач быстрая сортировка будет быстрее.
Тем не менее, все ранее сказанное верно: - Quicksort может быть N ^ 2, но Sedgewick утверждает, что хорошая рандомизированная реализация имеет больше шансов, что компьютер, выполняющий сортировку, будет поражен молнией, чем пойти N ^ 2 - Mergesort требует дополнительного места
Превосходит ли qsort сортировку слиянием даже для отсортированных входных данных, если сравнение дешево?
В отличие от сортировки слиянием, быстрая сортировка не использует вспомогательное пространство. В то время как сортировка слиянием использует вспомогательное пространство O (n). Но сортировка слиянием имеет наихудшую временную сложность O (nlogn), тогда как сложность наихудшего случая быстрой сортировки составляет O (n ^ 2), что происходит, когда массив уже отсортирован.
Нет, худшего случая QuickSort не происходит, когда массив уже отсортирован, если только вы не используете первый или последний элемент в качестве точки поворота, но никто этого не делает.
Когда я экспериментировал с обоими алгоритмами сортировки, подсчитывая количество рекурсивных вызовов, У быстрой сортировки постоянно меньше рекурсивных вызовов, чем у сортировки слиянием. Это связано с тем, что у быстрой сортировки есть точки поворота, и они не включаются в следующие рекурсивные вызовы. Таким образом, быстрая сортировка может достичь рекурсивного базового случая быстрее, чем сортировка слиянием.
Повороты не имеют ничего общего с тем, почему в QS меньше рекурсивных вызовов ... это потому, что половина рекурсии QS - это хвостовая рекурсия, которую можно исключить.
Одна из причин более философская. Quicksort - это философия сверху-> вниз. Из n элементов для сортировки остается n! возможности. С двумя разделами m & n-m, которые являются взаимоисключающими, количество возможностей уменьшается на несколько порядков. м! * (н-м)! на несколько порядков меньше n! один. представьте 5! против 3! * 2 !. 5! имеет в 10 раз больше возможностей, чем 2 раздела по 2 и 3 в каждом. и экстраполировать на 1 миллион факториалов против 900К! * 100К! vs. Итак, вместо того, чтобы беспокоиться об установлении какого-либо порядка в пределах диапазона или раздела, просто установите порядок на более широком уровне в разделах и уменьшите возможности внутри раздела. Любой порядок, установленный ранее в пределах диапазона, будет нарушен позже, если сами разделы не являются взаимоисключающими.
Любой подход восходящего порядка, такой как сортировка слиянием или сортировка кучей, похож на подход рабочих или служащих, когда сравнивать на микроскопическом уровне рано. Но этот порядок неизбежно будет утерян, как только впоследствии будет обнаружен промежуточный элемент. Эти подходы очень стабильны и предельно предсказуемы, но требуют некоторой дополнительной работы.
Быстрая сортировка похожа на управленческий подход, когда человек изначально не заботится о каком-либо порядке, а только о соблюдении широкого критерия без учета порядка. Затем перегородки сужаются, пока не получится отсортированный набор. Настоящая проблема в Quicksort - найти раздел или критерий в темноте, когда вы ничего не знаете об элементах для сортировки. Вот почему нам нужно либо приложить некоторые усилия, чтобы найти среднее значение, либо выбрать 1 случайным образом, либо какой-то произвольный «управленческий» подход. Поиск идеальной медианы может потребовать значительных усилий и снова привести к глупому подходу снизу вверх. Таким образом, Quicksort говорит, что просто выберите случайную точку поворота и надейтесь, что она будет где-то посередине, или поработайте, чтобы найти медианное значение 3, 5 или что-то большее, чтобы найти лучшую медиану, но не планируйте быть идеальным и не теряйте в любое время при первоначальном заказе. Кажется, это хорошо, если вам повезет, или иногда снижается до n ^ 2, когда вы не получаете медиану, а просто рискуете. В любом случае данные случайны. верно. Так что я больше согласен с логическим подходом быстрой сортировки сверху -> вниз, и оказывается, что шанс, который он берет в отношении выбора и сравнения поворотных точек, которые он сохраняет ранее, кажется, работает лучше в большем количестве раз, чем любой дотошный и тщательный стабильный подход снизу -> вверх, например Сортировка слиянием. Но
Quicksort извлекает выгоду из случайности выбора опорных точек. Случайный поворот, естественно, будет иметь тенденцию к разделению 50:50 и вряд ли будет постоянно двигаться к одной из крайностей. Постоянный коэффициент nlogn довольно низок до тех пор, пока среднее разбиение не будет составлять 60-40 или даже до 70-30.
Это полная чушь. quicksort используется из-за своей производительности, а не «философии» ... и утверждения о том, что «порядок неизбежно будет потерян», просто ложны.
Учитывайте как временную, так и пространственную сложность. Для сортировки слиянием: Временная сложность: O (nlogn), Сложность пространства: O (nlogn)
Для быстрой сортировки: Сложность времени: O (n ^ 2), Сложность пространства: O (n)
Теперь они оба выигрывают в одном сценарии каждый. Но, используя случайный поворот, вы почти всегда можете уменьшить временную сложность быстрой сортировки до O (nlogn).
Таким образом, во многих приложениях предпочтительнее использовать быструю сортировку вместо сортировки слиянием.
Это частый вопрос, который задают в интервью, что, несмотря на лучшую производительность сортировки слиянием в худшем случае, быстрая сортировка считается лучше, чем сортировка слиянием, особенно для больших входных данных. Есть определенные причины, по которым быстрая сортировка лучше:
1- Вспомогательное пространство: Быстрая сортировка - это алгоритм сортировки на месте. Сортировка на месте означает, что для выполнения сортировки не требуется дополнительное пространство для хранения. Сортировка слиянием, с другой стороны, требует временного массива для слияния отсортированных массивов и, следовательно, его нет на месте.
2- Худший случай: Худшего случая быстрой сортировки O(n^2) можно избежать, используя рандомизированную быструю сортировку. Этого легко можно избежать с большой вероятностью, выбрав правильный стержень. Получение поведения усредненного кейса путем выбора правильного сводного элемента позволяет ему улучшить производительность и стать таким же эффективным, как сортировка слиянием.
3- Местонахождение ссылки: Quicksort, в частности, демонстрирует хорошую локальность кеша, и это делает его быстрее, чем сортировка слиянием во многих случаях, например, в среде виртуальной памяти.
4- Хвостовая рекурсия: QuickSort является хвостовой рекурсивной, а сортировка слиянием - нет. Хвостовая рекурсивная функция - это функция, в которой рекурсивный вызов - это последнее, что выполняет функция. Хвостовые рекурсивные функции считаются лучше, чем нехвостовые рекурсивные функции, поскольку хвостовая рекурсия может быть оптимизирована компилятором.
Это не очень хороший вопрос для собеседования. Реальные данные не перетасовываются: они часто содержат много порядка, который может использовать интеллектуальная сортировка, и хотя ни один алгоритм не делает это автоматически, легче взломать сортировку слиянием, чем быструю сортировку.
qsortGNU libc,list.sortPython иArray.prototype.sortв JavaScript Firefox - все это усовершенствованные виды слияния. (GNU STLsortвместо этого использует Introsort, но это может быть связано с тем, что в C++ подкачка потенциально выигрывает перед копированием.)