「いくつかのテストケースを試す」ヒューリスティックを欺く方法:正しいように見えるが実際は正しくないアルゴリズム


105

ある問題のアルゴリズムが正しいかどうかをテストするための通常の出発点は、いくつかの単純なテストケースでアルゴリズムを手動で実行してみることです。 「。これは優れたヒューリスティックです。これは、アルゴリズムに対する誤った試行の多くを迅速に排除し、アルゴリズムが機能しない理由を理解するための優れた方法です。

ただし、アルゴリズムを学習するとき、一部の学生はそこでやめようとします:試行することを考えられるすべてのコーナーケースを含む少数の例でアルゴリズムが正しく機能する場合、アルゴリズムが正しい必要があると結論付けます。「いくつかのテストケースで試してみることができるのに、なぜアルゴリズムが正しいことを証明する必要があるのか​​」と尋ねる学生が常にいます。

それでは、「多数のテストケースを試す」ヒューリスティックをどのように欺くのでしょうか。このヒューリスティックでは不十分であることを示す良い例を探しています。言い換えれば、表面的には正しいように見えるアルゴリズムの1つ以上の例を探しており、だれかが思い付く可能性のあるすべての小さな入力に対して正しい答えを出力しますが、アルゴリズムは実際には動作しません。たぶん、アルゴリズムはたまたますべての小さな入力で正しく動作し、大きな入力に対してのみ失敗するか、異常なパターンを持つ入力に対してのみ失敗します。

具体的には、私は探しています:

  1. アルゴリズム。欠陥はアルゴリズムレベルである必要があります。実装のバグを探していません。(たとえば、最低限、この例は言語にとらわれず、ソフトウェアエンジニアリングや実装の問題ではなく、アルゴリズムの問​​題に関連する必要があります。)

  2. 誰かがもっともらしく思いつくかもしれないアルゴリズム。擬似コードは、少なくとももっともらしいように見えるはずです(たとえば、難読化された、または明らかに疑わしいコードは良い例ではありません)。宿題や試験の問題を解決しようとするときに、一部の学生が実際に思いついたアルゴリズムである場合、ボーナスポイントがあります。

  3. 妥当な手動テスト戦略に高い確率で合格するアルゴリズム。手作業でいくつかの小さなテストケースを試みる人は、欠陥を発見する可能性は低いはずです。たとえば、「12個の小さなテストケースで手動でQuickCheckをシミュレート」しても、アルゴリズムが正しくないことを明らかにすることはできません。

  4. 好ましくは、決定論的アルゴリズム。私は多くの学生が「手作業でいくつかのテストケースを試す」が決定論的アルゴリズムが正しいかどうかをチェックする合理的な方法であると考えているのを見てきましたが、ほとんどの学生はいくつかのテストケースを試すことは確率論を検証する良い方法だとは思わないでしょうアルゴリズム。確率的アルゴリズムの場合、特定の出力が正しいかどうかを確認する方法がないことがよくあります。また、出力分布に関する有用な統計テストを実行するのに十分な例をクランクすることはできません。したがって、決定論的アルゴリズムは学生の誤解の中心により明確になるため、決定論的アルゴリズムに焦点を当てることを好みます。

私はあなたのアルゴリズムが正しいことを証明することの重要性を教えたいです。そして、このようないくつかの例を使って、正当性の証明の動機付けを助けたいと思っています。私は比較的単純で学部生がアクセスしやすい例を好むでしょう。重い機械や数学的/アルゴリズム的背景を必要とする例はあまり有用ではありません。また、「不自然」なアルゴリズムも必要ありません。ヒューリスティックを欺くための奇妙な人工アルゴリズムを構築するのは簡単かもしれませんが、非常に不自然に見えるか、このヒューリスティックを欺くためだけに明らかなバックドアが構築されている場合、おそらく学生には納得しません。良い例はありますか?


2
私はあなたの質問が大好きです。また、先日数学で見た非常に興味深い質問に関連しています。あなたはそれを見つけることができ、ここで
ZeroUltimax

1
さらに掘り下げてみると、これら 2つの幾何学的アルゴリズムが見つかりました。
ZeroUltimax 14

