どのようなアルゴリズムにセットが必要ですか?


10

私の最初のプログラミングコースでは、何かの重複を削除するなどの必要があるときは常にセットを使用するように言われました。例:ベクトルからすべての重複を削除するには、上記のベクトルを反復処理して各要素をセットに追加すると、一意のオカレンスが残ります。ただし、各elementoを別のベクトルに追加し、要素が既に存在するかどうかを確認することで、これを行うこともできます。使用する言語によってパフォーマンスが異なると思います。しかし、それ以外のセットを使用する理由はありますか?

基本的に、どのような種類のアルゴリズムがセットを必要とし、他の種類のコンテナでは実行すべきではないのですか?


2
「セット」という用語を使用するときの意味を具体的に説明できますか?C ++セット
ロバートハーベイ

はい、実際には、「セット」の定義はほとんどの言語で非常に似ているように見えます。一意の要素のみを受け入れるコンテナです。
Floella 2017年

6
「各要素を別のベクトルに追加し、要素がすでに存在するかどうかを確認する」-これは、自分でセットを実装するだけです。それで、あなたは自分で手で書くことができるのに、なぜ組み込み機能を使うのかと尋ねていますか?
JacquesB

回答:


8

あなたは特にセットについて質問していますが、あなたの質問はより大きな概念、つまり抽象化に関するものだと思います。Vectorを使用してこれを実行できることは間違いありません(Javaを使用している場合は、代わりにArrayListを使用してください)。ベクターは何のために必要ですか?これはすべて配列で行うことができます。

配列に項目を追加する必要がある場合は、すべての要素をループして、そこにない場合は最後に追加します。しかし、実際には、最初に配列にスペースがあるかどうかを確認する必要があります。存在しない場合は、より大きな新しい配列を作成し、既存のすべての要素を古い配列から新しい配列にコピーする必要があります。その後、新しい要素を追加できます。もちろん、新しい配列を指すように古い配列へのすべての参照を更新する必要もあります。すべて完了しましたか?すごい!では、もう一度何をしようとしていたのでしょうか。

または、代わりにSetインスタンスを使用してを呼び出すこともできますadd()。セットが存在する理由は、セットが多くの一般的な問題に役立つ抽象であるからです。たとえば、アイテムを追跡し、新しいアイテムが追加されたときに反応したいとします。あなたは、呼び出しadd()セットにし、それを返しtrueたりfalseセットが変更されたかどうかに基づいて。すべてプリミティブを使用して手動で作成できますが、なぜですか?

実際にリストがあり、重複を削除したい場合があります。あなたが提案するアルゴリズムは、基本的にはあなたがそれを行うことができる最も遅い方法です。一般的な手っ取り早い方法は2つあります。それらをバケット化またはソートする方法です。または、これらのアルゴリズムの1つを実装するセットに追加することもできます。

あなたのキャリア/教育の早い段階での焦点は、これらのアルゴリズムの構築とそれらの理解にあり、それを行うことが重要です。しかし、それはプロの開発者が通常行うことではありません。これらのアプローチを使用して、はるかに興味深いものを構築し、事前に構築された信頼性の高い実装を使用することで、時間を大幅に節約できます。


23

使用する言語によってパフォーマンスが異なると思います。しかし、それ以外のセットを使用する理由はありますか?

ああ、そうです(ただし、パフォーマンスではありません)。

セットを使用できない場合は追加のコードを記述する必要があるため、セットを使用できる場合に使用してください。セットを使用すると、何をしているのかが読みやすくなります。一意性ロジックのテストはすべて、それについて考える必要がない他の場所に隠されています。それはすでにテストされた場所にあり、それが機能することを信頼できます。

それをするためにあなた自身のコードを書いてください、そしてあなたはそれについて心配しなければなりません。ブレー。誰がそれをしたいですか?

基本的に、どのような種類のアルゴリズムがセットを必要とし、他の種類のコンテナでは実行すべきではないのですか?

「他の種類のコンテナでは実行すべきではない」アルゴリズムはありません。セットを利用できる単純なアルゴリズムがあります。追加のコードを記述する必要がない場合に便利です。

現在、この点に関してセットについて特別なことは何もありません。常にニーズに最も適したコレクションを使用する必要があります。Javaでは、この写真がその決定に役立つことがわかりました。3種類のセットがあることに気づくでしょう。

ここに画像の説明を入力してください

@germiが正しく指摘しているように、ジョブに適切なコレクションを使用すると、コードが他の人にとって読みやすくなります。


