Чи існують реальні алгоритми, які значно перевершують клас нижче? [зачинено]


39

Минулої ночі я обговорював з іншим програмістом, що, хоча щось може бути O (1), операція, яка є O (n), може перевершити її, якщо в алгоритмі O (1) є велика константа. Він не погодився, тож я приніс це сюди.

Чи є приклади алгоритмів, які значно перевершують класи класу під ним? Наприклад, що O (n) швидше, ніж O (1) або O (n 2 ) швидше, ніж O (n).

Математично це можна продемонструвати для функції з асимптотичною верхньою межею, якщо ви нехтуєте постійними факторами, але чи існують такі алгоритми в дикій природі? І де я можу знайти їх приклади? Для яких ситуацій вони використовуються?


15
Навіть для «великих» алгоритмів менший не обов’язково кращий. Наприклад, гауссова елімінація - це O (n ^ 3), але є алгоритми, які можуть це робити в O (n ^ 2), але коефіцієнт для квадратичного альго часу такий величезний, що люди просто йдуть з O (n ^ 3) один.
BlackJack

11
Ви повинні додати "... для проблем у реальному світі" або щось подібне, щоб зробити це розумним питанням. В іншому випадку вам потрібно зробити nдостатньо великі, щоб компенсувати константу (що і полягає в позначенні big-O).
starblue

8
Не приймайте великі позначення швидкості.
Кодизм

16
Сенс позначення big-O полягає не в тому, щоб сказати вам, як швидко працює алгоритм, а наскільки він добре масштабується.
BlueRaja - Danny Pflughoeft

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

Відповіді:


45

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


14
Точніше, пошук хештету - це O (m), де m - розмір ключа. Ви можете викликати це O (1) лише тоді, коли розмір ключа постійний. Крім того, зазвичай це амортизується - інакше таблиця не може рости / скорочуватися. Потрійні дерева часто можуть бити хеш-таблиці для пошуку рядків у контекстах, де рядки досить часто не знайдені - пошук потрійного дерева часто виявить, що ключ відсутній під час перевірки першого символу чи двох рядків, де версія хештелю ще не обчислила хеш.
Steve314

2
Мені подобається відповідь Лорен Печтел і перший коментар Стів314. Я насправді бачив, як це відбувається. Якщо ви створюєте клас Java, який має метод хеш-коду (), який займає занадто багато часу, щоб повернути хеш-значення (і не може / не може кешувати його), то використовуючи екземпляри такого класу в колекції хеш-типів (наприклад, HashSet) зробить цю колекцію АЛО повільніше, ніж колекцію типу масиву (наприклад, ArrayList).
Shivan Dragon

1
@ Steve314: чому ви вважаєте, що хеш-функціями є O (m), де m - розмір ключа? Функції хешу можуть бути O (1), навіть якщо ви маєте справу зі струнами (або іншим складним типом). Немає надто великого значення для його формального визначення, ніж просто реалізація хеш-функції може істотно змінити складність, якщо для вашого вводу буде обрана неправильна структура даних (хеш-таблиця) (розмір ключа непередбачуваний).
Кодизм

1
@ Steve314: Зауважте, що я сказав фіксовані таблиці даних. Вони не ростуть. Крім того, ви отримуєте O (1) продуктивність з хеш-таблиці, лише якщо зможете оптимізувати ключ, щоб уникнути зіткнень.
Лорен Печтел

1
@Loren - строго, якщо стіл має фіксований розмір, є постійний максимум часу, який ви можете витратити на пошук вільного місця. Тобто, максимум, вам потрібно буде перевірити вже заповнені слоти n-1, де n - постійний розмір таблиці. Отже хеш-таблиця фіксованого розміру справді є O (1), не потребуючи амортизованого аналізу. Це не означає, що вам не байдуже, що доступ стає повільнішим у міру заповнення таблиці - лише те, що виражає не велике О.
Стів314

25

