Як обдурити евристику "спробувати деякі тестові випадки": Алгоритми, які здаються правильними, але насправді є невірними


105

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

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

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

Конкретно шукаю:

  1. Алгоритм. Недолік повинен бути на алгоритмічному рівні. Я не шукаю помилок в реалізації. (Наприклад, при мінімальному рівні приклад повинен бути мовно-агностичним, і недолік повинен стосуватися алгоритмічних проблем, а не інженерії програмного забезпечення або впровадження.)

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

  3. Алгоритм, який би пройшов розумну ручну тестову стратегію з високою ймовірністю. Хтось, хто намагається вручну спробувати кілька невеликих тестових випадків, навряд чи виявить недолік. Наприклад, "моделювати QuickCheck вручну з десятка невеликих тестових випадків" навряд чи виявить, що алгоритм невірний.

  4. Переважно, детермінований алгоритм. Я бачив, як багато студентів думають, що "спробувати кілька тестових випадків вручну" - це розумний спосіб перевірити, чи правильний алгоритм детермінований, але я підозрюю, що більшість студентів не вважають, що спробування декількох тестових випадків - це хороший спосіб перевірити вірогідність алгоритми. Для імовірнісних алгоритмів часто немає способу визначити, чи правильний якийсь конкретний вихід; і ви не можете перекрутити достатньо прикладів, щоб зробити будь-який корисний статистичний тест на розподіл виводу. Тому я вважаю за краще зосередитись на детермінованих алгоритмах, оскільки вони стають більш чіткими до серця помилок учнів.

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


2
Мені подобається ваше запитання, воно також пов'язане з дуже цікавим запитанням, яке я бачив на тему " Математика " днями, що стосується спростування здогад з великими константами. Ви можете знайти його тут
ZeroUltimax,

1
Ще трохи копав, і я знайшов ці два геометричні алгоритми.
ZeroUltimax

@ZeroUltimax Ви маєте рацію, центральний пт будь-яких 3 нелінійних балів не гарантовано знаходиться всередині. Швидкий засіб полягає в тому, щоб отримати птаха на лінії між лівою і далекою правою. Чи є ще проблема де?
Поінформовано

Положення цього питання мені здається дивним, так як у мене виникають труднощі з головою, але я думаю, що процес зведення алгоритму, як описано, є принципово порушеним. Навіть для студентів, які не «зупиняються на цьому», це приречено. 1> алгоритм запису, 2> придумати / запустити тестові випадки, 3a> зупинити або 3b> виявити правильним. Перший крок в значній мірі був би ідентифікувати вхідні класи для проблемної області. Кутові випадки та сам алгоритм виникають із цих питань. (продовження)
МістерМіндор

1
Як ви формально відрізняєте помилку реалізації від хибного алгоритму? Мене зацікавило ваше запитання, але в той же час мене турбувало те, що ситуація, яку ви описуєте, здається швидше правилом, ніж винятком. Багато людей тестують те, що вони реалізують, але вони, як правило, все ще мають помилки. Другий приклад найбільш схваленої відповіді - саме така помилка.
babou

Відповіді:


70

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

Приклад: Номінали монет, та число , виражають як суму : s з якомога кількістю монет. n n d id1,,dknndi

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

Наприклад, монети зі значенням , і дадуть правильні відповіді з жадібними відповідями на всі числа від до за винятком числа .5 1 1 14 10 = 6 + 1 + 1 + 1 + 1 = 5 + 565111410=6+1+1+1+1=5+5


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

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

2
Британська система монет у старому стилі (до децималізації 1971 року) мала справжній приклад цього. Жадібний алгоритм для підрахунку чотирьох шилінгів використовував би півкілі (2½ шилінгів), монету з одним шилінгу та шістпенду (½ шилінг). Але оптимальне рішення використовує два флорини (по 2 шилінги кожен).
Марк Домінус

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

62

Я одразу згадав приклад Р. Бекхаус (це, можливо, було в одній із його книг). Мабуть, він призначив завдання програмування, коли студенти повинні були написати програму Pascal для перевірки рівності двох рядків. Однією з програм, до яких звернувся студент, була така:

issame := (string1.length = string2.length);

if issame then
  for i := 1 to string1.length do
    issame := string1.char[i] = string2.char[i];

write(issame);

Тепер ми можемо протестувати програму за допомогою таких даних:

"університет" "університет" Правда; гаразд

"курс" "курс" Правда; гаразд

"" "" Правда; гаразд

"університетський" "курс" Неправдиво; гаразд

"лекція" "курс" Неправдиво; гаразд

"точність" "точність" Неправда, гаразд

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

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


Ще один милий приклад - двійковий пошук. У TAOCP Кнут говорить, що "хоча основна ідея бінарного пошуку є порівняно простою, деталі можуть бути напрочуд хитромудрими". Мабуть, помилка в реалізації двійкового пошуку Java залишилася непоміченою протягом десятиліття. Це була ціла помилка переповнення і виявлялася лише при досить великому вході. Хитрі деталі реалізації двійкового пошуку також висвітлюються Бентлі в книзі « Програмування перлів» .

Підсумок: напрочуд важко бути впевненим, що бінарний алгоритм пошуку правильний, просто перевіривши його.


9
Звичайно, недолік досить очевидний з джерела (якщо ви раніше писали подібну річ).
Рафаель

3
Навіть якщо простий недолік у прикладі програми виправлений, рядки дають досить багато цікавих проблем! Обертання рядків є класичним - "основний" спосіб зробити це шляхом простого обертання байтів. Потім кодування вступає в гру. Потім сурогати (як правило, двічі). Проблема, звичайно, у тому, що не існує простого способу формально довести, що ваш метод є правильним.
Звичайний

6
Можливо, я зовсім неправильно трактую питання, але це здається вадою в реалізації, а не вадою самого алгоритму .
Містер Міндор

8
@ Mr.Mindor: як ви можете сказати, чи програміст записав правильний алгоритм, а потім реалізував його неправильно, чи записав неправильний алгоритм, а потім його сумлінно реалізував (я вагаюся сказати «правильно»!)
Стів Джессоп,

1
@wabbit Це дискусійно. Те, що вам очевидно, може бути не очевидним для першого курсу.
Джухо

30

Найкращий приклад, з яким я коли-небудь стикався, - це перевірка первинності:

вхід: натуральне число p, p! = 2
вихід: є простим чи ні?
алгоритм: обчислити 2 ** (p-1) mod p. Якщо результат = 1, то p є простим іншим, p не є.

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

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


Тест на перманентність Ферма є імовірнісним тестом, тому ваш стан не є правильним.
Фемареф

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

2
Це приємний приклад з обмеженням: для практичного використання тестування первинності, яке я знайомий, а саме асиметричного генерування криптографічних ключів, ми використовуємо ймовірнісні алгоритми! Цифри занадто великі для точних тестів (якби їх не було, вони б не були придатні для криптовалюти, оскільки ключі могли бути знайдені грубою силою в реалістичний час).
Жиль

1
обмеження, на яке ви посилаєтесь, є практичним, а не теоретичним, а прості тести в криптосистемах, наприклад, RSA , зазнають рідкісних / дуже малоймовірних збоїв саме з цих причин, що ще раз підкреслює важливість прикладу. тобто на практиці іноді це обмеження приймається як неминуче. Існують алгоритми часу P для тестування первинності, наприклад, AKS, але для "менших" чисел, що використовуються на практиці, вони забирають занадто багато часу.
vzn

Якщо ви протестуєте не лише 2 p, але й p для 50 різних випадкових значень 2 ≤ a <p, то більшість людей знають, що це імовірнісно, ​​але з відмовами настільки малоймовірними, що більш ймовірно, що несправність у вашому комп'ютері призведе неправильна відповідь. З 2 p, 3 p, 5 p і 7 p збої вже дуже рідкісні.
gnasher729

21

Ось такий, який кинув на мене google повторень на конвенції, на яку я пішов. Він був закодований в C, але він працює іншими мовами, які використовують посилання. Вибачте за те, що потрібно кодувати на [cs.se], але це лише проілюструвати.

swap(int& X, int& Y){
    X := X ^ Y
    Y := X ^ Y
    X := X ^ Y
}

Цей алгоритм буде працювати для будь-яких значень, заданих x і y, навіть якщо вони однакові. Однак він не працюватиме, якщо його називають swap (x, x). У цій ситуації х закінчується як 0. Тепер це може вас не задовольнити, оскільки ви можете якось довести, що ця операція є математично правильною, але все ж забути про цей крайній випадок.


