QSを分析するとき、すべての人が常に「ほぼソートされた」最悪のケースを参照します。そのようなシナリオは、自然な入力でいつ発生しますか?
私が思いついた唯一の例は、インデックスの再作成です。
Quicksortはパーティションベースのソートアルゴリズムを混乱させ、さまざまなライブラリの実装を "qsort"しているのではないかと思います。
私は、アルゴリズムをプラグイン可能なピボット選択アルゴリズムを持つものとしてQuicksortに表示することを好みます。これは、その動作を分析するのに非常に重要です。
最初の要素が常にピボットとして選択される場合、既にソートされたリストが最悪のケースです。多くの場合、配列が既に/ほぼソートされている可能性が高いため、この実装はかなり貧弱です。
同様に、最後の要素をピボットとして選択することも同じ理由で悪いです。
一部の実装では、中間要素をピボットとして選択することにより、この問題を回避しようとします。これは、すでに/ほぼ並べ替えられた配列ではそれほど悪いことではありませんが、この予測可能なピボット選択を活用して2次時間で実行する入力を構築できます。
したがって、ランダム化されたピボット選択アルゴリズムを取得しますが、これでもO(N log N)
を保証しません。
そのため、ピボットを選択する前にシーケンスからの情報を使用する他のアルゴリズムが開発されました。もちろん、シーケンス全体をスキャンして中央値を見つけ、それをピボットとして使用できます。これによりO(N log N)
が保証されますが、実際にはもちろん遅くなります。
そのため、いくつかのコーナーがカットされ、人々は中央値3アルゴリズムを考案しました。もちろん、後でこれはいわゆる中央値3の「キラー」によって悪用されました。
そのため、O(N log N)
漸近的な振る舞いを保証するより「インテリジェントな」ピボット選択アルゴリズムを考案する試みがさらに行われています。
したがって、実際には、Quicksortの特定の実装を指定しない限り、最悪のシナリオがいつ発生するかという問題は不明確です。いわゆる中央値と中央値のピボット選択アルゴリズムを使用する場合、二次的な最悪のシナリオはありません。
ただし、ほとんどのライブラリの実装は、平均的な場合のはるかに高速なソートを保証するO(N log N)
を失います。いくつかの本当に古い実装では、最初の要素をピボットとして使用しますが、これは今では貧弱であるとよく理解されており、もはや広く行われているプラクティスではありません。
クイックソートの最悪のケースは、各ステップでのピボット要素の選択に依存すると考えています。ピボットがリスト内の最小要素または最大要素(たとえば、既にソートされたリストの最初または最後の要素)である可能性が高い場合、Quicksortのパフォーマンスは最悪です。
たとえば、リストの中央の要素を選択した場合、既にソートされたリストには最悪のランタイムはありません。
そのため、シナリオがクイックソートの悪いシナリオであると思われる場合は、ピボット要素の選択を変更するだけで、クイックソートのパフォーマンスを向上させることができます。
注:これは、クイックソートの最悪の場合の現実世界の機会の例ではないことを知っています。この例は、使用している実装によって異なります。
実際の質問は、「そのようなシナリオ(ほとんどソートされた)はいつ自然入力で発生するのか?」でした。
すべての答えは「最悪の場合のパフォーマンスを引き起こすもの」を扱っていますが、「最悪の場合のパフォーマンスシナリオを満たすデータを引き起こすもの」をカバーしているものはありません。
プログラマーエラー:基本的にリストを2回ソートすることになります。通常、これはリストがコード内の1箇所でソートされるために発生します。後で別のコードで、リストを並べ替える必要があることがわかっているので、再度並べ替えます。
ほぼ時系列のデータを使用する:一般に時系列で受信されるデータがありますが、場合によっては一部の要素の位置がずれています。 (タイムスタンプ付きの要素をリストに追加するマルチスレッド環境を検討してください。競合状態により、タイムスタンプされた順序とは異なる順序で要素が追加される可能性があります。) -ソート。データの順序は保証されていないためです。
リストへのアイテムの追加:ソートされたリストがあり、いくつかのアイテムを追加する場合(つまり、バイナリ挿入を使用しない場合)。ほとんどソートされたリストを再ソートする必要があります。
外部ソースからのデータ:外部ソースからデータを受け取った場合、それがソートされているという保証はありません。自分で並べ替えます。ただし、外部ソースがソートされている場合は、データを再ソートします。
自然順序付け:これは、時間データに似ています。基本的に、受け取るデータの自然な順序はソートされます。保険会社が車の登録を追加することを検討してください。車の登録を支援する当局が予測可能な順序でそうする場合、新しい車はおそらくが、より高い登録番号を持つことは保証されません。ソートされていることが保証されていないため、ソートし直す必要があります。
インターリーブされたデータ:重複するキーを持つ複数のソートされたソースからデータを受信すると、次のようなキーを取得できます:1 3 2 5 4 7 6 9 8 11 10 13 12 15 14 17 16 19 18.要素の半分が隣接していない場合でも、リストは「ほぼソート済み」です。確かに、最初の要素をピボットするQuickSortを使用すると、O(n^2)
のパフォーマンスが発揮されます。
したがって、上記のすべてのシナリオを考えると、ほとんどソートされたデータをソートすることは実際には非常に簡単です。そして、これがまさに、最初の要素でピボットするQuickSortが実際に避けられる最も良い理由です。 polygeneは、いくつかの 興味深い 代替ピボットの考慮事項に関する情報を提供しています。
副次的注意事項として、通常最もパフォーマンスの低いソートアルゴリズムの1つは、「ほぼソートされた」データで実際に非常にうまく機能します。上記のインターリーブデータでは、バブルソートに必要なスワップ操作は9回のみです。実際のパフォーマンスは
O(n)
です。
クイックソートの最悪の場合:
最悪の場合は、ピボット要素の選択に依存します。そのため、問題は1)配列が既に同じ順序でソートされている場合にのみ発生します。 2)配列はすでに逆順でソートされています。 3)すべての要素が同じ(ケース1および2の特別なケース)