Quicksort з 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
повертає попередній елемент, якщо false, інакше якщо це істина, він обчислює і повертає наступний елемент, маємо:
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]])
Зверніть увагу, що ця лямбда, як і більшість кодових гольфів, є досить поганим стилем.
Швидкий сорт на місці, використовуючи схему розділення Хоара
Попередня реалізація створює багато непотрібних додаткових списків. Якщо ми зможемо зробити це на місці, ми уникнемо марнотратства.
Наведена нижче реалізація використовує схему розділення Хоара, про яку ви можете детальніше прочитати у wikipedia (але ми, очевидно, видалили до 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, оскільки наш вбудований алгоритм тимчасового сортування є досить ефективним, і у нас є межі рекурсії. Ми очікували б сортувати списки на місці list.sort
або створювати нові відсортовані списки за допомогоюsorted
- обидва з яких прийняти key
і reverse
аргумент.
my_list = list1 + list2 + ...
. Або розпакуйте списки до нового спискуmy_list = [*list1, *list2]