@ZeroUltimaxそのとおりです。3つの非共線Ptの中心点が内側にあるとは限りません。迅速な解決策は、左端と右端を結ぶ線上にptを取得することです。他に問題はありますか?
情報14

この質問の前提は、頭を動かすのが難しいという点で私には奇妙に思えますが、説明されているアルゴリズム設計のプロセスは根本的に壊れているものだと思います。「そこで止まる」ことのない学生でさえ、それは運命的です。1>アルゴリズムの作成、2>テストケースの検討/実行、3a>停止、または3b>正しいことの証明。最初のステップはかなりしている問題領域のための入力クラスを特定します。コーナーケースとアルゴリズム自体は、それらから発生します。(続き)
ミンダー氏14

1
実装のバグと欠陥のあるアルゴリズムを正式に区別するにはどうすればよいですか?私はあなたの質問に興味がありましたが、同時に、あなたが説明する状況は例外というよりも規則のように思われるという事実に悩まされました。多くの人は実装したものをテストしますが、通常はまだバグがあります。最も賛成の回答の2番目の例は、まさにそのようなバグです。
babou 14

回答:


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年の10進数化以前)には、これの実例がありました。4シリングをカウントする貪欲なアルゴリズムでは、ハーフクラウン(2½シリング)、1シリングコイン、および6ペンス(1/2シリング)を使用します。しかし、最適なソリューションは2つのフロリンを使用します(それぞれ2シリング)。
マークドミナス

1
実際、多くの場合、貪欲なアルゴリズムは合理的と思われますが、機能しません-別の例は、最大の二者間マッチングです。一方、貪欲なアルゴリズムは機能しないはずの例もありますが、機能します:最大スパニングツリー。
jkff

62

私はすぐにR. Backhouseの例を思い出しました(これは彼の本の1つにあったかもしれません)。どうやら、彼は2つの文字列の等価性をテストするために学生がPascalプログラムを作成する必要があるプログラミングの割り当てを割り当てていたようです。学生が提出したプログラムの1つは次のとおりです。

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

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

write(issame);

次の入力でプログラムをテストできます。

「大学」「大学」真。OK

「コース」「コース」真。OK

"" "" ; OK

「大学」「コース」偽。OK

「講義」「もちろん」偽。OK

「精度」「正確」偽、OK

これらはすべて非常に有望なようです。おそらくプログラムは実際に機能するでしょう。しかし、「純粋」と「真」と言うより慎重なテストでは、出力に誤りがあることが明らかになります。実際、文字列が同じ長さと同じ最後の文字を持っている場合、プログラムは「True」と言います!

ただし、テストは非常に徹底的でした。長さの異なる文字列、長さは等しいが内容が異なる文字列、さらには文字列が等しい場合もありました。さらに、学生はすべてのブランチをテストして実行しました。ここではテストが不注意だったと主張することはできません。プログラムが実際に非常に単純であるため、十分にテストするための動機とエネルギーを見つけるのは難しいかもしれません。


別のかわいい例は、バイナリ検索です。TAOCPで、Knuthは「バイナリ検索の基本的な考え方は比較的簡単ですが、詳細は驚くほどトリッキーになる可能性がある」と述べています。どうやら、Javaのバイナリ検索実装のバグは10年間気付かれていませんでした。これは整数オーバーフローのバグであり、十分な大きさの入力でのみ現れました。バイナリ検索実装のトリッキーな詳細については、Bentleyの 『Programming Pearls』でも取り上げられています。

結論:バイナリ検索アルゴリズムが正しいかどうかをテストするだけでは、驚くほど難しい場合があります。


9
もちろん、この欠陥はソースから明らかです(以前に同様のことを書いたことがある場合)。
ラファエル

3
サンプルプログラムの単純な欠陥が修正されたとしても、文字列にはかなりの興味深い問題があります!文字列の反転は古典的です-それを行う「基本的な」方法は、単にバイトを反転させることです。次に、エンコーディングが作用します。次に代理(通常2回)。問題は、もちろん、メソッドが正しいことを正式に証明する簡単な方法がないことです。
オーダス14

