O(log n)
時間の複雑さよりO(1)
時間の複雑さを好む場合はありますか?またはO(n)
からO(log n)
?
例はありますか?
O時間の複雑さが大きいアルゴリズムを低いアルゴリズムよりも優先する理由は多数あります。
10^5
で実行するアルゴリズムは、1/10^5 * log(n)
(O(1)
vs O(log(n)
)よりも、Big-Oの観点から優れています。しかし、最も合理的なn
の場合、最初のものがより良くなります。たとえば、行列乗算の最適な複雑度はO(n^2.373)
ですが、定数は非常に高いため、(私の知る限り)計算ライブラリは使用しません。O(n*log(n))
アルゴリズムを使用するか、O(n^2)
アルゴリズムを使用するかはほとんど問題になりません。O(log log N)
にアイテムを見つけるための時間の複雑さを与えますが、O(log n)
で同じものを見つけるバイナリツリーもあります。膨大な数のn = 10^20
であっても、違いはごくわずかです。O(n^2)
で実行され、O(n^2)
メモリを必要とするアルゴリズムを想像してください。 nが実際には大きくない場合は、O(n^3)
timeおよびO(1)
spaceよりも望ましい場合があります。問題は、長い間待つことができるということですが、アルゴリズムでそれを使用するのに十分な大きさのRAMを見つけることができることを非常に疑いますO(n^2)
で、クイックソートまたはマージソートよりも劣りますが、 オンラインアルゴリズム として、値のリストを(ユーザー入力として)受け取ったときに効率的にソートできます他のほとんどのアルゴリズムは、完全な値のリストに対してのみ効率的に動作できます。O(log n)アルゴリズムではより低い可能性のある隠された定数が常にあります。そのため、実際のデータを実際に高速に処理できます。
スペースの問題もあります(トースターでの実行など)。
開発者の時間の懸念もあります-O(log n)は、実装と検証が1000倍簡単になる可能性があります。
私は誰もまだメモリに縛られたアプリケーションについて言及していないことに驚いています。
その複雑さのために、浮動小数点演算が少ないアルゴリズムがあるかもしれません(つまりO(1)<O(log n))または複雑さの前の定数が小さいため(つまり2n2 <6n2)。とにかく、より低いFLOPアルゴリズムがよりメモリバウンドである場合、FLOPの多いアルゴリズムを好むかもしれません。
「メモリバウンド」とは、頻繁にキャッシュ外にあるデータに頻繁にアクセスすることを意味します。このデータを取得するには、操作を実行する前に、実際のメモリスペースからキャッシュにメモリをプルする必要があります。多くの場合、この取得ステップは非常に遅く、操作自体よりもはるかに遅くなります。
したがって、アルゴリズムにさらに多くの操作が必要な場合(これらの操作は既にキャッシュにあるデータで実行されるため(フェッチが不要))、より少ない操作でアルゴリズムを実行します(out-ofで実行する必要があります) -キャッシュデータ[したがってフェッチが必要])実際のウォールタイムの観点から)。
データのセキュリティが懸念される状況では、より複雑なアルゴリズムの方が タイミング攻撃 に対する耐性が高い場合、より複雑でないアルゴリズムよりも複雑なアルゴリズムの方が望ましい場合があります。
アリストラはそれを釘付けにしたが、例を挙げられなかったので、私はそうする。
ストアの販売商品の10,000個のUPCコードのリストがあります。 10桁のUPC、価格の整数(ペニー単位の価格)、およびレシートの30文字の説明。
O(log N)アプローチ:ソートされたリストがあります。 ASCIIの場合は44バイト、Unicodeの場合は84バイト。または、UPCをint64として処理すると、42バイトと72バイトになります。 10,000レコード-最も高いケースでは、1メガバイト未満のストレージを見ています。
O(1)アプローチ:UPCを保存せず、代わりに配列へのエントリとして使用します。最も低いケースでは、ストレージのテラバイトのほぼ3分の1を探しています。
どのアプローチを使用するかは、ハードウェアによって異なります。ほとんどの合理的な最新の構成では、log Nアプローチを使用します。何らかの理由でRAMは非常に短いが大容量ストレージが十分にある環境で実行している場合、2番目のアプローチが正しい答えであることがわかります。ディスク上のテラバイトの3分の1は大した問題ではありません。ディスクの1つのプローブでデータを取得することは価値があります。単純なバイナリアプローチは平均で13かかります。 (ただし、キーをクラスター化することにより、これを保証された3回の読み取りにまで減らすことができ、実際には最初の読み取りをキャッシュすることに注意してください。)
赤黒の木を考えてみましょう。 O(log n)
のアクセス、検索、挿入、および削除が可能です。 O(1)
にアクセスできる配列と比較してください。残りの操作はO(n)
です。
したがって、アクセスするよりも頻繁に挿入、削除、または検索するアプリケーションと、これらの2つの構造のみの間の選択がある場合、赤黒ツリーを選択します。この場合、赤黒木のアクセス時間がより厄介なO(log n)
を好むと言うかもしれません。
どうして?アクセスは私たちの最優先事項ではないからです。私たちはトレードオフを行っています。アプリケーションのパフォーマンスは、これ以外の要因により大きく影響されます。他のアルゴリズムを最適化することで大きな利益を得るため、この特定のアルゴリズムのパフォーマンスを低下させることができます。
したがって、あなたの質問への答えはこれだけです:アルゴリズムの成長率が最適化したくない場合、最適化したい場合他の何か。他のすべての答えは、この特殊なケースです。他の操作の実行時間を最適化する場合があります。メモリを最適化することもあります。セキュリティのために最適化することもあります。保守性を最適化することもあります。開発時間を最適化することもあります。アルゴリズムの成長率が実行時間に最大の影響を及ぼさないことがわかっている場合、オーバーライド定数が問題に十分なほど低くても実行時間の最適化が行われます。 (データセットがこの範囲外の場合、最終的に定数を支配するため、アルゴリズムの成長率を最適化します。)すべてにコストがあり、多くの場合、より高い成長率のコストを他の何かを最適化するアルゴリズム。
はい。
実際のケースでは、短い文字列キーと長い文字列キーの両方を使用してテーブル検索を行うテストをいくつか実行しました。
std::map
、std::unordered_map
を使用し、文字列の長さで最大10回サンプリングするハッシュ(キーはguidに似ている傾向があるため、これは適切です)、およびすべての文字をサンプリングするハッシュ(理論的には衝突を減らします) )、==
比較を行う並べ替えられていないベクトル、および(正しく覚えている場合)ハッシュも保存する並べ替えられていないベクトル、最初にハッシュを比較し、次に文字を比較します。
これらのアルゴリズムの範囲は、O(1)
(unordered_map)からO(n)
(線形検索)までです。
適度なサイズのNの場合、O(n)がO(1)を上回ることがよくあります。これは、ノードベースのコンテナではメモリ内をさらにジャンプする必要があるのに対し、リニアベースのコンテナでは必要ではなかったためと思われます。
O(lg n)
は2つの間に存在します。どうしたのか覚えていない。
パフォーマンスの違いはそれほど大きくありませんでした。また、より大きなデータセットでは、ハッシュベースのパフォーマンスがはるかに優れていました。そのため、ハッシュベースの順序付けられていないマップを使用しました。
実際には、合理的なサイズのnの場合、O(lg n)
はO(1)
です。コンピューターのテーブルにエントリが40億しかない場合、O(lg n)
は上記の32
で区切られます。 (lg(2 ^ 32)= 32)(コンピューターサイエンスでは、lgはログベース2の省略形です)。
実際には、lg(n)アルゴリズムはO(1)アルゴリズムよりも遅くなりますが、これは対数成長因子ではなく、lg(n)部分が通常アルゴリズムにある程度の複雑さがあることを意味するためです。 、その複雑さにより、lg(n)項からの「成長」のいずれよりも大きな定数係数が追加されます。
ただし、複雑なO(1)アルゴリズム(ハッシュマッピングなど)には、同様の、またはより大きい定数係数を簡単に設定できます。
アルゴリズムを並行して実行する可能性。
クラスO(log n)
およびO(1)
の例があるかどうかはわかりませんが、いくつかの問題では、アルゴリズムの並列実行が簡単な場合は、より複雑なクラスのアルゴリズムを選択します。
一部のアルゴリズムは並列化できませんが、複雑度が非常に低くなっています。同じ結果を達成し、簡単に並列化できるが、より複雑なクラスを持つ別のアルゴリズムを検討してください。 1台のマシンで実行すると、2番目のアルゴリズムは遅くなりますが、複数のマシンで実行すると、実際の実行時間はどんどん短くなりますが、最初のアルゴリズムは高速化できません。
組み込みシステムにブラックリストを実装しているとします。ブラックリストには0〜1,000,000の数値が含まれる場合があります。次の2つのオプションがあります。
ビットセットへのアクセスにより、一定のアクセスが保証されます。時間の複雑さの観点から、最適です。理論的および実用的な観点の両方から(それはO(1)であり、一定のオーバーヘッドが非常に低い)。
それでも、2番目のソリューションを好むかもしれません。特に、メモリ効率が高いため、ブラックリストに登録された整数の数が非常に少ないと予想される場合。
また、メモリが不足している組み込みシステム向けに開発していなくても、1,000,000から1,000,000,000,000の任意の制限を増やして同じ議論をすることができます。その場合、ビットセットには約125Gのメモリが必要になります。 O(1)の最悪の場合の複雑さを保証しても、上司がそのような強力なサーバーを提供することを納得させないかもしれません。
ここでは、O(1)ビットセットよりもバイナリ検索(O(log n))またはバイナリツリー(O(log n))を強くお勧めします。そしておそらく、O(n)という最悪の場合の複雑さを持つハッシュテーブルは、実際にはそれらすべてを破ります。
ここでの私の答え 確率行列のすべての行にわたるランダムな重み付き選択の高速化 は、複雑さO(m)のアルゴリズムが複雑さO(log(m ))、m
が大きすぎない場合。
人々はすでにあなたの正確な質問に答えているので、ここに来るときに人々が実際に考えているかもしれないわずかに異なる質問に取り組みます。
「O(1)time」アルゴリズムとデータ構造の多くは、実際にはexpected O(1)時間、つまり、それらのaverage実行時間がO(1)であることを意味します。
一般的な例:ハッシュテーブル、「配列リスト」の拡張(別名動的サイズの配列/ベクトル)。
そのようなシナリオでは、平均的にはパフォーマンスが低下する可能性がありますが、absolutelyが対数的に制限されることが保証されているデータ構造またはアルゴリズムを使用することをお勧めします。
したがって、例としては、バランスの取れたバイナリ検索ツリーがあり、その実行時間は平均では悪くなりますが、最悪の場合は改善されます。
さらに一般的な質問は、n
としてのO(f(n))
が無限になりがちな場合でも、O(g(n))
アルゴリズムよりもg(n) << f(n)
アルゴリズムを好む状況があるかどうかです。他の人がすでに述べたように、f(n) = log(n)
とg(n) = 1
の場合、答えは明らかに「はい」です。 f(n)
が多項式であるが、g(n)
が指数関数である場合でも、yesになることがあります。有名で重要な例は、線形計画問題を解くための Simplex Algorithm の例です。 1970年代には、O(2^n)
であることが示されました。したがって、その最悪の場合の動作は実行不可能です。しかし、その平均ケースの動作は、数万の変数と制約を伴う実際的な問題に対しても非常に優れています。 1980年代には、線形計画法の多項式時間アルゴリズム( Karmarkarの内点アルゴリズム )が発見されましたが、30年後もシンプレックスアルゴリズムが選択のアルゴリズムであるようです(特定の非常に大きな問題)。これは、平均的なケースの振る舞いが悪いケースの振る舞いよりもしばしば重要であるという明白な理由のためですが、シンプレックスアルゴリズムが何らかの意味でより有益であるというより微妙な理由のためです(例えば、感度情報が抽出しやすいです)。
2セントを入れるには:
アルゴリズムが特定のハードウェア環境で実行される場合、より良いアルゴリズムの代わりに、より複雑なアルゴリズムが選択される場合があります。 O(1)アルゴリズムが、問題を解決するために非常に大きな固定サイズの配列のすべての要素に非順次にアクセスするとします。次に、そのアレイを機械的なハードドライブまたは磁気テープに配置します。
その場合、O(logn)アルゴリズム(ディスクにシーケンシャルにアクセスすると仮定)がより有利になります。
他の多くの答えが無視しているO(log(n))アルゴリズムの代わりにO(1)アルゴリズムを使用する良い使用例があります:不変性。ハッシュマップにはO(1) putsおよびgetsがあり、ハッシュ値の適切な分布を前提としていますが、可変状態が必要です。不変ツリーマップにはO(log(n)) putsおよびgetsがあり、漸近的に遅くなります。ただし、不変性はパフォーマンスの低下を補うのに十分な価値があり、マップの複数のバージョンを保持する必要がある場合、不変性により、マップ(O(n))をコピーする必要がなくなるため、 改善パフォーマンス。
単純に:係数(セットアップ、ストレージ、およびそのステップの実行時間に関連するコスト)は、より大きなbig-O問題よりも小さなbig-O問題ではるかに大きくなる可能性があるためです。 Big-Oは、アルゴリズムスケーラビリティの尺度にすぎません。
Multiple Worlds Interpretation of Quantum Mechanics に依存するソートアルゴリズムを提案する、Hacker's Dictionaryの次の例を検討してください。
- 量子プロセスを使用して配列をランダムに並べ替え、
- 配列がソートされていない場合、ユニバースを破壊します。
- 残りのすべてのユニバースがソートされます[現在のユニバースを含む]。
(ソース: http://catb.org/~esr/jargon/html/B/bogo-sort.html )
このアルゴリズムの大口はO(n)
であり、これは一般的なアイテムでこれまでの既知のソートアルゴリズムに勝っていることに注意してください。線形ステップの係数も非常に低くなっています(スワップではなく、比較のみであるため、線形に行われます)。実際には、同様のアルゴリズムを使用して、多項式時間で NP と co-NP の両方の問題を解決することができます。解決策はありません)量子プロセスを使用して生成し、多項式時間で検証できます。
ただし、ほとんどの場合、ステップ2を実装する行為はまだ「読者の演習として残されている」ことは言うまでもなく、Multiple Worldsが正しくないというリスクを冒したくないでしょう。
nが制限され、O(1)アルゴリズムの定数乗数がlog(n)の制限よりも高い場合たとえば、ハッシュセットに値を格納するO(1)、ただし、ハッシュ関数の高価な計算が必要になる場合があります。データアイテムを(ある順序に関して)簡単に比較でき、nの境界がlog nが1つのアイテムのハッシュ計算よりも大幅に小さい場合、バランスのとれたバイナリツリーでの保存は、ハッシュセット。
しっかりとした上限が必要なリアルタイムの状況では、たとえばヒープソートの平均的な動作は最悪の場合の動作でもあるため、クイックソートではなくヒープソートです。
すでに適切な答えを追加します。実用的な例は、postgresデータベースのハッシュインデックスとBツリーインデックスです。
ハッシュインデックスはハッシュテーブルインデックスを形成して、ディスク上のデータにアクセスしますが、btreeはその名の通り、Btreeデータ構造を使用します。
Big-O時間では、これらはO(1) vs O(logN)です。
ハッシュインデックスは現在、postgresでは現在推奨されていません。実際の状況では、特にデータベースシステムでは、衝突なしでハッシュを達成するのは非常に難しい(O(N)最悪の場合の複雑さにつながる)それらをクラッシュセーフにするのはさらに困難です(先読みロギングと呼ばれます-postgresのWAL)。
O(logN)はインデックスに十分であり、O(1)の実装は非常に難しく、時間差は実際には重要ではないため、この状況ではこのトレードオフが行われます。
n
が小さく、O(1)
が常に遅い場合。
または
これは、誰かが問題への回答を迅速に取得できないようにするために、アルゴリズムが意図的に遅い問題を設計したいセキュリティアプリケーションの場合によくあります。
ここに私の頭の上のいくつかの例があります。
O(2^n)
時にクラックされるように設計されています。ここでn
はキーのビット長です(これはブルートフォースです)。CSのその他の場所では、最悪の場合クイックソートはO(n^2)
ですが、一般的な場合はO(n*log(n))
です。このため、アルゴリズムの効率を分析する際に気にするのは「Big O」分析だけではない場合があります。