Чому на практиці кращий кіксорт, ніж інші алгоритми сортування?


308

У звичайному курсі алгоритмів нас вчать, що в середньому швидкості є а в гіршому - . Одночасно вивчаються інші алгоритми сортування, які є у гіршому випадку (наприклад, злиття та велика частина ), і навіть лінійний час у кращому випадку (як бульбашка ), але з деякими додатковими потребами пам'яті.O ( n 2 ) O ( n журнал n )O(nlogn)O(n2)O(nlogn)

Після швидкого огляду на ще певні часи , природно сказати, що хитрощі не повинні бути настільки ефективними, як інші.

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

Чому ж тоді химерна перевага перевершує інші алгоритми сортування на практиці? Чи це стосується структури даних реального світу ? Це має відношення до способу роботи пам'яті в комп’ютерах? Я знаю, що деякі спогади проходять швидше, ніж інші, але я не знаю, чи це справжня причина цього контрінтуїтивного виконання (порівняно з теоретичними оцінками).


Оновлення 1: канонічна відповідь говорить про те, що константи, що беруть участь у середнього випадку, менші, ніж константи, що беруть участь в інших алгоритмах . Однак я ще не бачив належного обґрунтування цього, точні розрахунки замість інтуїтивно зрозумілих ідей.O ( n журналу n )O(nlogn)O(nlogn)

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


Оновлення 2: Існує декілька веб-сторінок, які пропонують порівняння алгоритмів сортування, деякі більш привабливі, ніж інші (особливо це сортування-algorithms.com ). Цей підхід, крім представлення приємної наочної допомоги, не відповідає на моє запитання.


2
Сортування злиття - це в гіршому випадку, і сортування масиву цілих чисел, де є відомий обмежений розмір цілих чисел, може бути здійснено за час за допомогою підрахунку. O ( n )O(nlogn)O(n)
Карл Маммерт

13
sorting-algorithms.com має досить ретельне порівняння алгоритмів сортування.
Джо

2
Оновлення реклами 1: Я здогадуюсь, що ви можете мати суворий аналіз або реальні припущення. Я не бачив обох. Наприклад, більшість формальних аналізів враховують лише порівняння.
Рафаель

9
Це питання перемогло на недавньому конкурсі програмістів.SE !
Рафаель

3
Цікаве запитання. Деякий час тому я провів декілька тестів зі випадковими даними та наївною реалізацією швидкого сортування та злиття. Обидва алгоритми спрацювали досить добре для невеликих наборів даних (до 100000 елементів), але після цього сортування злиття виявилося набагато кращим. Це, здається, суперечить загальному припущенню, що швидкий сорт настільки хороший, і я досі не знайшов пояснення цьому. Єдина ідея, яку я міг би придумати, - це те, що зазвичай термін швидке сортування використовується для складніших алгоритмів, таких як інтро сортування, і що наївна реалізація швидкого сортування з випадковим стрижнем не є такою хорошою.
Джорджіо

Відповіді:


215

Коротка відповідь

Аргумент ефективності кешу вже детально пояснений. Крім того, є внутрішній аргумент, чому Quicksort швидкий. Якщо реалізовано як з двома "перехресними покажчиками", наприклад , внутрішні петлі мають дуже маленьке тіло. Оскільки цей код виконується найчастіше, це окупається.

Довга відповідь

Поперше,

Середнє Справа не існує!

Оскільки найкращий і гірший випадок часто є крайнощами, які рідко трапляються на практиці, робиться аналіз середнього випадку. Але будь-який середній аналіз випадку передбачає деякий розподіл входів ! Для сортування типовим вибором є випадкова модель перестановки (мовчазно передбачається у Вікіпедії).

Чому -Запис?O

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