6
質問を完全に誤解しているのかもしれませんが、これはアルゴリズム自体の欠陥ではなく、実装の欠陥のようです。
Mr.Mindor

8
@ Mr.Mindor:プログラマーが正しいアルゴリズムを書き留めてから間違って実装したか、間違ったアルゴリズムを書き留めて忠実に実装したかをどのように確認できますか(「正しく」と言うのをためらいます!)
Steve Jessop

1
@wabbitそれは議論の余地があります。あなたにとって明らかなことは、1年生にとっては明らかではないかもしれません。
ジュホ

30

私が出会った中で最高の例は、素数性のテストです:

入力:自然数p、p!= 2
出力:paプライムかどうか?
アルゴリズム:2 **(p-1)mod pを計算します。結果= 1の場合、pは素数であり、そうでない場合はpです。

これは、ごく少数の反例を除き、ほぼすべての数で機能します。実際には、現実的な期間で反例を見つけるためにマシンが必要です。最初の反例は341であり、反例の密度はpの増加とともに減少しますが、ほぼ対数になります。

単にパワーの基礎として2を使用する代わりに、前の素数が1を返した場合に、基礎として追加の増加する小さな素数を使用することでアルゴリズムを改善できます。さらに、このスキームに対する反例、すなわちCarmichael数、かなりまれですが


Fermatの素数性検定は確率的検定であるため、事後条件は正しくありません。
Femaref 14

5
ofcこれは確率的テストですが、答えは(より一般的に)正確なアルゴリズムと間違えられた確率的アルゴリズムがエラーの原因になる可能性があることをうまく示しています。詳細カーマイケル数
vzn

2
これは良い例ですが、制限があります。私が精通している素数性テストの実際の使用、つまり非対称暗号鍵の生成には、確率的アルゴリズムを使用します。正確なテストには数値が大きすぎます(そうでない場合、現実的な時間にブルートフォースによってキーが検出される可能性があるため、暗号に適していません)。
ジル14

1
参照する制限は理論的ではなく実用的であり、暗号システムの主要なテスト、たとえばRSAは、まさにこれらの理由でまれ/非常に起こりにくい障害にさらされ、例の重要性を強調しています。つまり、実際には、この制限が避けられないものとして受け入れられることがあります。AKSなどの素数テスト用のP時間アルゴリズムがありますが、実際に使用される「より小さい」数値には時間がかかりすぎます。
vzn 14

2 pだけでなく、50種類のランダム値2≤a <pで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)として呼び出された場合は機能しません。この状況では、xは0になります。この操作が数学的に正しいことを何らかの方法で証明することはできますが、このエッジケースについては忘れることができるため、これで満足できないかもしれません。


1
その手口は、手に負えないCコンテストで欠陥のあるRC4実装を作成するために使用されました。再びその記事を読んで、私はこのハックはおそらく@DWにより提出されたことに気づいた
CodesInChaos

7
この欠陥は確かに微妙ですが、言語固有のものですが、アルゴリズムの欠陥ではありません。それは実装の欠陥です。微妙な欠陥を簡単に隠すことができる言語の奇妙な例は他にもありますが、それは私が探していたものではありませんでした(アルゴリズムの抽象化レベルで何かを探していました)。いずれにせよ、この欠陥は証明の価値を示す理想的なデモンストレーションではありません。エイリアスを既に考えていない限り、正しいことの「証拠」を書き出すときに同じ問題を見落とすことになります。
DW

だから私はこれが非常に高く投票されたことに驚きました。
ZeroUltimax 14

2
@DWこれは、どのモデルでアルゴリズムを定義するかという問題です。メモリ参照が明示的なレベルまで下がった場合(共有がないことを前提とする一般的なモデルではなく)、これはアルゴリズムの欠陥です。この欠陥は実際には言語固有のものではなく、メモリ参照の共有をサポートするすべての言語で発生します。
ジル14

16

本質的にテストするのが難しいアルゴリズムのクラス全体があります:擬似乱数ジェネレーター。単一の出力をテストすることはできませんが、統計を使用して(多くの)一連の出力を調査する必要があります。何をどのようにテストするかにもよりますが、非ランダムな特性を見逃す可能性があります。