Матричне множення. Наївний алгоритм O (n ^ 3) часто використовується на практиці так само швидше, ніж O (n ^ 2.8) Страссена для малих матриць; і для більш великих матриць використовується Страссен замість алгоритму Копперсміта — Винограда замість O (n ^ 2.3).



2
Мідь-Віноград НІКОЛИ не використовується. Її реалізація була б жахливою задачею сама по собі, а константа настільки погана, що це було б нездійсненно навіть для сучасних проблем наукової матриці.
tskuzzy

24

Простий приклад - різниця між різними алгоритмами сортування. Mergesort, Heapsort та деякі інші - O (n log n) . Квіксорт - це O (n ^ 2) найгірший випадок. Але часто Quicksort швидше, і насправді він виконує в середньому як O (n log n) . Більше інформації .

Інший приклад - це генерація єдиного числа Фібоначчі. Ітераційний алгоритм - O (n) , тоді як алгоритм на основі матриці - O (log n) . І все-таки для перших кількох тисяч чисел Фібоначчі ітераційний алгоритм, ймовірно, швидший. Це також залежить від виконання курсу!

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


Це є чудовим поясненням Big-O, але не вдається вирішити питання цього питання, що стосується конкретних випадків, коли алгоритм O (n) буде швидшим, ніж O (1).
KyleWpppd

Фібоначчі номер один трохи відключений. Розмір виводу є експоненціальним у вхідному розмірі, тому це різниця між O (lg n * e ^ n) проти O (lg lg n * e ^ n).
Пітер Тейлор

Додаток: в кращому випадку. Алгоритм на основі матриці робить множення з числами в порядку 1,5 ^ n, тому O (lg lg n * ne ^ n) може бути найкращим пов'язаним доказом.
Пітер Тейлор

1
Quicksort зазвичай описується як O (n log n) очікувана продуктивність у будь-якому випадку - найгірший випадок є малоймовірним для випадкових входів, а побудова деякої випадковості в попередньому доборі або до вибору стрижня означає, що найгірший випадок в цілому є дуже малоймовірним для значних розмірів вводу. Найгірший випадок є менш актуальним, ніж той факт, що кваксорт (1) дуже простий і (2) дуже зручний для кешу, обидва вони призводять до значно кращих постійних факторів, ніж у багатьох інших алгоритмах сортування.
Steve314

(2) - це саме такий тип зовнішнього врахування, який слід враховувати, дивлячись на продуктивність великого виходу. Алгоритмічно Mergesort завжди повинен перевершувати Quicksort, але використання ресурсів і кеш-локальність, як правило, зворотно змінюють їхні позиції в реальному світі.
Ден Ліонс

18

Примітка: Прочитайте коментарі @ back2dos нижче та інших гуру, оскільки вони насправді корисніші за те, що я написав - Дякую всім учасникам.

Я думаю, що з наведеної нижче діаграми (взято з позначення Big O , пошук "Песимістичний характер алгоритмів:") ви бачите, що O (log n) не завжди кращий, ніж скажімо, O (n). Отже, я думаю, ваш аргумент справедливий.

Рис-1


6
Питання вимагало конкретних реальних прикладів алгоритмів. Цього немає, як є.
Меган Уокер

19
На цьому графіку ви нічого не бачите, що би відповіло на питання. Це вводить в оману. Цей графік просто накреслює функції y = 1, y = log xі так далі, і перетин y = 1і y = xє фактично суть (1,1). Якби це дійсно було правильним, ніж тобі сказали б, алгоритми вищої складності можуть бути швидшими на 0 до 2 записів, що люди навряд чи переймалися б. Те, що графік повністю не враховує (і від чого виходить помітна різниця у ефективності, про яку йдеться), є постійними факторами.
back2dos

@Samuel Walker, дякую за коментар. Надане посилання (Link-1) містить кілька прикладів алгоритмів для категорії.
NoChance

