Быстрая сортировка с Python
В реальной жизни мы всегда должны использовать встроенную сортировку, предоставляемую Python. Однако понимание алгоритма быстрой сортировки поучительно.
Моя цель здесь - так разбить предмет, чтобы читатель мог легко его понять и воспроизвести без необходимости возвращаться к справочным материалам.
Алгоритм быстрой сортировки по сути следующий:
- Выберите точку сводных данных.
- Переместите все точки данных ниже (ниже) точки поворота в положение ниже точки поворота - переместите те, которые больше или равны (выше) оси поворота, в положение над ней.
- Примените алгоритм к областям выше и ниже точки поворота.
Если данные распределены случайным образом, выбор первой точки данных в качестве точки поворота эквивалентен случайному выбору.
Читаемый пример:
Во-первых, давайте посмотрим на читаемый пример, в котором комментарии и имена переменных используются для указания промежуточных значений:
def quicksort(xs):
"""Given indexable and slicable iterable, return a sorted list"""
if xs:
pivot = xs[0]
below = [i for i in xs[1:] if i < pivot]
above = [i for i in xs[1:] if i >= pivot]
return quicksort(below) + [pivot] + quicksort(above)
else:
return xs
Чтобы переформулировать алгоритм и код, продемонстрированные здесь, мы перемещаем значения выше точки поворота вправо, а значения ниже точки поворота влево, а затем передаем эти разделы той же функции для дальнейшей сортировки.
В гольф:
Это может быть гольф до 88 символов:
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Чтобы увидеть, как мы к этому пришли, сначала возьмите наш читаемый пример, удалите комментарии и строки документации и найдите точку поворота на месте:
def quicksort(xs):
if xs:
below = [i for i in xs[1:] if i < xs[0]]
above = [i for i in xs[1:] if i >= xs[0]]
return quicksort(below) + [xs[0]] + quicksort(above)
else:
return xs
Теперь найдите внизу и вверху на месте:
def quicksort(xs):
if xs:
return (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
else:
return xs
Теперь, зная, что and
возвращает предыдущий элемент, если ложно, иначе, если это правда, он оценивает и возвращает следующий элемент, у нас есть:
def quicksort(xs):
return xs and (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
Поскольку лямбда-выражения возвращают одно выражение, и мы упростили его до одного выражения (хотя оно становится все более нечитаемым), теперь мы можем использовать лямбда:
quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
И, чтобы сократить наш пример, сократите имена функций и переменных до одной буквы и удалите ненужные пробелы.
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Обратите внимание, что эта лямбда, как и большинство игр в гольф, - довольно плохой стиль.
Быстрая сортировка на месте с использованием схемы Hoare Partitioning
Предыдущая реализация создает множество ненужных дополнительных списков. Если мы сможем сделать это на месте, мы не тратим впустую пространство.
В приведенной ниже реализации используется схема разделения Хоара, о которой вы можете прочитать больше в Википедии (но мы, по-видимому, удалили до 4 избыточных вычислений на каждый partition()
вызов, используя семантику цикла while вместо do-while и переместив шаги сужения в конец внешний цикл while.).
def quicksort(a_list):
"""Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort"""
def _quicksort(a_list, low, high):
if low < high:
p = partition(a_list, low, high)
_quicksort(a_list, low, p)
_quicksort(a_list, p+1, high)
def partition(a_list, low, high):
pivot = a_list[low]
while True:
while a_list[low] < pivot:
low += 1
while a_list[high] > pivot:
high -= 1
if low >= high:
return high
a_list[low], a_list[high] = a_list[high], a_list[low]
low += 1
high -= 1
_quicksort(a_list, 0, len(a_list)-1)
return a_list
Не уверен, достаточно ли тщательно протестировал:
def main():
assert quicksort([1]) == [1]
assert quicksort([1,2]) == [1,2]
assert quicksort([1,2,3]) == [1,2,3]
assert quicksort([1,2,3,4]) == [1,2,3,4]
assert quicksort([2,1,3,4]) == [1,2,3,4]
assert quicksort([1,3,2,4]) == [1,2,3,4]
assert quicksort([1,2,4,3]) == [1,2,3,4]
assert quicksort([2,1,1,1]) == [1,1,1,2]
assert quicksort([1,2,1,1]) == [1,1,1,2]
assert quicksort([1,1,2,1]) == [1,1,1,2]
assert quicksort([1,1,1,2]) == [1,1,1,2]
Вывод
Этот алгоритм часто преподается на курсах информатики и спрашивается на собеседовании. Это помогает нам думать о рекурсии и о разделении и победе.
Quicksort не очень практичен в Python, поскольку наш встроенный алгоритм timsort достаточно эффективен, и у нас есть ограничения на рекурсию. Можно было бы ожидать , чтобы сортировать списки в месте с list.sort
или создавать новые отсортированные списки sorted
- оба из которых принять key
и reverse
аргумент.
my_list = list1 + list2 + ...
. Или распаковать списки в новый списокmy_list = [*list1, *list2]