物事がひどく間違っていた有名なケースの1つはRANDUです。その時点で利用可能な精査に合格しました- 後続の出力のタプルの動作を考慮できませんでした。すでにトリプルは多くの構造を示しています:

基本的に、テストはすべてのユースケースをカバーしていませんでした:RANDUの1次元の使用は(おそらくほとんど)大丈夫でしたが、3次元の点をサンプリングするための使用はサポートしていませんでした(この方法で)。

適切な擬似ランダムサンプリングは、トリッキーなビジネスです。幸いなことに、例えば、強力なテストがスイート日、あるdieharder提案ジェネレータで私たちが知っているすべての統計を投げるに特化。それは十分か?

公平を期すために、PRNGで何を証明できるかはわかりません。


2
しかし、実際には一般的に、PRNGに欠陥がないことを証明する方法はなく、弱いテストと強いテストの無限の階層しかありません。実際には、厳密な意味で「ランダム」であることを証明することは、おそらく決定不能です(ただし、証明されたハベント)。
vzn 14

1
これはテストするのが難しいことの良い考えですが、RNGも証明するのが難しいです。PRNGは、指定が不適切であるほど実装のバグが発生しにくい。diehardのようなテストはいくつかの用途に適していますが、暗号の場合、diehardに合格しても、部屋の外で笑うことができます。「安全であることが証明された」CSPRNGはありません。CSPRNGが破損した場合、AESも破損していることを証明することをお勧めします。
ジル14

@Gilles私は暗号化しようとはしていませんでしたが、統計的なランダム性のみを求めていました(この2つにはかなり直交する要件があると思います)。答えでそれを明確にする必要がありますか?
ラファエル

1
暗号のランダム性は、統計的なランダム性を意味します。しかし、私の知る限り、情報理論的ランダム性の概念(決定論的チューリングマシンで実装されたPRNGの概念と矛盾する)を除いて、どちらも数学的に正式な定義はありません。統計的ランダム性には、「テスト対象の分布から独立している必要があります」を超える正式な定義がありますか?
ジル

1
@vzn:ランダムな数列の意味は多くの可能な方法で定義できますが、簡単なものは「大きなコモルゴロフの複雑さ」です。その場合、ランダム性の決定が決定できないことを示すのは簡単です。
コーディ14

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

太字のセルはそれぞれ極大値です。空でない配列にはすべて、少なくとも1つのローカル最大値があります。

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年。よく知られているWebサイトに誤ったテストが行​​われ、10 ^ 14まで失敗しませんでした。修正は正しいものでしたが、問題を再考するのではなく、単にパッチを当てるだけでした。

(2)Perl 6の素数性。Perl6には、固定ベースで多数のMRテストを使用するis-primeが追加されました。反例は知られていますが、デフォルトのテスト数が膨大であるため、かなり大きくなります(基本的にパフォーマンスを低下させることで実際の問題を隠します)。これはすぐに対処されます。

(3)FLINTの素数。 修正されたためn_isprime()はcompositesに対してtrueを返します。基本的にSymPyと同じ問題。SPRP-2擬似プライムのFeitsma / Galwayデータベースを2 ^ 64に使用して、これらをテストできます。

(4)PerlのMath :: Primality。 is_aks_primeが壊れています。このシーケンスは、多くのAKS実装に類似しているようです-偶然に動作した(たとえば、ステップ1で失われ、試用部門ですべてを実行した)多くのコードは、より大きな例では動作しませんでした。残念ながら、AKSは非常に遅いため、テストが困難です。

(5)Pariの2.2より前のis_prime。 Math :: Pariチケット。MRテストには10​​個のランダムベースを使用しました(呼び出しごとにGMPの固定シードではなく、起動時に固定シードを使用)。9が、1Mコールごとに約1であることがわかります。適切な番号を選択すると、比較的頻繁に失敗する可能性がありますが、番号がまばらになるため、実際にはあまり表示されません。その後、アルゴリズムとAPIが変更されました。

