web-dev-qa-db-ja.com

アイテムを挿入した後、またはソートされたリストに追加した後にリストをソートする方が速いですか?

並べ替えられたリスト(並べ替えるクイックソートなど)があり、追加する値が多い場合、並べ替えを一時停止し、最後に追加してから並べ替えるか、バイナリチョップを使用してアイテムを正しく配置することをお勧めしますそれらを追加します。アイテムがランダムである場合、またはすでに多かれ少なかれ順序が異なる場合、違いが生じますか?

61
Steve

リストをゼロから効果的に構築するのに十分なアイテムを追加する場合、後でリストをソートすることでパフォーマンスを向上できるはずです。

アイテムの順序がほぼ揃っている場合は、増分更新と定期的な並べ替えの両方を調整して、それを利用できますが、率直に言って、通常は面倒な価値はありません。 (また、予期しない順序付けでアルゴリズムに多くの時間をかけさせないように注意する必要がありますlonger、q.v. naive quicksort)

増分更新と通常のリストの並べ替えは両方ともO(N log N)ですが、後ですべてを並べ替えるより良い定数係数を取得できます(ここでは、増分更新がOよりも速くリストアイテムにアクセスできるように補助データ構造があると仮定しています) (N)...)。一般的に言えば、インクリメンタル更新では常に完全な順序を維持する必要がありますが、一括ソートでは維持しないため、一度にすべてをソートする場合は、順序をインクリメンタルに維持するよりも設計の自由度が高くなります。

それ以外の場合は、高度に最適化されたバルクソートが多数利用可能であることを覚えておいてください。

32
comingstorm

通常、 heap を使用する方がはるかに優れています。要するに、プッシャーとピッカーの間で順序を維持するコストを分割します。両方の操作は、他のほとんどのソリューションと同様に、O(n log n)ではなくO(log n)です。

20
Javier

まとめて追加する場合は、マージソートを使用できます。追加するアイテムのリストを並べ替えてから、両方のリストからコピーし、アイテムを比較して次にコピーするアイテムを決定します。コピー先の配列のサイズを変更し、最後から逆方向に作業する場合、インプレースでコピーすることもできます。

このソリューションの効率は、O(n + m)+ O(m log m)です。ここで、nは元のリストのサイズで、mは挿入されるアイテムの数です。

編集:この答えはあまり愛されていないので、C++のサンプルコードで具体化すると思いました。ソートされたリストは、配列ではなくリンクされたリストに保持されていると思います。これにより、マージよりも挿入のようにアルゴリズムが変更されますが、原理は同じです。

// Note that itemstoadd is modified as a side effect of this function
template<typename T>
void AddToSortedList(std::list<T> & sortedlist, std::vector<T> & itemstoadd)
{
    std::sort(itemstoadd.begin(), itemstoadd.end());
    std::list<T>::iterator listposition = sortedlist.begin();
    std::vector<T>::iterator nextnewitem = itemstoadd.begin();
    while ((listposition != sortedlist.end()) || (nextnewitem != itemstoadd.end()))
    {
        if ((listposition == sortedlist.end()) || (*nextnewitem < *listposition))
            sortedlist.insert(listposition, *nextnewitem++);
        else
            ++listposition;
    }
}
10
Mark Ransom

原則として、リストをソートするよりもツリーを作成する方が高速です。ツリーの挿入は、O(log(n))挿入ごとに行われ、全体的なO(n log(n))になります。O(n log( n))。

JavaにはTreeMapがあります(ListのTreeSet、TreeList、ArrayList、LinkedListの実装に加えて)。

  • TreeSetは、物事をオブジェクト比較の順序で保持します。キーは、Comparableインターフェイスによって定義されます。

  • LinkedListは、物事を挿入順で保持します。

  • ArrayListはより多くのメモリを使用し、一部の操作ではより高速です。

  • 同様に、TreeMapを使用すると、キーでソートする必要がなくなります。マップは挿入時にキーの順序で作成され、常にソートされた順序で維持されます。

ただし、何らかの理由で、Java TreeSetの実装は、ArrayListとソートを使用するよりもかなり遅くなります。

[なぜそれが劇的に遅くなるのか推測するのは難しいが、そうである。データを1回通過することで、わずかに高速になるはずです。この種のことは、多くの場合、アルゴリズム分析に勝るメモリ管理のコストです。]

5
S.Lott

試してみましょう! :)

私はクイックソートを試しましたが、クイックソートでほとんどソート配列をソートすることは...まあ、本当に良いアイデアではありません。修正したものを試してみました。7つの要素で切り取り、そのために挿入ソートを使用しました。それでも、恐ろしいパフォーマンス。マージソートに切り替えました。並べ替えにはかなりのメモリが必要になる場合がありますが(インプレースではありません)、並べ替えられた配列ではパフォーマンスがはるかに良く、ランダムな配列でもほぼ同じです(最初の並べ替えは両方でほぼ同じ時間でしたが、クイック並べ替えはわずかに速くなりました) )。