6
既に説明しましたが、セットを使用すると、他の人がコードについて推論しやすくなります。一意のアイテムのみが含まれていることを知るために、データがどのように入力されているかを確認する必要はありません。
germi

14

ただし、各elementoを別のベクトルに追加し、要素が既に存在するかどうかを確認することで、これを行うこともできます。

これを行うと、ベクターデータ構造の上にセットのセマンティクスが実装されます。追加のコード(エラーが含まれる可能性があります)を作成しているため、エントリが多い場合、結果は非常に遅くなります。

既存のテスト済みの効率的なセット実装を使用して、なぜそれを実行したいのですか?


6

多くの場合、実際のエンティティを表すソフトウェアエンティティは論理的にセットされています。たとえば、車を考えてみましょう。車には一意の識別子があり、車のグループがセットを形成します。セットの概念は、プログラムが知ることができる車のコレクションに対する制約として機能し、データ値の制約は非常に価値があります。

また、集合には非常に明確に定義された代数があります。Georgeが所有するCarのセットとAliceが所有するCarのセットがある場合、GeorgeとAliceの両方が同じ車を所有していても、結合は明らかにGeorgeとAliceの両方が所有するセットです。したがって、セットを使用する必要があるアルゴリズムは、関係するエンティティのロジックがセットの特性を示すアルゴリズムです。それはかなり一般的であることが判明しました。

セットの実装方法と一意性制約の保証方法は別の問題です。セットがロジックの基本であることを考えると、重複を排除するセットロジックの適切な実装を見つけられることが望まれますが、独自に実装を行ったとしても、セットへのアイテムの挿入には固有性の保証が不可欠です。 「要素がすでに存在するかどうかを確認する」必要はありません。


「すでに存在するかどうかを確認する」ことは、多くの場合、重複排除に不可欠です。多くの場合、オブジェクトはデータから作成されます。また、同一のデータに対して1つのオブジェクトのみを使用し、同じデータからオブジェクトを作成するユーザーが再利用できるようにする必要があります。したがって、新しいオブジェクトを作成し、それがセット内にあるかどうかを確認します。そこにある場合は、セットからオブジェクトを取得します。それ以外の場合は、オブジェクトを挿入します。オブジェクトを挿入したばかりの場合でも、同じオブジェクトがたくさんあります。
gnasher729

1
@ gnasher729 セットの実装者の責任には存在のチェックが含まれますが、セットのユーザーは、セットにfor 1..100: set.insert(10)10が1つしかないことを知ることができます
Caleth

ユーザーは、等しいオブジェクトの10個のグループに100個の異なるオブジェクトを作成できます。挿入後、セットには10​​個のオブジェクトがありますが、100個のオブジェクトがまだ浮かんでいます。重複排除とは、セットに10個のオブジェクトがあり、全員がそれらの10個のオブジェクトを使用することを意味します。もちろん、テストだけでなく、オブジェクトを指定して、セット内の一致するオブジェクトを返す関数が必要です。
gnasher729

4

パフォーマンス特性(非常に重要であり、簡単に却下することはできません)を除いて、セットは抽象コレクションとして非常に重要です。

配列でSet動作(パフォーマンスを無視)をエミュレートできますか?そのとおり!挿入するたびに、要素が既に配列内にあるかどうかを確認し、まだ見つからない場合にのみ要素を追加できます。しかし、それは意識的に意識しなければならないことであり、Array-Psuedo-Setに挿入するたびに覚えておいてください。あれは何ですか、最初に重複をチェックせずに直接挿入しましたか?ウェルプ、あなたの配列はその不変式を壊しています(すべての要素が一意であり、同等に、重複が存在しないこと)。

それを回避するにはどうしますか?新しいデータ型を作成し、それを(たとえばPsuedoSet)呼び出します。これは、内部配列をラップし、insert操作を公開して、要素の一意性を強制します。ラップされた配列はこのパブリックinsertAPI を介してのみアクセスできるため、重複が発生しないことが保証されます。containsチェックのパフォーマンスを向上させるためにハッシュを追加します。遅かれ早かれ、フルアウトを実装したことがわかりますSet

私も声明で応答し、質問をフォローアップします:

私の最初のプログラミングコースでは、何かの複数の順序付けられた要素を格納するなどの必要があるときはいつでも配列を使用するように言われました。例:同僚の名前のコレクションを保存する。ただし、生のメモリを割り当て、開始ポインタ+オフセットで指定されたメモリアドレスの値を設定することによっても、これを行うことができます。