5
@ back2dos: Сам графік не відповідає на питання, але може бути використаний для відповіді на нього. Форма кожної відображеної функції однакова для будь-якого масштабу та постійного коефіцієнта. З цього графіку видно, що за даної комбінації функцій існує діапазон входів, для яких одна менша, і діапазон входів, для яких інший.
Ян Худек

2
@dan_waterworth, ти маєш рацію, я визнаю цю точку та видаляю коментар. Тим не менш, відповідь є невірною або оманливою у двох аспектах: 1) вся суть Big-O полягає в тому, що вона дає верхню межу складності; це важливо лише для великих n, оскільки ми явно викидаємо менші терміни, які переповнюються найбільшим терміном у міру зростання n. 2) Суть питання полягає у пошуку прикладів двох алгоритмів, коли той, що має більшу зв'язану велику величину O, перевершує той, що має нижню межу. Ця відповідь не відповідає, оскільки не дає таких прикладів.
Калеб

11

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

Я колись змусив свого професора обчислювальної геометрії описати алгоритм тріангуляції багатокутника за лінійним часом, але він закінчив "дуже складно. Я не думаю, що його реально реалізували" (!!).

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


Це швидше для величезних графіків порядку 100000 вершин.
tskuzzy

Купи Фібоначчі також були моєю першою (власне, другою) думкою.
Конрад Рудольф

10

Порівняйте вставку у зв’язаний список та вставлення у масив змінного розміру.

Кількість даних має бути досить великою, щоб вставити зв'язаний список O (1).

Зв'язаний список має додаткові накладні витрати для наступних покажчиків та відмежувань. Масив, що змінює розмір, повинен копіювати дані навколо. Це копіювання O (n), але на практиці дуже швидко.


1
Масив, що змінює розмір, збільшується вдвічі в розмірі щоразу, коли він заповнюється, тому середня вартість зміни розміру за одну вставку становить O (1).
кевін клайн

2
@kevincline, так, але O (n) походить від необхідності переміщення всіх елементів після точки вставки вперед. Виділення амортизується O (1) раз. Моя думка, що цей рух все ще дуже швидкий, тому на практиці зазвичай б'ють пов'язані списки.
Вінстон Еверт

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

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

@jgmjgm, якщо ви вставите в середину змінного масиву, він абсолютно копіює елементи після цього.
Вінстон Еверт

8

Нотація Big-Oh використовується для опису швидкості росту функції, тому можливо, що алгоритм O (1) буде швидшим, але лише до певної точки (постійний коефіцієнт).

Поширені позначення:

O (1) - Кількість ітерацій (іноді ви можете позначати це як час, який користувач витрачає функція) не залежить від розміру вводу, а насправді є постійною.

O (n) - кількість ітерацій зростає в лінійній пропорції до розміру вводу. Значення - якщо алгоритм повторюється через будь-який вхід N, 2 * N разів, він все ще вважається O (n).

O (n ^ 2) (квадратична) - кількість ітерацій є вхідним розміром у квадрат.


2
Щоб додати приклад до відмінної відповіді: метод O (1) може зайняти 37 років на виклик, тоді як метод O (n) може зайняти 16 * n мікросекунд на виклик. Що швидше?
Kaz Dragon

16
Я повністю не бачу, як це відповідає на питання.
avakar

7
Я розумію, великий-О. Це не стосується фактичного питання, яке стосується конкретних прикладів функцій, коли алгоритми з меншим великим-O перевершують ті, у кого вищий big-O.
KyleWpppd

Коли ви ставите питання у формі "Чи є приклади ...", хтось неминуче збирається відповісти "Так". не даючи жодної.
rakslice

1
@rakslice: Можливо, так. Однак цей сайт вимагає пояснення (або ще краще підтвердження) будь-яких заяв, які ви робите. Тепер найкращий спосіб довести, що є такі приклади, - навести один;)
back2dos

6