Є два шляхи виходу:

  1. Зафіксуйте модель машини.

    Це зроблено в книжковій серії Дон Кнут « Мистецтво комп’ютерного програмування» для штучного «типового» комп'ютера, винайденого автором. У томі 3 ви знайдете точні середні результати для багатьох алгоритмів сортування, наприклад

    • :11.667(n+1)ln(n)1.74n18.74
    • Mergesort:12.5nln(n)
    • Гіпсорт: 16nln(n)+0.01n
    • Вставка: [ джерело ]2.25n2+7.75n3ln(n) Час виконання декількох алгоритмів сортування

    Ці результати свідчать про те, що Quicksort є найшвидшим. Але це доведено лише на штучній машині Knuth, це не обов'язково означає нічого, скажімо, для вашого ПК x86. Зауважте також, що алгоритми по-різному відносяться до малих входів:
    Виконання декількох алгоритмів сортування невеликих входів
    [ джерело ]

  2. Проаналізуйте абстрактні основні операції .

    Для порівняння на основі сортування це, як правило, свопи та ключові порівняння . У книгах Роберта Седжевіка, наприклад "Алгоритми" , такий підхід дотримується. Ви знайдете там

    • Quicksort: порівняння та в середньому12nln(n)13nln(n)
    • Mergesort: порівнянь, але до доступу до масиву (об'єднання не засноване на свопі, тому ми не можемо порахувати це).8,66 n ln ( n )1.44nln(n)8.66nln(n)
    • : порівняння та свопи в середньому.114n214n2

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

Інші вхідні розподіли

Як зазначалося вище, середні випадки завжди стосуються деякого розподілу вхідних даних, тому можна розглядати ті, що не є випадковими перестановками. Наприклад, було проведено дослідження для Quicksort з рівними елементами, і є хороша стаття про стандартну функцію сортування на Java


8
Результати типу 2. можуть бути перетворені в результати типу 1, вставляючи залежні від машини константи. Тому я заперечую 2. це вищий підхід.
Рафаель

2
@Raphael +1. Я припускаю, що ви припускаєте, що машина залежить також від реалізації, правда? Я маю на увазі, швидка машина + погана реалізація, мабуть, не дуже ефективна.
Янома

2
@Janoma Я вважав, що проаналізований алгоритм повинен бути наданий у дуже детальній формі (як детальний аналіз), а реалізація - якнайбільше в письмі, наскільки це можливо. Але так, реалізація також вплине на це.
Рафаель

3
Власне, аналіз типу 2 поступається на практиці. Машини реального світу настільки складні, що результати другого типу неможливо перевести в тип 1. Порівняйте це з типом 1: побудова експериментальних тривалість роботи займає 5 хвилин роботи.
Жуль

4
@Jules: "побудова експериментального часу роботи" не є типом 1; це не форма формального аналізу і не підлягає передачі іншим машинам. Тому ми робимо офіційний аналіз.
Рафаель

78

Щодо цього питання можна зробити декілька пунктів.

Кіксорт зазвичай швидкий

O(n2)

n1O(nlogn)

Квіксорт зазвичай швидший, ніж більшість сортів

O(nlogn)O(n2)n

O(nlogn)O(nBlog(nB))B

Причиною цієї ефективності кешу є те, що він лінійно сканує вхід і лінійно розділяє вхід. Це означає, що ми можемо максимально використати кожне завантаження кешу, коли ми читаємо кожне число, яке завантажуємо в кеш, перш ніж поміняти цей кеш на інший. Зокрема, алгоритм не враховує кеш-пам'ять, що дає гарну ефективність кешу для кожного рівня кешу, що є ще одним виграшем.

O(nBlogMB(nB))Mk

Кіксорт зазвичай швидший, ніж Мергезорт

Це порівняння повністю стосується постійних факторів (якщо розглядати типовий випадок). Зокрема, вибір полягає в субоптимальному виборі стрижня для Quicksort порівняно з копією всього вводу для Mergesort (або складністю алгоритму, необхідного для уникнення цього копіювання). Виявляється, перший є більш ефективним: теорії за цим немає, це просто відбувається швидше.

nO(logn)O(n)

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

Використовуйте сорт, який відповідає вашим потребам

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


3
Ваше останнє зауваження особливо цінне. Зараз мій колега аналізує реалізацію Quicksort під різними вхідними розподілами. Деякі з них розбиваються, наприклад, на багато дублікатів.
Рафаель

4
O(n2)

8
"[T] тут немає теорії за цим, це просто відбувається швидше". Це твердження є вкрай незадовільним з наукової точки зору. Уявіть собі, що Ньютон говорив: "Метелики злітають, яблука падають: за цим немає жодної теорії, яблука просто трапляються".
Девід Річербі,

2
@ Алекс десять Брінк, що ви маєте на увазі під "Зокрема, алгоритм відсутній в кеші "?
Hibou57

4
@ Дейвид Ріхербі, "Ця заява з наукової точки зору є вкрай незадовільною": він може просто бути свідком факту, не роблячи виду, що ми повинні бути задоволені цим. Деякі сімейства алгоритмів страждають від недостатньої повної формалізації; Хеш-функції є прикладом.
Hibou57

45

В одному з навчальних посібників з програмування в моєму університеті ми попросили студентів порівняти ефективність кваксорту, злиття, сортування вставки та вбудованого list.sort Python (званий Timsort ). Експериментальні результати мене глибоко здивували, оскільки вбудований list.sort виконаний набагато краще, ніж інші алгоритми сортування, навіть із випадками, коли легко робиться крах, злиття злиття. Тому робити висновок про те, що звичайна реалізація кікспорту найкраща на практиці. Але я впевнений, що там набагато краща реалізація quicksort або її гібридна версія там.

Це приємна стаття в блозі Девіда Р. Маківера, що пояснює Тимсорт як форму адаптаційного злиття.


17
@Raphael Щоб сказати коротко, Timsort є сортуванням злиття для асимптотики плюс сортування вставки для коротких входів плюс деякі евристики, щоб ефективно впоратися з даними, які мають випадкові вже відсортовані пориви (що часто трапляється на практиці). Дай: крім алгоритму, list.sortкористь від вбудованої функції, оптимізованої професіоналами. Більш справедливим порівнянням були б усі функції, написані однією мовою при однаковому рівні зусиль.
Жиль

1
@Dai: Ви могли принаймні описати, з якими типами входів (відповідно їх розподілу) за яких обставин (низька оперативна пам'ять, паралелізація однієї реалізації, ...) ви отримали свої результати.
Рафаель

7
Ми тестувались у списку випадкових чисел та частково відсортованих, повністю відсортованих та реверсованих. Це був вступний курс 1 курсу, тому це не було глибоким емпіричним дослідженням. Але той факт, що він зараз офіційно використовується для сортування масивів у Java SE 7 та на платформі Android, щось не означає.
Дай

3
Про це також йшлося тут: cstheory.stackexchange.com/a/927/74
Jukka Suomela

34

Я думаю, що одна з головних причин, чому QuickSort настільки швидкий порівняно з іншими алгоритмами сортування, - це те, що це кеш-пам'ять. Коли QS обробляє сегмент масиву, він отримує доступ до елементів на початку та в кінці сегмента та рухається до центру сегмента.

Отже, коли ви запускаєте, ви отримуєте доступ до першого елемента в масиві, і фрагмент пам'яті ("розташування") завантажується в кеш. І коли ви намагаєтеся отримати доступ до другого елемента, він (швидше за все) вже знаходиться в кеші, тому це дуже швидко.

Інші алгоритми, такі як гипсорт, не працюють так, вони багато стрибають у масиві, що робить їх повільнішими.


5
Це суперечливе пояснення: mergesort теж кеш-пам'ять.
Дмитро Кордубан

2
Я думаю, що ця відповідь в основному правильна, але ось деякі деталі youtube.com/watch?v=aMnn0Jq0J-E
rgrig

3
ймовірно, краща мультипликативна константа для середнього часу складності швидкості сортування (незалежно від фактора кешу, який ви згадали).
Каве

1
Точка, яку ви згадали, не така важлива, порівняно з іншими хорошими властивостями швидкого сортування.
MMS

1
@Kaveh: "Краща мультиплікативна константа для середнього часу складності швидкості сортування" Чи є у вас дані про це?
Джорджо

29

Інші вже говорили, що середній асимптотичний час виконання Quicksort кращий (в постійному), ніж у інших алгоритмів сортування (у певних налаштуваннях).

O(nlogn)

Зауважимо, що існує багато варіантів Quicksort (див., Наприклад, дисертацію Седжевіка). Вони працюють по-різному на різних вхідних розподілах (рівномірний, майже відсортований, майже обернено відсортований, багато дублікатів, ...), а інші алгоритми можуть бути кращими для деяких.

k10


20

O(nlgn)

ps: якщо бути точним, бути кращим, ніж інші алгоритми, залежить від завдання. Для деяких завдань може бути краще використовувати інші алгоритми сортування.

Дивитися також:


3
@Janoma - це питання, якою мовою та компілятором ви користуєтесь. Практично всі функціональні мови (ML, Lisp, Haskell) можуть робити оптимізацію, яка запобігає зростанню стека, а розумніші компілятори для імперативних мов можуть робити те саме (GCC, G ++, і я вважаю, що MSVC все це робить). Визначним винятком є ​​Java, яка ніколи не зробить цю оптимізацію, тому в Java має сенс переписати свою рекурсію як ітерацію.
Рейф Кеттлер

4
@JD, ви не можете використовувати оптимізацію хвостових викликів за допомогою quicksort (принаймні, не повністю), оскільки він дзвонить собі двічі. Ви можете оптимізувати другий дзвінок, але не перший.
svick

1
@Janoma, вам не дуже потрібна рекурсивна реалізація. Наприклад, якщо подивитися на реалізацію функції qsort в C, вона не використовує рекурсивних викликів, і тому реалізація стає набагато швидшою.
Kaveh

1
Гіпсорт також є на місці, чому QS часто швидше?
Кевін

6
23240

16

Θ(n2)Θ(nlogn)

Друга причина полягає в тому, що вона виконує in-placeсортування та дуже добре працює з середовищами віртуальної пам'яті.

ОНОВЛЕННЯ: (Після коментарів Яноми та Свічка)

Щоб проілюструвати це, дозвольте навести приклад, використовуючи сортування об'єднань (тому що сортування об'єднань - це наступний широко прийнятий алгоритм сортування після швидкого сортування), і скажу вам, звідки беруться додаткові константи (наскільки мені відомо, і чому я думаю, що Швидке сортування краще):

Розглянемо наступну послідовність:

12,30,21,8,6,9,1,7. The merge sort algorithm works as follows:

(a) 12,30,21,8    6,9,1,7  //divide stage
(b) 12,30   21,8   6,9   1,7   //divide stage
(c) 12   30   21   8   6   9   1   7   //Final divide stage
(d) 12,30   8,21   6,9   1,7   //Merge Stage
(e) 8,12,21,30   .....     // Analyze this stage

Якщо ви всебічно бачите, як відбувається останній етап, перший 12 порівнюється з 8, а 8 - меншим, так що він проходить перший. Тепер 12 - ПРОТИ, порівняно з 21, а 12 - далі і так далі. Якщо взяти остаточне злиття, тобто 4 елементи з 4 іншими елементами, воно проводить безліч EXTRA порівнянь як констант, які НЕ проводяться в Швидкому Сортуванні. Це причина, чому віддається перевага швидкого сортування.


1
Але що робить константи такими маленькими?
svick

1
@svick Оскільки вони сортуються, in-placeтобто додаткова пам'ять не потрібна.
0x0

Θ(nlgn)

15

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

Ще в 2008 році я відстежив висячу помилку програмного забезпечення до використання quicksort. Через деякий час я написав прості програми введення сортування, quicksort, сортування купи та злиття та випробував їх. Мій сорт злиття випереджав усі інші під час роботи над великими наборами даних.

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

У багатьох випадках я виявляв себе, думаючи, що дана реалізація працює напрочуд добре для кваксорту, лише щоб з'ясувати, що вона насправді не є quicksort. Іноді реалізація перемикається між quicksort та іншим алгоритмом, а іноді зовсім не використовує quicksort. Як приклад, функції qsort () GLibc фактично використовують сортування злиття. Тільки якщо розподілу робочого простору не вдасться, він повертається до місцевого швидкодію, який в кодовому коментарі називає "повільнішим алгоритмом" .

Редагувати: мови програмування, такі як Java, Python та Perl, також використовують сортування злиття, а точніше похідну, наприклад, Timsort або сортування злиття для великих наборів та сортування вставки для малих наборів. (Java також використовує подвійний шарнір, який швидше, ніж звичайний швидкодіючий.)


Я бачив щось подібне до цього, тому що ми траплялися постійно додавати / вдаватися, щоб вставити в групу вже відсортованих даних. Ви можете в середньому обійти це, використовуючи рандомізований кварц (і здивуватися рідкісним і випадковим жахливо повільним сортуванням), або ви можете терпіти завжди повільніший сорт, який ніколи не потребує дивовижної кількості часу, щоб закінчити. Іноді також потрібна стабільність сортування. Java перейшла від сортування злиття до варіанту quicksort.
Роб

@Rob Це не точно. Java і досі використовує варіант злиття (Тимсорт). Він також використовує варіант кваксорту (двоквартирний кваксорт).
Ерван Легранд

14

1 - Швидке сортування є місцем (не потрібно додаткової пам’яті, крім постійної кількості).

2 - Швидке сортування легше здійснити, ніж інші ефективні алгоритми сортування.

3 - Швидке сортування має менші постійні фактори за час його роботи, ніж інші ефективні алгоритми сортування.

Оновлення: для сортування злиття вам потрібно зробити «об’єднання», для якого потрібні додаткові масиви для зберігання даних перед об'єднанням; але в швидкому роді ви цього не зробите. Ось чому швидкий сорт на місці. Існує також кілька додаткових порівнянь, зроблених для злиття, які збільшують постійні фактори в роді злиття.


3
Ви бачили розширені місцеві, ітеративні реалізації Quicksort? Вони багато речей, але не "легкі".
Рафаель

2
Число 2 взагалі не відповідає на моє запитання, а цифри 1 і 3 потребують належного обґрунтування, на мою думку.
Янома

@Raphael: Вони легкі. Набагато простіше реалізувати швидке сортування на місці за допомогою масиву замість покажчиків. І не потрібно бути ітеративним, щоб бути на місці.
MMS

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

@ 1 Mergesort також може бути замінений. @ 2 Що визначає ефективність? Мені подобається сортування злиття, тому що він дуже простий і в той же час ефективний на мою думку. @ 3 Нерелевантно, коли ви сортуєте велику кількість даних і вимагає ефективного впровадження алгоритму.
Оскар скаго

11

За яких умов конкретний алгоритм сортування насправді найшвидший?

Θ(log(n)2)Θ(nlog(n)2)

Θ(nk)Θ(nm)k=2#number_of_Possible_valuesm=#maximum_length_of_keys

3) Чи складається в основі структура даних з пов'язаних елементів? Так -> завжди використовувати на місці сортування об'єднань. Існують як прості в реалізації фіксованого розміру, так і адаптивні (так само природні) знизу вгору місця злиття різних типів для пов'язаних структур даних, і оскільки вони ніколи не вимагають копіювання всіх даних на кожному кроці, і вони ніколи не потребують рекурсій, вони є швидше, ніж будь-які інші загальні сортування на основі порівняння, навіть швидше, ніж швидке сортування.