生のポインタと固定オフセットを使用して配列を模倣できますか?そのとおり!挿入するたびに、オフセットが、作業中の割り当てられたメモリの最後から離れないかどうかを確認できます。しかし、それは意識的に意識しなければならないことであり、疑似配列に挿入するたびに覚えておく必要があります。あれは何ですか、最初にオフセットを確認せずに直接直接挿入しましたか?ウェルプ、あなたの名前にセグメンテーション違反があります!

それを回避するにはどうしますか?新しいデータ型を作成してそれを呼び出し(たとえば、PsuedoArray)、ポインターとサイズをラップし、insert操作を公開して、オフセットがサイズを超えないようにします。ラップされたデータにはこのパブリックinsertAPI を介してのみアクセスできるため、バッファオーバーフローが発生しないことが保証されます。ここで、他のいくつかの便利な関数(配列のサイズ変更、要素の削除など)を追加します。遅かれ早かれ、フルアウトを実装したことがわかりますArray


3

すべての種類のセットベースのアルゴリズムがあります。特に、セットの共通部分とユニオンを実行し、結果をセットにする必要がある場合です。

セットベースのアルゴリズムは、さまざまな経路探索アルゴリズムなどで頻繁に使用されます。

セット理論の入門書については、次のリンクを確認してください。http//people.umass.edu/partee/NZ_2006/Set%20Theory%20Basics.pdf

セットのセマンティクスが必要な場合は、セットを使用します。ある段階でベクター/リストをプルーニングするのを忘れたため、偽の重複によるバグを回避し、常にベクター/リストをプルーニングするよりも速くなります。


1

私は実際には標準セットコンテナーはほとんど役に立たないことがわかり、配列だけを使用することを好みますが、それは別の方法で行います。

セットの交差を計算するために、最初の配列を反復処理し、要素を1ビットでマークします。次に、2番目の配列を反復処理して、マークされた要素を探します。ちなみに、ハッシュテーブルよりもはるかに少ない作業とメモリで線形時間で交差を設定します。たとえば、ユニオンと差分は、この方法を使用して適用するのも同じくらい簡単です。それは私のコードベースがそれらを複製するのではなくインデックス付け要素を中心に展開するのに役立ち(要素自体のデータではなく要素にインデックスを複製します)、ソートする必要があることはほとんどありませんが、私は何年もセットデータ構造を使用していませんでした。結果。

また、要素がそのような目的でデータフィールドを提供していない場合でも、私が使用するいくつかの邪悪なビット操作Cコードがあります。トラバースされた要素をマークする目的で最上位ビット(私が使用することはありません)を設定することにより、要素自体のメモリを使用します。それはかなり大まかなことですが、アセンブリに近いレベルで実際に作業している場合を除き、そうしないでください。ただし、要素がトラバーサルに固有のフィールドを提供しない場合でも、それがどのように適用できるかを説明したかっただけです。特定のビットは使用されません。私のdinky i7で2億個の要素(データの約2.4ギグ)間のセット交差を1秒未満で計算できます。std::setそれぞれ同時に1億個の要素を含む2つのインスタンス間の集合交差を実行してみてください。近づくことすらありません。

それはさておき...

ただし、各elementoを別のベクトルに追加し、要素が既に存在するかどうかを確認することで、これを行うこともできます。

要素が新しいベクトルに既に存在するかどうかを確認するチェックは、通常、線形時間演算になります。これにより、セットの交点自体が2次演算になります(爆発的な量の作業が入力サイズが大きくなる)。単純な古いベクトルまたは配列を使用し、見事にスケーリングする方法で実行する場合は、上記の手法をお勧めします。

基本的に、どのような種類のアルゴリズムがセットを必要とし、他の種類のコンテナでは実行すべきではないのですか?

コンテナーレベルでそれについて話しているのか(セット操作を効率的に提供するために具体的に実装されているデータ構造の場合など)、偏った意見を聞いた場合はありませんが、概念レベルでセットロジックを必要とするものはたくさんあります。たとえば、飛行と水泳の両方が可能なゲームの世界にいる生き物を見つけたいとし、あるセット(実際にセットコンテナを使用しているかどうかに関係なく)と別のセットで泳ぐことができる空飛ぶクリーチャーがあるとします。 。その場合は、交差点を設定します。飛ぶことができる、または魔法のようなクリーチャーが必要な場合は、集合和集合を使用します。もちろん、実際にこれを実装するためにセットコンテナーは必要ありません。最も最適な実装では、通常、セットとして設計されたコンテナーを必要としないか、必要としません。