Бібліотеки Regex зазвичай реалізуються для зворотного відстеження, що має найгірший експоненційний час, а не генерації DFA, що має складність O(nm).

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

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


Я думаю, що це також частково історично - алгоритм перетворення регулярного виразу в DFA був запатентований під час розробки деяких попередніх інструментів (sed і grep, я думаю). Звичайно, я чув це від свого професора-упорядника, який не був повністю впевнений, тому це третій рахунок.
Тихон Єлвіс

5

O(1)алгоритм:

def constant_time_algorithm
  one_million = 1000 * 1000
  sleep(one_million) # seconds
end

O(n)алгоритм:

def linear_time_algorithm(n)
  sleep(n) # seconds
end

Зрозуміло, що для будь-якого значення nде n < one_million, O(n)алгоритм, наведений у прикладі, буде швидшим, ніж O(1)алгоритм.

Хоча цей приклад трохи вибагливий, він по духу рівнозначний наступному прикладу:

def constant_time_algorithm
  do_a_truckload_of_work_that_takes_forever_and_a_day
end

def linear_time_algorithm(n)
  i = 0
  while i < n
    i += 1
    do_a_minute_amount_of_work_that_takes_nanoseconds
  end
end

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

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


4

Сортування:

Сортування вставки - це O (n ^ 2), але перевершує інші алгоритми сортування O (n * log (n)) для невеликої кількості елементів.

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

Див. Розділ Timsort про поточну реалізацію сортування Python та Java 7 за замовчуванням, які використовують цю методику.



3

Bubblesort в пам’яті може перевершити швидкодію, коли програма переходить на диск або потрібно читати кожен елемент з диска при порівнянні.

Це повинен бути приклад, до якого він може ставитися.


Чи не приведені складності на швидкості і пухирці передбачають O (1) випадковий доступ до пам'яті? Якщо цього більше не відбувається, чи не потрібно було б переглядати складність швидкості у вирій?
Віктор Даль