これは間違いではありませが、古典的な確率的テストです。たとえば、mpz_probab_prime_pを何ラウンド与えますか?5ラウンドを与えると、うまくいくように見えます。数字はbase-210 Fermatテストに合格し、次に5つの事前選択されたbases-Miller-Rabinテストに合格する必要があります。3892757297131(GMP 5.0.1または6.0.0aを使用)まで反例は見つからないため、それを見つけるには多くのテストを行う必要があります。しかし、2 ^ 64には数千の反例があります。だからあなたは数を増やし続けます。どのくらい遠い?敵はいますか?正解はどれくらい重要ですか?ランダムベースと固定ベースを混同していますか?与えられる入力サイズを知っていますか?

1016

これらを正しくテストすることは非常に困難です。私の戦略には、明白な単体テストに加えて、エッジケースに加えて、可能であればテスト対既知のデータベースの前または他のパッケージで見られる障害の例が含まれます(たとえば、単一のベース2 MRテストを行う場合、計算上実行不可能な2 ^ 64の数字をテストし、約3,200万の数字をテストするタスク)、最後に、別のパッケージを標準として使用する多くのランダム化テスト。最後の点は、かなり単純な入力と既知の出力がある素数性のような関数で機能しますが、かなりの数のタスクがこのようなものです。これを使用して、自分の開発コードの欠陥と、比較パッケージの偶発的な問題の両方を見つけました。しかし、無限の入力空間を考えると、すべてをテストすることはできません。

正しさの証明については、別の素数の例があります。BLS75メソッドとECPPには、原始証明書の概念があります。基本的に、証明に役立つ値を見つけるための検索を行った後、既知の形式で出力できます。その後、検証者を作成するか、他の誰かにそれを作成してもらうことができます。これらは作成と比較して非常に高速に実行され、(1)両方のコードが正しくない(検証者に他のプログラマを好む理由)か、(2)証明アイデアの背後にある数学が間違っているかのどちらかです。#2は常に可能ですが、これらは通常、複数の人によって公開およびレビューされています(場合によっては、自分で簡単に確認できるほど簡単です)。

これに対して、AKS、APR-CL、試行分割、決定論的ラビン検定などのメソッドはすべて、「prime」または「composite」以外の出力を生成しません。後者の場合、検証できる要因がある可能性がありますが、前者の場合は、この1ビットの出力しか残されていません。プログラムは正常に機能しましたか?ダンノ

いくつかのおもちゃの例だけでソフトウェアをテストすることは重要です。また、アルゴリズムの各ステップでいくつかの例を見て、「この入力が与えられたら、この状態でここにいるのは理にかなっていますか?」


1
これらの多くは、(1)実装エラー(基礎となるアルゴリズムは正しいが正しく実装されなかった)のように見えます。これは興味深いが、この質問のポイントではありません。または(2)は高速で、ほとんど動作しますが、非常に小さな確率で失敗する可能性があります(1つのランダムベースまたはいくつかの固定/ランダムベースでテストするコードの場合、パフォーマンスのトレードオフを行うことを選択した人は誰でも希望します)。
DW

あなたは最初の点で正しいです-正しいアルゴリズム+バグはポイントではありませんが、議論や他の例もそれらを混同しています。このフィールドは、少数では機能するが正しくない推測で熟している。ポイント(2)については一部に当てはまりますが、私の例#1と#3はこの場合ではありませんでした-アルゴリズムは正しいと信じられていました(これらの5つのベースは10 ^ 16未満の数値に対して実証済みの結果を提供します)、その後そうではないことを発見した。
DanaJ

これは、擬似素数性テストの基本的な問題ではありませんか?
asmeurer 14

asmeurer、はい#2とはい、それらについては後で議論します。ただし、#1と#3は、既知のベースでMiller-Rabinを使用して、しきい値未満の決定論的で正しい結果を出す場合です。そのため、この場合、「アルゴリズム」(OPに一致するように大まかに用語を使用)は正しくありませんでした。#4はプライムテストの可能性はありませんが、DWが指摘したように、アルゴリズムは問題なく機能します。実装が難しいだけです。同様の状況につながるので、私はそれを含めました:テストが必要であり、それが機能すると言う前に、単純な例をどのくらい越えますか?
DanaJ