1
Цей трюк був використаний у змаганнях C, щоб створити хибну реалізацію RC4 . Читаючи цю статтю ще раз, я щойно помітив, що цей хак, ймовірно, був поданий @DW
CodesInChaos

7
Цей недолік справді тонкий - однак цей недолік є специфічним для мови, тому насправді не є вадою в алгоритмі; це недолік у реалізації. Можна було б придумати й інші приклади дивних диваків, які полегшують приховування тонких недоліків, але це було насправді не те, що я шукав (я шукав щось на рівні абстрагування алгоритмів). У будь-якому випадку, цей недолік не є ідеальною демонстрацією цінності доказу; якщо ви вже не замислюєтесь про подорож, ви, можливо, переглянете ту саму проблему, коли випишете свій "доказ" правильності.
DW

Ось чому я здивований, що за цей голос проголосували так високо.
ZeroUltimax

2
@DW Це питання про те, в якій моделі ви визначаєте алгоритм. Якщо ви перейдете до рівня, коли посилання на пам'ять явні (а не загальна модель, яка передбачає відсутність спільного доступу), це недолік алгоритму. Недолік насправді не є специфічним для мови, він виявляється на будь-якій мові, яка підтримує спільний доступ до пам'яті.
Жиль

16

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

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

В основному тести не охоплювали всіх випадків використання: хоча одновимірне використання RANDU було (мабуть, переважно) добре, воно не підтримувало його використання для вибірки тривимірних точок (таким чином).

Правильна псевдовипадкова вибірка - справа хитра. На щастя, там існують потужні набори тестів, наприклад, dieharder, які спеціалізуються на передачі всієї статистики, яку ми знаємо, на запропонованому генераторі. Чи достатньо?

Якщо чесно, я не маю уявлення, що можна довести для PRNG.


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

1
Це гарна ідея того, що важко перевірити, але RNG також важко довести. PRNG не настільки схильні до помилок у впровадженню, скільки до погано визначених. Випробування на кшталт diehard корисні для деяких застосувань, але для криптовалюти ви можете пройти diehard і все ще сміятися з кімнати. Немає «перевіреного безпечного» CSPRNG, найкраще, на що можна сподіватися, - це довести, що якщо ваш CSPRNG порушений, то це також AES.
Жиль

@Gilles Я не намагався вникати в криптовалюту, лише статистична випадковість (я думаю, що в обох є ортогональні вимоги). Чи варто це зрозуміти у відповіді?
Рафаель

1
Крипто випадковість передбачає статистичну випадковість. Немає математично формального визначення, хоча, наскільки я знаю, крім ідеалу (і суперечить концепції PRNG, реалізованої на детермінованій машині Тьюрінга), поняття інформаційно-теоретичної випадковості. Чи має статистична випадковість формальне визначення за межами "має бути незалежним від розподілів, які ми перевіримо"?
Жиль

1
@vzn: що означає бути випадковою послідовністю чисел, можна визначити багатьма можливими способами, але простим є "велика складність Комолгорова". У цьому випадку легко показати, що визначення випадковості не можна визначити.
коді

9

2D локальний максимум

n×nA

(i,j)A[i,j]

A[i,j+1],A[i,j1],A[i1,j],A[i+1,j]A

0134323125014013

то кожна жирна клітина є локальним максимумом. Кожен непустий масив має щонайменше один локальний максимум.

O(n2)

AXXA(i,j)X(i,j)(i,j)

AXAX(i,j)A

AA

(i,j)AA(i,j)

n2×n2A(i,j)

T(n)n×nT(n)=T(n/2)+O(n)T(n)=O(n)

Таким чином, ми довели наступну теорему:

O(n)n×n

Або ми?


T(n)=O(nlogn)T(n)=T(n/2)+O(n)