@ViktorDahl, час доступу до елемента не є частиною того, що традиційно вимірюється складністю алгоритму сортування, тому "O (1)" не є правильним вибором слів тут. Використовуйте натомість "постійний час". PHK не раз писав статтю про алгоритми сортування, знаючи, що деякі елементи дорожче відновлювати, ніж інші (віртуальна пам'ять) - queue.acm.org/detail.cfm?id=1814327 - вам це може бути цікавим.

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

3

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

Наприклад: бінарний пошук і пошук хеш-таблиць обидва набагато швидше за пошук, ніж лінійний пошук, але вони вимагають, щоб ви сортували список або склали хеш-таблицю відповідно.

Сортування обійдеться вам N log (N), а хеш-таблиця обійдеться щонайменше N. Тепер, якщо ви збираєтеся робити сотні чи тисячі пошуку, це все-таки амортизована економія. Але якщо вам потрібно зробити лише один або два підшуки, можливо, буде просто сенс зробити лінійний пошук і заощадити вартість запуску.


1

Розшифровка часто 0 (1). Наприклад, простір ключів для DES становить 2 ^ 56, тому розшифровка будь-якого повідомлення є постійною операцією в часі. Це тільки те, що у вас є коефіцієнт 2 ^ 56, так що це дійсно велика константа.


Чи не є розшифровка повідомлення O ( n ), де n пропорційний розміру повідомлення? Поки у вас є правильний ключ, розмір ключа навіть не враховується; деякі алгоритми мають мінімальні або відсутні процеси налаштування / розширення ключів (DES, RSA - зауважте, що генерація ключів все ще може бути складним завданням, але це не має нічого спільного з розширенням ключів), тоді як інші надзвичайно складні (Blowfish приходить на думку), але як тільки це зроблено, час на виконання фактичної роботи пропорційний розміру повідомлення, отже, O (n).
CVn

Ви, мабуть, маєте на увазі криптоаналіз, а не дешифрування?
Лише близько

3
Ну, так, існує будь-яка кількість речей, які ви можете вважати постійними і оголошувати алгоритм O (1). [сортування неявно передбачає, що елементи потребують постійної кількості часу, щоб порівняти, наприклад, чи будь-яку математику з числами, що не входять до числа]
Random832

1

Різні втілення наборів ведуть мені на думку. Одне з найбільш наївних - це реалізувати його над вектором, що означає remove, а також, containsтому і addвсі приймають O (N).
Альтернативою є його реалізація над деяким хешем загального призначення, який відображає хеши вводу на вхідні значення. Така реалізація набору виконується за допомогою O (1) для та add, containsа remove.

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

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

Ви дійсно не можете знати, наскільки швидко все відбувається в реальному сценарії, поки ви не введете його в один і просто не виміряєте. Часто результати дивують (принаймні, для мене).


1

Так, для відповідно малих N. Завжди буде N, над яким ви завжди матимете впорядкування O (1) <O (lg N) <O (N) <O (N log N) <O (N ^ c ) <O (c ^ N) (де O (1) <O (lg N) означає, що в алгоритмі O (1) буде проведено менше операцій, коли N є відповідно великим, а c - деякою фіксованою постійною, що перевищує 1 ).

Скажімо, певний алгоритм O (1) займає точно f (N) = 10 ^ 100 (googol) операцій, а алгоритм O (N) займає рівно g (N) = 2 N + 5 операцій. Алгоритм O (N) буде давати більшу продуктивність, поки ви N буде приблизно рівним googol (насправді, коли N> (10 ^ 100 - 5) / 2), тож якщо ви лише очікували, що N буде в межах від 1000 до мільярда, ви зазнав би великого штрафу за допомогою алгоритму O (1).

Або для реалістичного порівняння скажіть, що ви множите n-значні числа разом. Алгоритм Карацуби - це щонайбільше 3 n ^ (lg 3) операцій (тобто приблизно O (n ^ 1,585)), тоді як алгоритм Шенгаге – Страссена - O (N log N log log N), що є швидшим порядком , але для цитування Вікіпедія:

На практиці алгоритм Шенгаге-Страссена починає перевершувати більш старі методи, такі як множення Карацуби та Тома – Кука для чисел, що перевищують 2 ^ 2 ^ 15 до 2 ^ 2 ^ 17 (від 10 000 до 40 000 десяткових цифр). [4] [5] [6 ]

Отже, якщо ви множите 500-цифрові числа разом, не має сенсу використовувати алгоритм, який "швидший" великими аргументами O.

EDIT: Ви можете визначити f (N) порівняно g (N), взявши межа N-> нескінченність f (N) / g (N). Якщо межа дорівнює 0, то f (N) <g (N), якщо межа є нескінченністю, то f (N)> g (N), а якщо межа є якоюсь іншою постійною, то f (N) ~ g (N) з точки зору великого O позначення.


1

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

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


0

Алгоритм Укконена для побудови суфіксних спроб становить O (n log n). Він має перевагу в тому, що він є "он-лайн" - тобто ви можете поступово додавати більше тексту.

Останнім часом інші більш складні алгоритми стверджують, що на практиці більш швидкі, значною мірою тому, що доступ до пам'яті має більш високу локальність, тим самим покращуючи використання кеш-процесора та уникаючи зупинок конвеєра конвеєра. Дивіться, наприклад, це опитування , в якому стверджується, що 70-80% часу на обробку витрачається на очікування пам'яті, і цей документ, що описує алгоритм "wotd".

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


0

Завжди є найшвидший і найкоротший алгоритм для будь-якої чітко визначеної проблеми . Це лише чисто теоретично (асимптотично) найшвидший алгоритм.

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

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


0

Багато мов / фреймворків використовують наївні відповідність візерунка, щоб відповідати рядкам замість KMP . Ми шукаємо такі струни, як Том, Нью-Йорк, а не абабаабабабабабаабабабабабаб.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.