接線から外れます

よし、このセット交差点アプローチに関して、JimmyJamesからいい質問を受けた。それはちょっと話題から外れていますが、まあ、私はより多くの人々がこの基本的な侵入型アプローチを使用して交差を設定し、バランスの取れたバイナリツリーやハッシュテーブルなどの補助構造全体を設定操作のためだけに構築していないことを知りたいと思っています。前述のように、基本的な要件は、リストが浅いコピー要素であるため、最初の並べ替えられていないリストまたは配列または2番目にピックアップするものを通過するときに「マーク」できる共有要素をインデックスまたはポイントすることです。 2番目のリストを通過します。

ただし、これは、次の条件が満たされていれば、マルチスレッドのコンテキストでも要素に触れずに実際に実行できます。

  1. 2つの集約には、要素へのインデックスが含まれています。
  2. インデックスの範囲は大きすぎず(たとえば[0、2 ^ 26]、数十億以上ではない)、かなり密に占有されています。

これにより、集合演算のために並列配列(要素ごとに1ビットのみ)を使用できます。図:

ここに画像の説明を入力してください

スレッド同期は、プールから並列ビット配列を取得し、それをプールに解放するときにのみ必要です(スコープ外に出ると暗黙的に行われます)。設定操作を実行するための実際の2つのループは、スレッドの同期を必要としません。スレッドがビットをローカルに割り当てて解放できる場合は、並列ビットプールを使用する必要さえありませんが、ビットプールは、中央の要素がよく参照されるこの種のデータ表現に適合するコードベースでパターンを一般化するのに便利です。各スレッドが効率的なメモリ管理に煩わされる必要がないように、インデックスによって。私の領域の主な例は、エンティティコンポーネントシステムとインデックス付きメッシュ表現です。どちらも頻繁にセットの交差が必要であり、中央に保存されているすべてのもの(ECSのコンポーネントとエンティティ、頂点、エッジ、

インデックスが密に占有されず、まばらに散在している場合でも、512ビットのチャンク(512の隣接するインデックスを表す展開されていないノードごとに64バイト)にのみメモリを格納するような、並列ビット/ブール配列の合理的なまばらな実装でこれは引き続き適用できます。 )、完全に空いている連続したブロックの割り当てをスキップします。中央のデータ構造が要素自体によってまばらに占められている場合、おそらくこのようなものをすでに使用しているでしょう。

ここに画像の説明を入力してください

...並列ビット配列として機能するスパースビットセットの同様のアイデア。これらの構造は、新しい不変コピーを作成するために深くコピーする必要のないチャンクブロックを浅くコピーするのが簡単であるため、不変性にも役立ちます。

繰り返しますが、非常に平均的なマシンでこのアプローチを使用すると、数億の要素間の交差を1秒未満で設定でき、それは単一のスレッド内にあります。

また、クライアントが両方のリストにある要素にロジックを適用するだけでよい場合など、クライアントが結果の交差の要素のリストを必要としない場合は、半分未満の時間で実行できます。関数ポインタ、ファンクタ、デリゲート、または交差する要素の範囲を処理するためにコールバックされるもの。この効果に何か:

// 'func' receives a range of indices to
// process.
set_intersection(func):
{
    parallel_bits = bit_pool.acquire()

    // Mark the indices found in the first list.
    for each index in list1:
        parallel_bits[index] = 1

    // Look for the first element in the second list 
    // that intersects.
    first = -1
    for each index in list2:
    {
         if parallel_bits[index] == 1:
         {
              first = index
              break
         }
    }

    // Look for elements that don't intersect in the second
    // list to call func for each range of elements that do
    // intersect.
    for each index in list2 starting from first:
    {
        if parallel_bits[index] != 1:
        {
             func(first, index)
             first = index
        }
    }
    If first != list2.num-1:
        func(first, list2.num)
}

...またはこの効果に何か。最初の図の疑似コードの最も高価な部分はintersection.append(index)2番目のループにありstd::vector、それは事前に小さいリストのサイズに予約されている場合にも当てはまります。

すべてをディープコピーするとどうなりますか?

やめて、やめて!交差を設定する必要がある場合は、交差するデータを複製していることを意味します。ごく小さなオブジェクトでさえ、32ビットのインデックスより小さくない可能性があります。実際にインスタンス化されるエレメントが43億個を超える必要がない限り、エレメントのアドレス範囲を2 ^ 32(2 ^ 32バイトではなく2 ^ 32エレメント)に減らすことは非常に可能です。そしてそれは間違いなくメモリ内のセットコンテナを使用していません)。