投稿の一部は質問に合っているようですが、一部はそうではありません(@DWのコメントを参照)。質問に答えていない例(およびその他のコンテンツ)は削除してください。
ラファエル

7

Fisher-Yates-Knuthシャッフリングアルゴリズムは(実用的な)例であり、このサイトの著者の1人がについてコメントしています。

このアルゴリズムは、指定された配列のランダム置換を次のように生成します。

 // 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

シャッフルアルゴリズムが正しいかどうか(バイアスがあるかどうか)を検証する場合の主な問題は、統計のために、多数のサンプルが必要になることです。codinghorrorの記事私は上記のリンクは正確に(と実際のテストで)説明しています。


1
シャッフルアルゴリズムの正当性証明の例については、こちらをご覧ください。
ラファエル

5

私が今まで見た中で最良の例(読んでください:私が最もお尻を痛めていること)は、collat​​z推測に関係しています。。問題は、2つの番号が同じ番号に達するために必要な最小ステップ数を見つけることでした。もちろん、解決策は、両方が前に見たものに到達するまで、それぞれを交互にステップすることです。数値の範囲(1〜1000000)が与えられ、collat​​z予想が2 ^ 64まで検証されたため、与えられた数値はすべて1に収束することを伝えました。32ビットを使用しましたただし、手順を実行する整数。1〜1000000のあいまいな数字(17万個)があり、32ビット整数がやがてオーバーフローすることがわかります。実際、これらの数字は2 ^ 31以下です。オーバーフローが発生していないことを「確認」するために、1000000をはるかに超える巨大な数値についてシステムをテストしました。オーバーフローの原因となるテストを行わなかっただけの、はるかに小さい数値が判明しました。「長い」の代わりに「int」を使用したため、賞金$ 500ではなく300ドルしか獲得できませんでした。


5

ナップザック0/1の問題は、ほぼすべての学生が貪欲なアルゴリズムによって解けると思い一つです。以前に貪欲なアルゴリズムが機能するナップザックの問題バージョンとしていくつかの貪欲なソリューションを示した場合、それはより頻繁に起こります。

これらの問題については、クラスで、疑いを取り除くためとナップザック0/1(動的計画法)の証明と貪欲な問題バージョンの証明を示す必要があります。実際、両方の証明は簡単ではなく、生徒たちはおそらく非常に役立つと思うでしょう。さらに、CLRS 3edの Chapter 16、ページ425-427にこれに関するコメントがあります。

問題: 泥棒は店を強奪し、ナップザックに最大重量のWを運ぶことができます。n個のアイテムがあり、i番目のアイテムの重量はwiであり、viドルの価値があります。泥棒はどのようなアイテムを取るべきですか?彼の利益を最大化するには

ナップザック0/1の問題:セットアップは同じですが、アイテムは小さな断片分割されない可能性があるため、泥棒はアイテム取り出すか、そのままにするかを選択できます(バイナリ選択) 。

そして、貪欲なバージョンの問題と同じアイデアに従ういくつかのアイデアやアルゴリズムを学生から得ることができます:

  • バッグの総容量を取得し、可能な限り最も価値のあるオブジェクトを配置し、バッグがいっぱいになるか、バッグに入れるための重量が等しいオブジェクトがなくなるため、オブジェクトを追加できないまでこのメソッドを繰り返します。
  • 他の間違った方法は考えています:より軽いアイテムを置いて、これらを最高価格から最低価格まで置きます。
  • ...

役に立ちましたか?実際、コインの問題はナップザックの問題バージョンであることがわかっています。しかし、ナップザックの問題の森にはもっと多くの例があります。例として、ナップザック2Dはどうですか家具を作るために木を切りたいときは本当に役立ちます。私は地元の都市で見ました)。欲張りもここで動作しますが、動作しません。


貪欲はすでに受け入れられた答えでカバーされていましが、特にナップザックの問題はいくつかのトラップを設定するのに適しています。
ラファエル

3

よくある間違いは、シャッフルアルゴリズムを間違って実装することです。ウィキペディアの議論を参照してください。