2
Це прекрасний приклад! Я це люблю. Дякую. (Нарешті я з'ясував недолік цього алгоритму. З часових позначок ви можете отримати нижню межу на те, скільки часу у мене зайняло. Я занадто соромлюся, щоб розкрити фактичний час. :-)
DW

1
O(n)

8

Це приклади первинності, оскільки вони поширені.

(1) Первинність у SymPy. Випуск 1789 року . На відомому веб-сайті було проведено неправильне тестування, яке провалилося не пізніше 10 ^ 14. Хоча виправлення було правильним, це було лише латання дірок, а не переосмислення проблеми.

(2) Первинність в Perl 6. Perl6 додав первинний прайм, який використовує ряд тестів MR на фіксованій основі. Існують відомі контрприклади, але вони досить великі, оскільки кількість тестів за замовчуванням величезна (в основному приховуючи реальну проблему, знижуючи продуктивність). Це буде вирішено найближчим часом.

(3) Первинність у FLINT. n_isprime (), що повертає справжнє для композитів , оскільки виправлено. В основному те саме питання, що і SymPy. Використовуючи базу даних Feitsma / Galway псевдоприкладів SPRP-2 до 2 ^ 64, ми можемо перевірити їх.

(4) Матерія Перла: Первинність. is_aks_prime порушено . Ця послідовність здається схожою на безліч реалізацій AKS - безліч кодів, які або спрацьовували випадково (наприклад, загубилися на етапі 1 і в кінцевому підсумку робили всю справу шляхом пробного поділу), або не працювали для великих прикладів. На жаль, AKS настільки повільний, що його важко перевірити.

(5) Попереднє значення 2.2 для Пари є Математика :: Білет Пари . Він використовував 10 випадкових баз для тестів на МР (з фіксованим насінням при запуску, а не з фіксованим насінням GMP кожного виклику). Він скаже вам, що 9 - це приблизно 1 з кожного 1М-дзвінка. Якщо ви виберете потрібне число, ви зможете вийти з ладу відносно часто, але цифри стають рідшими, тому на практиці це не дуже добре відображається. З тих пір вони змінили алгоритм та API.

Це не помиляється, але це класика ймовірнісних тестів: скільки раундів ви даєте, скажімо, mpz_probab_prime_p? Якщо ми дамо йому 5 раундів, це впевнено виглядає, що він працює добре - номери повинні пройти базовий тест Ферма-210 і потім 5 попередньо вибраних баз тестів Міллера-Рабіна. Ви не знайдете контрприклад до 3892757297131 (з GMP 5.0.1 або 6.0.0a), тому вам доведеться зробити багато тестування, щоб знайти його. Але є тисячі зустрічних прикладів під 2 ^ 64. Таким чином, ви продовжуєте збільшувати кількість. Як далеко? Чи є противник? Наскільки важлива правильна відповідь? Ви плутаєте випадкові бази з фіксованими базами? Чи знаєте ви, які розміри вводу вам дадуть?

1016

Їх досить складно перевірити правильно. Моя стратегія включає очевидні одиничні тести, плюс крайові випадки, а також приклади відмов, виявлені перед або в інших пакунках, тестуйте порівняно з відомими базами даних, де це можливо (наприклад, якщо ви робите один тест базового рівня-2 МР, то ви зменшили обчислювально нездійсненні завдання тестування 2 ^ 64 чисел для тестування близько 32 мільйонів чисел), і, нарешті, безліч рандомізованих тестів, використовуючи інший пакет як стандарт. Останній пункт працює для таких функцій, як первинність, де є досить простий вхід і відомий вихід, але досить багато завдань схожі на це. Я використовував це для виявлення дефектів як у власному коді розробки, так і у випадкових проблемах у пакунках порівняння. Але враховуючи нескінченний вхідний простір, ми не можемо протестувати все.

Щодо доведення правильності, то тут ще один приклад первинності. Методи BLS75 та ECPP мають концепцію сертифіката первинності. В основному після того, як вони відхиляються, роблячи пошук, щоб знайти значення, які працюють на їх докази, вони можуть виводити їх у відомому форматі. Потім можна написати верифікатор або домовитись когось іншого. Вони запускаються дуже швидко порівняно зі створенням, і тепер або (1) обидва фрагменти коду є неправильними (отже, чому ви віддаєте перевагу іншим програмістам для верифікаторів), або (2) математика, що стоїть на ідеї доказування, неправильна. №2 завжди можливий, але вони, як правило, публікуються та переглядаються багатьма людьми (а в деяких випадках досить просто, щоб ви пройшли через себе).

Для порівняння, такі методи, як AKS, APR-CL, пробний поділ або детермінований тест Рабіна, не дають іншого виходу, окрім "простих" або "композитних". В останньому випадку ми можемо мати чинник, який ми можемо перевірити, але в першому випадку нам не залишається нічого іншого, крім цього одного біта виводу. Чи правильно працювала програма? Данно.

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


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

Ви маєте рацію в першому пункті - правильний алгоритм + помилка не в цьому суть, хоча обговорення та інші приклади також їх плутають. Поле стигло гіпотезами, які працюють у невеликій кількості, але неправильні. Для пункту (2) це справедливо для деяких, але мої приклади №1 та №3 були не в цьому випадку - вважалося, що алгоритм був правильним (ці 5 підстав дають перевірені результати для чисел під 10 ^ 16), потім пізніше виявив, що це не так.
DanaJ

Це не фундаментальне питання тестів на псевдопримітність?
asmeurer

Asmeurer, так, у моєму №2 та подальшому обговоренні їх. Але №1 і №3 були випадками використання Міллера-Рабіна з відомими базами для отримання детермінованих правильних результатів нижче порогового значення. Отже, в цьому випадку "алгоритм" (використовуючи термін "вільно" для узгодження з ОП) був невірним. №4 не є імовірним простим тестом, але, як зазначав DW, алгоритм працює добре, просто реалізація є складною. Я включив його, оскільки це призводить до подібної ситуації: тестування потрібне, і наскільки ви виходите за рамки простих прикладів, перш ніж сказати, що це працює?
DanaJ

Деякі ваші дописи, здається, відповідають на питання, а деякі - ні (див. Коментар @ DW). Видаліть приклади (та інший вміст), які не відповідають на запитання.
Рафаель

7

Алгоритм перетасовки Фішера-Йейтса-Кнута є (практичним) прикладом, про який прокоментував один з авторів цього сайту .

Алгоритм генерує випадкову перестановку даного масиву як:

 // To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ i
       exchange a[j] and a[i]

ij0ji

"Наївним" алгоритмом може бути:

 // To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ n-1
       exchange a[j] and a[i]

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

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

nn!=n×n1×n2..nn1

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


1
Дивіться тут приклад підтвердження правильності алгоритму переміщення.
Рафаель

5

Найкращий приклад (читайте: те, що мене найбільше болить в задниках ), я коли-небудь бачив, пов'язане з гіпотезою коллац. Я був у змаганні з програмування (з першочерговим призом 500 доларів на першому місці), в якому однією з проблем було знайти мінімальну кількість кроків, необхідних для двох чисел, щоб досягти однакової кількості. Рішення, звичайно, полягає в тому, щоб по черзі крокувати кожен з них, поки вони обидва не досягнуть чогось, що бачили раніше. Нам дали нам діапазон чисел (я думаю, це було від 1 до 1000000) і сказали, що гіпотеза Колаца була перевірена до 2 ^ 64, тому всі числа, які нам дали, врешті збігаються в 1. Я використовував 32-бітні цілі числа робити кроки, однак. Виявляється, що існує одне незрозуміле число між 1 і 1000000 (170 тис. Щось), яке спричинить переповнення 32-бітного цілого числа у свій час. Насправді ці цифри є вкрай рідкісними нижче 2 ^ 31. Ми перевірили нашу систему на ВЕЛИЧЕЗНІ числа, які перевищують 1000000, щоб "переконатися", що переповнення не відбулося. Виходить набагато менша кількість, яку ми просто не тестували, спричиняючи переповнення. Тому що я використовував "int" замість "long", я отримав лише приз у розмірі 300 доларів, а не приз у розмірі 500 доларів.


5

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

Для цих проблем у класі я повинен показати доказ Knapsack 0/1 ( динамічне програмування ) для усунення будь-яких сумнівів і для жадної версії проблеми також. Насправді обидва докази не є банальними, і студенти, ймовірно, вважають їх дуже корисними. Крім того, є коментар до цього в CLRS 3ed , Глава 16, Сторінка 425-427 .

Проблема: злодій грабує магазин і може перенести максимальну вагу Вт у свій рюкзак. Існує п предметів, а цей предмет важить wi і коштує vi доларів. Які предмети повинен взяти злодій? щоб максимізувати свій прибуток ?

Проблема з рюкзаком 0/1 : налаштування те саме, але елементи можуть не розбиватися на більш дрібні шматки , тому злодій може вирішити або взяти предмет, або залишити його (двійковий вибір), але може не взяти частину предмета .

І ви можете отримати від студентів кілька ідей або алгоритмів, які відповідають тій же ідеї, що і жадібна версія версії, це:

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

Це вам корисно? насправді ми знаємо, що проблема з монетою є версією проблеми з рюкзаком. Але є ще кілька прикладів проблем лісового ранку, наприклад, що стосується Рюкзака 2D (це справді корисно, коли ви хочете різати деревину для виготовлення меблів , я бачив у місцевому з мого міста), дуже часто вважають, що жадібні тут теж працюють, але ні.


Жадібний був уже висвітлений у прийнятій відповіді , але проблема "Рюкзака", зокрема, добре підходить для встановлення деяких пасток.
Рафаель

3

Поширена помилка - неправильно реалізувати алгоритми перетасування. Дивіться дискусію на wikipedia .

n!nn(n1)n


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

Ви, звичайно, тестуєте його. Уніфікована випадковість - це далеко не "що-небудь може трапитися на виході". Чи не будете ви підозрілими, якби програма, яка казала, щоб наслідувати кістки, дала вам 100 3 підряд?
Пер Александерсон

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

1
@PerAlexandersson: Навіть якщо ви генеруєте лише одну перетасування, вона не може бути справді випадковою, використовуючи MT з n> 2080. Тепер відхилення від очікуваного буде дуже малим, тому ви, ймовірно, не потурбуєтеся ... але це стосується навіть, якщо ви генеруєте набагато менше, ніж за цей період (як вказує асмельєр вище).
Чарльз

2
Ця відповідь, здається, застаріла більш досконалою Нікосом М. ?
Рафаель

2

Пітони PEP450, які запровадили статистичні функції в стандартну бібліотеку, можуть зацікавити. Як частина обґрунтування наявності функції, яка обчислює дисперсію в стандартній бібліотеці пітона, автор Стівен Д'Апрано пише:

def variance(data):
        # Use the Computational Formula for Variance.
        n = len(data)
        ss = sum(x**2 for x in data) - (sum(data)**2)/n
        return ss/(n-1)

Наведене вище видається правильним у випадковому тесті:

>>> data = [1, 2, 4, 5, 8]
>>> variance(data)
  7.5

Але додавання константи до кожної точки даних не повинно змінювати дисперсію:

>>> data = [x+1e12 for x in data]
>>> variance(data)
  0.0

І дисперсія ніколи не повинна бути негативною:

>>> variance(data*100)
  -1239429440.1282566

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


1
n1

2
@Raphael: Хоча для справедливості вибраний алгоритм є поганим вибором даних з плаваючою комою.

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

Окрім точного коментаря Рафаеля, недоліком цього прикладу є те, що я не думаю, що доказ коректності допоможе уникнути цієї вади. Якщо вам невідомі тонкощі арифметики з плаваючою комою, ви можете подумати, що ви довели це правильно (довівши, що формула є дійсною). Тож це не ідеальний приклад, щоб навчити учнів, чому важливо довести їх алгоритми правильними. Якби студенти побачили цей приклад, я підозрюю, що вони замість цього намалювали б урок "плаваюча точка / числові обчислення - хитрість".
DW

1

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

Проблема : Напишіть функцію, яка приймає негативне ціле число і повертає кількість належних дільників n 2 + nnn2+n+410<dd divides n2+n+41d<n2+n+41

Пропоноване рішення :

int f(int n) {
   return 1;
}

n=0,1,2,,39n=40

Це "спробуй кілька невеликих випадків і підрахуй алгоритм з результату" підходить часто (хоча і не настільки надзвичайно, як тут) на змаганнях з програмування, де тиском потрібно створити алгоритм, який (a) швидко реалізується і (b ) має швидкий час роботи.


5
Я не думаю, що це дуже вдалий приклад, тому що мало хто намагається знайти дільників многочлена, повертаючись 1.
Брайан S

1
Припустимо, задачу задають для визначення заданого цілого числа nn3n

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

@NikosM. Хе. Мені здається, що я тут б'ю мертвого коня, але другий параграф питання говорить про те, що "якщо їх алгоритм працює правильно на кількох прикладах, включаючи всі кутові випадки, які вони можуть спробувати спробувати, то вони роблять висновок, що алгоритм повинен будьте правильні. Завжди є студент, який запитує: "Чому мені потрібно довести свій алгоритм правильним, якщо я можу просто спробувати його в декількох тестових випадках?" У цьому випадку для перших 40 значень (набагато більше, ніж студент ймовірно, спробую), повернення є правильним. Мені здається, саме це шукала ОП.
Рік Декер

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