Θ(n)

5) Чи може розмір базових даних пов'язати малий до середній розмір? наприклад, n <10 000 ... 100 000 000 (залежно від базової архітектури та структури даних)? Так -> використання бітонічного сортування або нечетного злиття Batcher. Перейти 1)

Θ(n)Θ(n2)Θ(nlog(n)2)найгірший час роботи відомий, або, можливо, спробуйте сортувати гребінець. Я не впевнений, що сортування оболонки або сортування гребінців на практиці матимуть досить хороші результати.

Θ(log(n))Θ(n)Θ(n)Θ(log(n))Θ(n2)Θ(n)Θ(n)Θ(log(n))Θ(nlog(n))

Θ(nlog(n))

Підказки щодо імплементації швидкості:

Θ(n)Θ(log(n))Θ(nlogk(k1))

2) Існують ітеративні варіанти знизу вгору вгору, але в AFAIK вони мають такий же асимптотичний простір та часові межі, як і зверху вниз, при цьому додаткові сторони вниз важко реалізувати (наприклад, чітко керувати чергою). Мій досвід полягає в тому, що в будь-яких практичних цілях це ніколи не варто задумуватися.

Підказки щодо реалізації об'єднань:

1) Злиття з низкою вгору завжди швидше, ніж злиття зверху вниз, оскільки воно не вимагає рекурсійних викликів.

