Сложность Quicksort, когда все элементы одинаковы?
У меня есть массив из n чисел, которые одинаково.Я применяю быструю сортировку. Какой должна быть временная сложность сортировки в данном случае.
я вытаращил вокруг этого вопроса, но не получил точного объяснения.
любая помощь будет оценили.
5 ответов
это зависит от реализации Quicksort. Традиционная реализация, которая разбивается на 2 (<
и >=
) разделы будут иметь O(n*n)
на одинаковых входных данных. Пока нет ОСП обязательно произойдет, это вызовет n
рекурсивные вызовы должны быть сделаны-каждый из которых должен сделать сравнение с pivot и n-recursionDepth
элементы. т. е. O(n*n)
сравнения должны быть сделаны
однако есть простой вариант, который разбивается на 3 набора (<
, =
и >
). Этот вариант имеет O(n)
производительность в этом случае - вместо выбора поворота, замены, а затем рекурсии на 0
to 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
сравнивает, тем самым следуя линеарифмическому подходу.