Сложность Quicksort, когда все элементы одинаковы?

У меня есть массив из n чисел, которые одинаково.Я применяю быструю сортировку. Какой должна быть временная сложность сортировки в данном случае.

я вытаращил вокруг этого вопроса, но не получил точного объяснения.

любая помощь будет оценили.

5 ответов


это зависит от реализации Quicksort. Традиционная реализация, которая разбивается на 2 (< и >=) разделы будут иметь O(n*n) на одинаковых входных данных. Пока нет ОСП обязательно произойдет, это вызовет n рекурсивные вызовы должны быть сделаны-каждый из которых должен сделать сравнение с pivot и n-recursionDepth элементы. т. е. O(n*n) сравнения должны быть сделаны

однако есть простой вариант, который разбивается на 3 набора (<, = и >). Этот вариант имеет O(n) производительность в этом случае - вместо выбора поворота, замены, а затем рекурсии на 0to pivotIndex-1 и pivotIndex+1 to n, он поставит swap все вещи, равные pivot к "среднему" разделу (что в случае всех идентичных входов всегда означает замену с самим собой, т. е. no-op), что означает, что стек вызовов будет только 1 в этом конкретном случае N сравнений и никаких свопов не происходит. Я считаю, что этот вариант сделал свой путь в стандартную библиотеку на Linux, по крайней мере.


производительность quicksort зависит от выбора поворота. Чем ближе выбранная ось к медианному элементу, тем лучше производительность quicksort.

в этом конкретном случае вам повезло-ось, которую вы выбираете, всегда будет a медиана, так как все значения одинаковы. Шаг раздела quicksort, следовательно, никогда не придется менять местами элементы, и два указателя будут встречаться точно посередине. Двух подзадач поэтому будет ровно половина размер-дает вам идеальный O(n log n).

чтобы быть немного более конкретным, это зависит от того, насколько хорошо реализован шаг раздела. Инвариант петли должен только убедиться, что меньшие элементы находятся в левой подзадаче, в то время как большие элементы находятся в правой подзадаче. Нет никакой гарантии, что реализация раздела никогда не меняет местами равные элементы. Но это всегда ненужная работа, поэтому никакая умная реализация не должна этого делать:left и right указатели никогда не обнаружит инверсию, соответствующую оси (т. е. вы никогда не попадете в случай, когда *left > pivot && *right < pivot) и так left указатель будет увеличен,right указатель будет уменьшаться каждый шаг, и они, наконец, встретятся в середине, создавая подзадачи размера n/2.


Это зависит от конкретной реализации.

Если есть только один вид сравнения (≤илиn2) производительность, так как размер проблемы будет уменьшаться только на 1 каждый шаг.

алгоритм перечисленные здесь является примером (сопровождающая иллюстрация предназначена для другого алгоритм.)

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

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

поэтому это зависит от того, имеете ли вы в виду этот особый случай при реализации алгоритма.

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


tobyodavies предоставил правильное решение. Он обрабатывает случай и заканчивает в o(n) время, когда все ключи равны. Это то же самое разделение, что и в голландской проблеме национального флага

http://en.wikipedia.org/wiki/Dutch_national_flag_problem

совместное использование кода из Принстона

http://algs4.cs.princeton.edu/23quicksort/Quick3way.java.html


Если вы реализуете алгоритм двухстороннего разбиения, то на каждом шаге массив будет уменьшен вдвое. Это связано с тем, что при обнаружении одинаковых ключей сканирование останавливается. В результате на каждом шаге элемент секционирования будет располагаться в центре поддерева, тем самым вдвое сокращая массив при каждом последующем рекурсивном вызове. Теперь этот случай похож на случай mergesort, который использует ~N lg N сравнивает для сортировки массива из N элементов. Ergo для дубликатов ключей, традиционный двухсторонний алгоритм секционирования для Quicksort использует ~N lg N сравнивает, тем самым следуя линеарифмическому подходу.