キーマッチ

要素が同一ではないが一致するキーを持つ可能性がある場合に、セット演算を実行する必要がある場合はどうでしょうか?その場合、上記と同じ考えです。一意の各キーをインデックスにマップする必要があるだけです。たとえば、キーが文字列の場合、インターンされた文字列はそれを行うことができます。これらの場合、トライやハッシュテーブルなどの素晴らしいデータ構造が文字列キーを32ビットインデックスにマップするために呼び出されますが、結果の32ビットインデックスで集合演算を行うためにそのような構造は必要ありません。

非常に安価で簡単なアルゴリズムソリューションとデータ構造の多くは、マシンの完全なアドレス指定範囲ではなく、非常に合理的な範囲の要素のインデックスを操作できる場合に、このように開きます。一意のキーごとに一意のインデックスを取得できます。

インデックスが大好きです!

ピザやビールと同じくらい私はインデックスが大好きです。私が20代のとき、私は本当にC ++に入り、すべての種類の完全に標準に準拠したデータ構造(コンパイル時に範囲アクタから範囲アクタを明確にするために必要なトリックを含む)の設計を始めました。振り返ってみると、それは大きな時間の無駄でした。

要素を断片化して、場合によってはマシンのアドレス可能な範囲全体に格納するのではなく、配列に要素を集中的に格納し、それらにインデックスを付けることを中心にデータベースを展開すると、アルゴリズムとデータ構造の可能性の世界を探ることができます。プレーンオールドintまたはを中心に展開するコンテナとアルゴリズムの設計int32_t。また、あるデータ構造から別のデータ構造に要素を常に転送していなかった場合でも、最終的な結果が非常に効率的で維持しやすいことがわかりました。

の一意の値にT一意のインデックスがあり、中央の配列にインスタンスが存在すると想定できるユースケースの例:

マルチスレッドの基数ソートは、インデックスの符号なし整数でうまく機能します。私は実際には、マルチスレッドの基数ソートを行っています。これは、1億の要素をIntel独自の並列ソートとしてソートするのに約1/10の時間を要しますstd::sort。もちろん、Intelは比較ベースのソートであり、辞書順でソートできるため、はるかに柔軟性が高く、リンゴとオレンジを比較します。しかし、ここでは、キャッシュに適したメモリアクセスパターンを実現したり、重複をすばやく除外したりするために、基数ソートパスを実行する場合があるように、多くの場合、オレンジだけが必要です。

ノードごとのヒープ割り当てなしで、リンクリスト、ツリー、グラフ、個別のチェーンハッシュテーブルなどのリンク構造を構築する機能。要素に並行してノードを一括で割り当て、それらをインデックスでリンクすることができます。ノード自体は次のノードへの32ビットインデックスになり、次のように大きな配列に格納されます。

ここに画像の説明を入力してください

並列処理に適しています。多くの場合、リンクされた構造は並列処理に適していません。これは、たとえば、配列を介して並列forループを実行するだけではなく、ツリーまたはリンクされたリストのトラバーサルで並列処理を実現しようとすることは少なくとも厄介です。インデックス/中央配列表現を使用すると、常にその中央配列に移動して、すべてを分厚い並列ループで処理できます。一部のみを処理したい場合でも、常にこの方法で処理できるすべての要素の中央配列があります(この時点で、中央配列を介してキャッシュに適したアクセスのために、基数でソートされたリストによってインデックスが付けられた要素を処理できます)。

一定の時間でオンザフライで各要素にデータを関連付けることができます。上記のビットの並列配列の場合と同様に、たとえば一時的な処理のために、並列データを要素に簡単かつ非常に安価に関連付けることができます。これには、一時データ以外のユースケースがあります。たとえば、メッシュシステムでは、ユーザーが好きなだけ多くのUVマップをメッシュにアタッチできるようにする場合があります。このような場合、AoSアプローチを使用して、すべての単一の頂点と面に存在するUVマップの数をハードコードすることはできません。そのようなデータをその場で関連付けることができる必要があり、並列配列はそこで便利であり、あらゆる種類の洗練された連想コンテナ、さらにはハッシュテーブルよりもはるかに安価です。