2) дуже наївне злиття може бути пришвидшено за допомогою подвійного буфера і перемикання буфера замість копіювання даних назад з тимчасового масиву після кожного кроку.

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

Θ(k)Θ(log(k))Θ(1)Θ(n)

З того, що я написав, зрозуміло, що кваксорт часто не найшвидший алгоритм, за винятком випадків, коли застосовуються такі умови:

1) існує більше, ніж "кілька" можливих значень

2) основна структура даних не пов'язана

3) нам не потрібен стабільний порядок

4) дані достатньо великі, що незначний неоптимальний асимптотичний час роботи бітонічного сортувального пристрою або непарного злиття об'єднаного об'єднання Батчера починає

5) дані майже не відсортовані і не складаються з великих вже відсортованих частин

6) ми можемо отримати доступ до послідовності даних одночасно з декількох місць

Θ(log(n))Θ(n)

ps: Хтось повинен мені допомогти з форматуванням тексту.


(5): Реалізація сортування Apple перевіряє один запуск у порядку зростання чи спадання як на початку, так і в кінці масиву. Це дуже швидко, якщо таких елементів не так багато, і вони можуть дуже ефективно поводитися з цими елементами, якщо їх більше n / ln n. Об’єднайте два відсортовані масиви та відсортуйте результат, і ви отримаєте злиття
gnasher729

8

Більшість методів сортування мають переміщувати дані короткими кроками (наприклад, сортування злиття вносить зміни на локальному рівні, потім об'єднує цей невеликий фрагмент даних, потім об'єднує більш великий ...). Отже, вам потрібно багато рухів даних, якщо дані далекі від місця призначення.

ab


5
Ваш аргумент щодо сорту quicksort vs merge не тримає води. Quicksort починається з великого ходу, потім робить все менші та менші рухи (приблизно вдвічі більше на кожному кроці). Сортування сортування починається з невеликого ходу, потім робить все більші та більші ходи (приблизно вдвічі більше на кожному кроці). Це не вказує на те, що один є більш ефективним, ніж інший.
Жиль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.