n!nn(n1)n


1
テストは実際にはシャッフリングアルゴリズムに適用されないため、これは良いバグですが、テストケースのヒューリスティックな欺きの良い例ではありません(ランダム化されているので、どのようにテストしますか?出力を見ることでそれをどのように検出しますか?)
DW

もちろん、統計的にテストします。一様なランダム性は、「出力で何が起こるか」にはほど遠い。サイコロをエミュレートすると言われたプログラムが100の3を連続して与えたとしても、あなたは疑わないでしょうか?
アレキサンダーソンごと14

繰り返しになりますが、私は「手作業でいくつかのテストケースを試してみてください」というヒューリスティックな学生について話しています。私は多くの学生がこれが決定論的アルゴリズムが正しいかどうかをチェックする合理的な方法だと思うのを見てきましたが、シャッフルアルゴリズムが正しいかどうかをテストする良い方法だとは思わないでしょう(シャッフルアルゴリズムはランダム化されているため、特定の出力が正しいかどうかを判断する方法はありません。いずれの場合でも、有用な統計的テストを行うのに十分な例を手動でクランクすることはできません)。したがって、シャッフルアルゴリズムが一般的な誤解を解消するのに大いに役立つとは思わない。
DW

1
@PerAlexandersson:シャッフルを1つしか生成しない場合でも、n> 2080のMTを使用して真にランダムにすることはできません。これで、予想からの偏差は非常に小さくなるため、おそらく気にしません...生成されるのはその期間よりもはるかに少ない(asmeurerが上で指摘しているように)。
チャールズ14

2
この答えは、ニコス・M.のより精巧なものによって時代遅れにされたようです?
ラファエル

2

標準ライブラリに統計関数を導入したPythons PEP450は興味深いかもしれません。著者のSteven D'Apranoは、Pythonの標準ライブラリで分散を計算する関数を持つことの正当化の一部として次のように書いています。

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つでした。


1
n1

2
@Raphael:公平ではありますが、選択されたアルゴリズムは、浮動小数点データの選択として不適切であることがよく知られています。

2
それは、単に数値と精度が失われる方法に関する操作の実装に関するものではありません。最大の精度が必要な場合は、特定の方法で操作を注文する必要があります。それは、大学での数値コースの問題の1つでした。
クリスチャン

Raphaelの正確なコメントに加えて、この例の欠点は、正しさの証拠がこの欠陥の回避に役立つとは思わないことです。浮動小数点演算の微妙さに気付いていない場合は、(式が有効であることを証明することによって)これが正しいことを証明したと思うかもしれません。したがって、アルゴリズムを正しく証明することが重要である理由を生徒に教えるのは理想的な例ではありません。学生がこの例を見ると、代わりに「浮動小数点/数値計算は難しい」というレッスンを引き受けるだろうと思われます。
DW

1

これはおそらくあなたが望んでいることではありませんが、理解するのは確かに簡単であり、他の思考なしでいくつかの小さなケースをテストすると、誤ったアルゴリズムにつながります。

nn2+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

これは、除数(または別の計算)に一定の値を返すことは、問題に対する誤ったアルゴリズムアプローチの結果である可能性があるという意味で関連する可能性があります(たとえば、統計的な問題、またはアルゴリズムのエッジケースを処理しない)。ただし、答えは言い換える必要があります
ニコスM. 14

@NikosM。へえ。私はここで死んだ馬を打ち負かしているように感じますが、質問の2番目の段落では、「アルゴリズムが、考えられるすべてのコーナーケースを含む少数の例で正しく動作する場合、アルゴリズムは「正しいアルゴリズムを証明する必要があるのはなぜか、いくつかのテストケースで試してみればいいのに」と尋ねる学生が常にいます。この場合、最初の40個の値(学生よりもはるかに多く。。1が正しい戻って、)しようとする可能性が高いそれはOPが探していたものだように私には思える
リック・デッカー

ええ、ええ、しかし、これは言い回されているように些細なことです(典型的には正しいかもしれません)が、質問の精神ではありません。言い換える必要があります
ニコスM. 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.