Чому макс повільніше сортування?


92

Я виявив, що maxце повільніше, ніж sortфункція в Python 2 і 3.

Python 2

$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 239 usec per loop
$ python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'max(a)'        
1000 loops, best of 3: 342 usec per loop

Python 3

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a.sort();a[-1]'
1000 loops, best of 3: 252 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a)'
1000 loops, best of 3: 371 usec per loop

Чому це max ( O(n)) повільніше , ніж sortфункції ( O(nlogn))?


3
Ви запустили аналіз Python 2 один раз, і код Python 3 абсолютно однаковий.
erip

9
a.sort()працює на місці. Спробуйтеsorted(a)
Андреа Корбелліні

Якщо ви це виправили, опублікуйте, що ви зробили, щоб виправити це.
Кренделі

4
@Pretzel OP означає, що публікацію відредаговано, а не те, що проблему вирішено.
erip

2
@WeizhongTu але sortсортує, а потім aсортується назавжди
njzk2

Відповіді:


125

Ви повинні бути дуже обережними при використанні timeitмодуля в Python.

python -m timeit -s 'import random;a=range(10000);random.shuffle(a)' 'a.sort();a[-1]'

Тут код ініціалізації запускається один раз для створення рандомізованого масиву a. Потім решта коду запускається кілька разів. Перший раз він сортує масив, але кожен інший раз ви викликаєте метод сортування на вже відсортованому масиві. Повертається лише найшвидший час, тому ви фактично визначаєте час, скільки часу потрібно Python для сортування вже відсортованого масиву.

Частиною алгоритму сортування Python є виявлення, коли масив вже частково або повністю відсортований. Повністю відсортований, він просто повинен один раз просканувати масив, щоб виявити це, а потім зупиниться.

Якщо замість цього ви спробували:

python -m timeit -s 'import random;a=range(100000);random.shuffle(a)' 'sorted(a)[-1]'

тоді сортування відбувається на кожному циклі синхронізації, і ви бачите, що час сортування масиву справді набагато довший, ніж просто знайти максимальне значення.

Змінити: @ skyking - х відповідь пояснює частина я залишив непоясненим: a.sort()знає , що працює над списком , так можна отримати прямий доступ до елементів. max(a)працює на будь-якій довільній ітерації, тому доводиться використовувати загальну ітерацію.


10
Хороший улов. Я ніколи не розумів, що стан інтерпретатора зберігається у всіх прогонах коду. Тепер мені цікаво, скільки несправних тестів я виробляв у минулому. : -}
Frerich Raabe

1
Для мене це було очевидним. Але зауважте, що навіть якщо ви сортуєте вже відсортований масив, вам доведеться перевірити всі елементи. Що рівно стільки ж праці, скільки отримання максимуму .... Для мене це виглядає як напіввідповідь.
Каролі Горват

2
@KarolyHorvath, ти маєш рацію. Я думаю, що @skyking отримав другу половину відповіді: a.sort()знає, що він працює над списком, тому може безпосередньо отримувати доступ до елементів. max(a)працює над довільною послідовністю, щоб не використовувати загальну ітерацію.
Дункан

1
@KarolyHorvath можливо пророкування розгалужень може пояснити , чому неодноразово сортування відсортований масив швидше: stackoverflow.com/a/11227902/4600
marcospereira

1
@JuniorCompressor listsort.txtпояснює: "Він має надприродну ефективність на багатьох видах частково впорядкованих масивів (потрібно менше порівняння lg (N!) І лише N-1)", а потім далі пояснює всі види оптимізації крові. Я припускаю, що це може зробити багато припущень, які maxне можуть, тобто сортування не асимптотично швидше.
Frerich Raabe

87

По-перше, зверніть увагу, що max()використовується протокол ітератора , тоді як list.sort()використовується спеціальний код . Зрозуміло, що використання ітератора є важливою накладною витратою, тому ви спостерігаєте таку різницю в термінах.

Однак, крім цього, ваші тести не є чесними. Ви працюєте a.sort()в одному списку більше одного разу. Алгоритм , який використовується Python спеціально розроблений , щоб бути швидко для вже (частково) упорядковано дані. Ваші тести говорять про те, що алгоритм добре виконує свою роботу.

Це справедливі випробування:

$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'max(a[:])'
1000 loops, best of 3: 227 usec per loop
$ python3 -m timeit -s 'import random;a=list(range(10000));random.shuffle(a)' 'a[:].sort()'
100 loops, best of 3: 2.28 msec per loop

Тут я кожного разу створюю копію списку. Як бачите, порядок величини результатів різний: мікро- проти мілісекунд, як ми і очікували.

І пам’ятайте: big-Oh визначає верхню межу! Нижня межа алгоритму сортування Python - Ω ( n ). Значення O ( n log n ) не означає автоматично, що кожен пробіг займає час, пропорційний n log n . Це навіть не означає, що він повинен бути повільнішим за алгоритм O ( n ), але це вже інша історія. Важливо розуміти, що в деяких сприятливих випадках алгоритм O ( n log n ) може працювати за O ( n ) час або менше.


31

Це може бути тому l.sort, що член, listа maxє загальною функцією. Це означає, що l.sortможна покластися на внутрішнє представлення, listпоки maxдоведеться проходити загальний протокол ітераторів.

Це робить вибір кожного елемента l.sortшвидшим, ніж кожен, що maxробить.

Я припускаю, що якщо ви замість цього скористаєтесь, sorted(a)ви отримаєте результат повільніше ніж max(a).


5
Це припущення - лише час на один лайнер, щоб стати більш конкретним. Не ставлячи під сумнів ваші знання, просто таке додавання є тривіальним для демонстрації тих, хто цього не знає.
Reti43

Ви праві, що sorted(a)повільніше ніж max(a). Не дивно, що мова йде про таку ж швидкість a.sort(), що і ваша, але ваша здогадка щодо причини, чому це не так - це тому, що операційна система зробила помилку під час тестування, як зазначено у прийнятій відповіді.
Мартіно

Справа в тому, що існує ймовірність того, що протокол загального ітератора має достатньо накладних витрат, щоб компенсувати log(n)фактор складності. Тобто O(n)алгоритм гарантується лише швидшим, ніж O(nlogn)алгоритм для досить великих n(наприклад, оскільки час для кожної операції може відрізнятися між алгоритмами - nlognшвидкі кроки можуть бути швидшими, ніж nповільні). У цьому випадку не враховувалось саме те, де саме беззбитковість (але слід пам’ятати, що log nфактор не дуже великий фактор для дрібних n).
skyking
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.