これはすでに1つのことを示しています。質問に対する答えは、使用するソートアルゴリズムに大きく依存します。ほとんどソートされたリストでパフォーマンスが低下する場合、正しい位置に挿入すると、最後に追加してから再ソートするよりもはるかに高速になります。リストが巨大な場合、外部メモリを必要とする可能性があるため、マージソートはオプションではありません。ところで、私はカスタムマージソート実装を使用しました。これは、単純な実装に対して外部ストレージの1/2のみを使用します(これには、配列サイズと同じくらいの外部ストレージが必要です)。

マージソートがオプションではなく、クイックソートが確実にオプションではない場合、おそらく最良の選択肢はヒープソートです。

私の結果は次のとおりです。新しい要素を最後に追加してから、配列を再ソートすると、正しい位置に挿入するよりも数倍速くなりました。ただし、最初の配列には10個のmio要素(ソート済み)があり、別のmio(未ソート)を追加していました。したがって、10個のmioの配列に10個の要素を追加した場合、それらを正しく挿入すると、すべてを再ソートするよりもはるかに高速になります。したがって、あなたの質問に対する答えは、初期(ソート済み)配列の大きさと、それに追加する新しい要素の数にも依存します。

4
Mecki

それはほぼ同じです。ソート済みリストへのアイテムの挿入はO(log N)であり、リスト内のすべての要素Nに対してこれを行う(したがってリストを構築する)ことは、クイックソート(またはマージソート)の速度であるO(N log N)になりますこのアプローチに近い)。

代わりに前面に挿入した場合、O(1)になりますが、その後クイックソートを実行すると、O(N log N)のままになります。

最初のアプローチを使用します。これは、少し速くなる可能性があるためです。リストの初期サイズNが、挿入する要素の数Xよりもはるかに大きい場合、挿入方法はO(X log N)です。リストの先頭に挿入した後のソートはO(N log N)です。 N = 0(IE:最初は空のリスト)の場合、ソートされた順序で挿入する速度、またはその後にソートする速度は同じです。

1
bmdhacks

リストがa)既にソートされており、b)本質的に動的である場合、ソートされたリストへの挿入は常に高速になるはずです(正しい場所を見つけて(O(n))と挿入します(O(1)))。

ただし、リストが静的な場合は、リストの残りの部分をシャッフルする必要があります(正しい場所を見つけるにはO(n)、下にスライドするにはO(n))。

いずれにせよ、ソートされたリスト(またはバイナリ検索ツリーのようなもの)への挿入はより高速になるはずです。

O(n)+ O(n)は常にO(N log n)よりも高速でなければなりません。

1
warren

高いレベルでは、並べ替えは単なる反復検索と考えることができるため、これは非常に単純な問題です。順序付けられた配列、リスト、またはツリーに要素を挿入する場合、挿入するポイントを検索する必要があります。次に、できれば低コストでそれを入れます。したがって、ソートアルゴリズムは、たくさんのものを取り、1つずつ適切な位置を検索して挿入するものと考えることができます。したがって、挿入ソート(O(n * n))は反復線形検索(O(n))です。ツリー、ヒープ、マージ、基数、クイックソート(O(n * log(n)))は、反復バイナリ検索(O(log(n)))と考えることができます。基になる検索が順序付きハッシュテーブルのようにO(n)である場合、O(1)ソートを使用することができます。これは、52枚のビンに投げ捨てて52枚のカードをソートします。)

したがって、質問に対する答えは、物を一度に1つずつ挿入するのではなく、それらを保存してからソートすることは、大きなOの意味でそれほど大きな違いはないはずです。もちろん、一定の要因に対処する必要がありますが、それらは重要な場合があります。

もちろん、nが10のように小さい場合、議論全体は馬鹿げています。

0
Mike Dunlavey

ソートされたリストへのアイテムの挿入には、O(n)時間ではなく、O(log n)時間かかります。 O(log n)時間をかけて、置く場所を見つける必要があります。ただし、すべての要素をシフトする必要があります-O(n)時間を要します。したがって、ソートされた状態を維持しながら挿入するのはO(n ^ 2)で、すべてを挿入してからソートするのはO(n log n)です。

ソートの実装にもよりますが、挿入の数がリストのサイズよりもはるかに少ない場合は、O(n log n)よりもさらに良くなります。しかし、そうであれば、どちらの方法でもかまいません。

したがって、すべて挿入を実行し、挿入数が多い場合はソリューションを並べ替えます。そうでない場合はおそらく問題になりません。

0
hazzen

前にそれらを追加してから、基数ソートを使用する必要がありますこれは最適です

http://en.wikipedia.org/wiki/Radix_sort#Efficiency

0
Peter Parker

これが.NETで、アイテムが整数の場合、それらをディクショナリに追加する方が迅速です(または、.Net 3.0以降を使用している場合、重複を失うことを気にしないのであればHashSetを使用してください)。

文字列も同じように機能すると思います。美しさは、この方法でO(1)挿入とソートを取得することです。

0
Michael Brown

(あなたが話しているリストがC#List<T>。ただし、追加する値の数が多くなると、さらに多くの値が必要になります。

リストではなく、より適切なデータ構造を使用することをお勧めします。たとえば、バイナリツリーのように。最小の挿入時間でソートされたデータ構造。

0
Ihar Bury