もちろん、並列配列は、並列配列を互いに同期させておくというエラーが発生しやすい性質があるため、不快に感じられます。たとえば、「ルート」配列からインデックス7の要素を削除するときは常に、「子」に対しても同じことを行う必要があります。ただし、ほとんどの言語では、この概念を汎用コンテナに一般化するのは簡単なので、並列配列を互いに同期させるトリッキーなロジックは、コードベース全体で1か所に存在するだけでよく、そのような並列配列コンテナは上記のスパース配列の実装を使用して、配列内の連続する空のスペースが後続の挿入時に回収されるために大量のメモリを浪費しないようにします。

詳細:スパースビットセットツリー

さて、私は皮肉なことだと思うもう少し詳しく説明するように依頼されましたが、とにかくそうするつもりなので、とても楽しいです!このアイデアをまったく新しいレベルに引き上げたい場合は、N + M要素を直線的にループすることなく、セットの交差を実行できます。これは、私が古くから使用してきた基本的なデータ構造であり、基本的にはモデルset<int>です。

ここに画像の説明を入力してください

両方のリストの各要素を検査することなく、セットの交差を実行できる理由は、階層のルートにある単一のセットビットが、たとえば、セット内で100万個の連続する要素が占有されていることを示すことができるためです。1ビットを検査するだけで、範囲内のN個のインデックスが[first,first+N)セットに含まれていることがわかります。Nは非常に大きな数になる可能性があります。

セット内で800万のインデックスが占有されているとしましょう。占有されたインデックスをトラバースするとき、私は実際にこれをループオプティマイザとして使用します。その場合、通常、メモリ内の800万の整数にアクセスする必要があります。これを使用すると、潜在的に数ビットを検査して、ループする占有インデックスのインデックス範囲を作成できます。さらに、それが提供するインデックスの範囲はソートされた順序になっているため、たとえば、元の要素データへのアクセスに使用されるインデックスのソートされていない配列を反復するのではなく、非常にキャッシュフレンドリーなシーケンシャルアクセスが可能になります。もちろん、この手法は極端にまばらなケースではさらに悪くなり、最悪のシナリオでは、すべてのインデックスが偶数である(またはすべてのインデックスが奇数である)場合があり、その場合、隣接する領域はまったくありません。しかし、少なくとも私のユースケースでは、


2
「セットの交差を計算するために、最初の配列を反復処理し、要素を1ビットでマークします。次に、2番目の配列を反復処理して、マークされた要素を探します。」2番目の配列のどこにマークしますか?
JimmyJames 2017

1
ああ、なるほど、あなたは各値を表す単一のオブジェクトをデータに「インターン」しています これは、セットのユースケースのサブセットにとって興味深い手法です。このアプローチを独自のセットクラスの操作として実装しない理由はありません。
JimmyJames 2017

2
「それはいくつかのケースでカプセル化に違反する侵入型ソリューションです...」いったんあなたが何を意味しているのかを理解したら、それは私には起こりましたが、それは必要ではないと思います。この動作を管理するクラスがある場合、インデックスオブジェクトはすべての要素データから独立していて、コレクション型のすべてのインスタンスで共有できます。つまり、1つのマスターデータセットがあり、各インスタンスはマスターセットをポイントします。マルチスレッドはもっと複雑にする必要がありますが、私は扱いやすいと思います。
JimmyJames 2017

1
これはデータベースソリューションで潜在的に役立つようですが、この方法で実装されているものがあるかどうかはわかりません。これをここに出してくれてありがとう。あなたは私の心を働かせました。
JimmyJames 2017

1
もう少し詳しく説明してもらえますか?;)時間があれば、チェックします。
JimmyJames 2017

-1

n個の要素を含むセットに別の要素が含まれているかどうかを確認するには、通常、Xに一定の時間がかかります。n個の要素を含む配列に別の要素が含まれているかどうかを確認するには、Xは通常O(n)時間かかります。それは悪いことですが、n個のアイテムから重複を削除したい場合、突然O(n ^ 2)ではなくO(n)が必要になります。100,000個のアイテムがあなたのコンピューターをひざまずきます。

そして、あなたはより多くの理由を求めていますか?「撮影は別として、リンカーンさん、夕方は楽しかったですか?」


2
あなたはそれをもう一度読みたいと思うかもしれません。O(n² )時間の代わりに O(n)時間をとることは、一般的に良いことと考えられています。
JimmyJames 2017年

これを読んでいる間、あなたは頭に立っていたのでしょうか?OPは、「配列を取るだけではないのはなぜですか」と尋ねました。
gnasher729

2
なぜO(n²)からO(n)に移行すると、「コンピュータがひざまずく」のですか?私のクラスではそれを見逃していたに違いありません。
JimmyJames
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.