クイックソートがマージソートより優れているのはなぜですか?


354

面接でこの質問をされました。どちらもO(nlogn)ですが、ほとんどの人はMergesortではなくQuicksortを使用しています。何故ですか?


91
これは非常に良いインタビューの質問ではありません。実際のデータはシャッフルされていません。多くの場合、スマートソートで使用できる多くの順序が含まれています。どちらのアルゴリズムでもこれが自動的に行われるわけではありませんが、クイックソートよりもマージソートをハックする方が簡単です。GNU libc qsort、Python list.sort、およびArray.prototype.sortFirefoxのJavaScriptは、すべてマージされたマージです。(GNU STL sortは代わりにIntrosortを使用しますが、C ++ではスワップがコピーよりも優先される可能性があるためと考えられます。)
Jason Orendorff

3
@Jason Orendorff:なぜ"easier to hack a mergesort to do it than a quicksort"ですか?引用できる具体的な例はありますか?
Lazer、

16
@eSKayマージソートは、初期データをソートされたサブ配列にグループ化することから始まります。配列に最初にソート済みの領域が含まれている場合は、開始する前にそれらが存在することを検出するだけで、多くの時間を節約できます。そして、O(n)時間でそれを行うことができます。具体的な例については、私が言及した3つのプロジェクトのソースコードを参照してください。最良の例は、PythonのTimsortである可能性があります。詳細はsvn.python.org/view/python/trunk/Objects/…で、svn.python.org / view / python / trunk / Objects / に実装されています
Jason Orendorff 2010

4
@JasonOrendorff:既にソートされたセクションを利用するために、mergesortをより簡単に変更できるというあなたの主張は確かにわかりません。クイックソートのパーティション分割手順を簡単に変更して、後で両方の結果のパーティションがソートされているかどうかを確認し、ソートされている場合は再帰を停止することができます。これにより、比較の数が2倍になる可能性がありますが、そのステップのO(n)時間の複雑さは変わりません。
j_random_hacker 2012

3
@j_random_hacker:そうです、私が示唆していたことです。ただし、次の点を考慮してください:{10、2、3、4、5、6、7、8、1、9}既にほぼ完全に並べ替えられていますが、パーティションがそれを見つける前に、または後で確認しないでください。そして、次の呼び出しがそれをチェックする前に、パーティションはそれを台無しにします。一方、マージソートは、移動する前に分割ステップでソートされたシーケンスをチェックします。スマートシーケンスは、特に分割ステップ中にこのような実行を探します(参照:Tim Sort)
Mooing Duck

回答:


275

クイックソートには、O(n 2)ワーストケースランタイムとO(n log n)平均ケースランタイムがあります。ただし、多くの要因がアルゴリズムのランタイムに影響を与えるため、多くのシナリオでマージソートの方が優れています。これらすべてをまとめると、クイックソートが優先されます。

特に、よく引用されるソートアルゴリズムのランタイムとは、データのソートに必要な比較の数またはスワップの数を指します。これは、特に基礎となるハードウェア設計とは独立しているため、確かにパフォーマンスの優れた指標です。ただし、参照の局所性(つまり、おそらくキャッシュにある多くの要素を読み取るか)などの他の事柄も、現在のハードウェアで重要な役割を果たします。特にクイックソートは、追加のスペースをほとんど必要とせず、キャッシュの局所性が優れているため、多くの場合、マージソートよりも高速になります。

さらに、ピボットの適切な選択を使用することで、クイックソートのO(n 2)の最悪の場合の実行時間をほぼ完全に回避できます(ランダムに選択するなど)(これは優れた戦略です)。

実際には、クイックソートの多くの最新の実装(特にlibstdc ++のstd::sort)は実際にはイントロソートであり、その理論的な最悪のケースはマージソートと同じO(n log n)です。これは、再帰の深さを制限し、log nを超えると別のアルゴリズム(ヒープソート)に切り替えることでこれを実現します。


4
ウィキペディアの記事によると、マージソートではなくヒープソートに切り替わります...ただ参考までに。
重大度

3
@Sev:…元の論文と同様に。間違いを指摘してくれてありがとう。–漸近的な実行時間が同じであるため、それが本当に重要であるというわけではありません。
Konrad Rudolph

110
なぜこれが正解として選択されたのですか?それが説明するすべては、クイックソートの問題にパッチを適用する方法です。それでも、なぜクイックソートが他よりも多く使用されるのかはわかりません。「1つの深さの後でヒープソートに切り替えることができるので、クイックソートが他よりも多く使用されます」という答えですか?..そもそもなぜヒープソートを使わないのですか?..ただ理解しようとしています...
codeObserver '

16
@ p1いい質問です。本当の答えは、平均して、平均データの場合、クイックソートはマージソートよりも速い(そして、ヒープソート、さらに言えば)であり、最悪の場合のクイックソートはマージソートよりも遅いにもかかわらず、この最悪のケースは非常に簡単に軽減できます(したがって、私の答えです)。
Konrad Rudolph

4
クイックソートは、メモリの面でも優れています。
Shashwat、2014年

287

多くの人が指摘しているように、クイックソートの平均ケースパフォーマンスはマージソートよりも高速です。 ただし、これは、オンデマンドで任意のメモリにアクセスするための一定の時間を想定している場合にのみ当てはまります。

RAMでは、この仮定は一般的に悪くありません(キャッシュのために常にそうであるとは限りませんが、悪くありません)。あなたのデータ構造がディスク上に生きるために十分に大きいである場合しかし、その後、クイックソートます殺さランダム200は毎秒追求のようなあなたの平均的なディスクが何かをするという事実によって。しかし、同じディスクでも、メガバイト/秒のデータを順番に読み書きするのに問題はありません。これはまさにmergesortが行うことです。

したがって、データをディスク上でソートする必要がある場合は、mergesortのバリエーションを実際に使用したいと考えています。(通常、サブリストをクイックソートしてから、サイズのしきい値を超えてそれらをマージし始めます。)

さらに、そのサイズのデータ​​セットで何かをする必要がある場合は、ディスクへのシークを回避する方法について十分に検討してください。たとえば、データベースで大量のデータをロードする前にインデックスを削除し、後でインデックスを再構築することが標準的なアドバイスであるのはこのためです。ロード中にインデックスを維持することは、常にディスクをシークすることを意味します。対照的に、インデックスを削除する場合、データベースは、最初に処理する情報を(もちろん、mergesortを使用して!)ソートし、次にそれをインデックスのBTREEデータ構造にロードすることにより、インデックスを再構築できます。(BTREEは自然に順番に保持されるため、ディスクへのシークがほとんどないソート済みデータセットからロードできます。)

ディスクシークを回避する方法を理解することで、データ処理ジョブに数日または数週間ではなく数時間かかる場合がいくつかあります。


1
とてもいいですが、データ構造にアクセスするための前提について考えていませんでした。良い洞察力:)
中通2014

2
「ディスクにシークする」とはどういう意味ですか?それは、データがディスクに保存されているときに単一の値を検索することを意味しますか?
James Wierzba、2015年

8
@JamesWierzba私は、彼が「ディスク上の場所を探す」ことを意味するという文脈からとっています。回転ディスクデバイスで「シーク」とは、読み取りヘッドをピックアップして新しい絶対アドレスに移動することを意味します。これは、非常に遅い動作です。格納された順序でデータにアクセスする場合、ディスクハードウェアはシークする必要がなく、高速で耕してアイテムを順番に読み取ります。
nclark 2016年

1
これについてもう少し説明できますか?これが私の見方です:クイックソート:ランダムピボットを使用している場合、コールスタックにはランダムに分割された配列のフラグメントがあります。これにはランダムアクセスが必要です。ただし、スタック内の呼び出しごとに、左と右の両方のポインターが順番に移動します。これらはキャッシュに保持されると想定しています。スワップは、キャッシュにある(最終的にはディスクに書き込まれる)情報に対する操作です。(私の次のコメントで続きます)
sam

1
コストのかかるディスクの読み取り/書き込みオーバーヘッドを回避するだけの貢献:ディスクアクセスを必要とする非常に大きなデータを並べ替える場合、各パスの並べ替えの方向を切り替えると有利です。つまり、ループの最上位レベルで、一度からに0向かっnて、次にからにn向かっていき0ます。これには、メモリ(キャッシュ)ですでに使用可能なデータブロックを後退(ソート)し、1回のディスクアクセスで2回攻撃するという利点があります。ほとんどのDBMSはこの最適化手法を使用していると思います。
ssd 2018年

89

実際、QuickSortはO(n 2)です。その平均ケース実行時間はO(nlog(n))ですが、最悪のケースはO(n 2)です。これは、一意のアイテムをほとんど含まないリストで実行すると発生します。ランダム化はO(n)を取ります。もちろん、これで最悪のケースが変わることはありません。悪意のあるユーザーが並べ替えに時間がかかるのを防ぐだけです。

QuickSortは、次の理由により人気があります。

  1. インプレースです(MergeSortは、並べ替える要素の数に比例した追加のメモリが必要です)。
  2. 小さな隠し定数があります。

4
実際、最悪の場合O(n ^ 2)ではなくO(n * log(n))であるQuickSortの実装があります。
jfs 2008

12
また、コンピュータのアーキテクチャにも依存します。クイックソートはキャッシュからメリットを得ますが、MergeSortはメリットをもたらしません。
クリスティアンCiupitu 2008

4
@JF Sebastian:これらはおそらくクイックソートではなくイントロソートの実装です(イントロソートはクイックソートとして開始され、n * log(n)になろうとするとヒープソートに切り替わります)。
CesarB 2008年

44
マージソートを適切に実装できます。
Marcin、

6
マージソートは、O(1)の追加のストレージのみを必要とする方法で実装できますが、それらの実装のほとんどは、パフォーマンスの点で大きな影響を受けます。
2014

29

「それでも、ほとんどの人はMergesortの代わりにQuicksortを使用しています。なぜですか?」

与えられていない心理的な理由の1つは、Quicksortがより巧妙に命名されていることです。すなわちよいマーケティング。

はい、トリプルパーティショニングを使用したクイックソートは、おそらく最も優れた汎用ソートアルゴリズムの1つですが、「クイック」ソートは「マージ」ソートよりもはるかに強力に聞こえるという事実を克服することはできません。


3
どちらが良いかという質問には答えません。アルゴリズムの名前は、どちらが優れているかを判断する上で重要ではありません。
Nick Gallimore

18

他の人が指摘したように、Quicksortの最悪のケースはO(n ^ 2)ですが、mergesortとheapsortはO(nlogn)のままです。ただし、平均的なケースでは、3つすべてがO(nlogn)です。ですから、それらは大多数の場合に匹敵します。

クイックソートの平均的な改善点は、内部ループが複数の値を1つの値と比較することを意味し、他の2つの値は比較ごとに異なることです。つまり、Quicksortは、他の2つのアルゴリズムの半分の数の読み取りを実行します。最近のCPUでは、パフォーマンスはアクセス時間によって大きく左右されるため、結局のところ、クイックソートは最初の選択肢として最適です。


9

これまでに述べた3つのアルゴリズム(マージソート、クイックソート、ヒープソート)のうち、マージソートのみが安定しているアルゴリズムを追加したいと思います。つまり、同じキーを持つ値の順序は変わりません。これが望ましい場合もあります。

しかし、実のところ、実際の状況では、ほとんどの人は良い平均パフォーマンスしか必要とせず、クイックソートは... quick =)

すべてのソートアルゴリズムには、浮き沈みがあります。概要については、Wikipediaのソートアルゴリズムの記事を参照してください。


7

クイックソートのウィキペディアのエントリから:

クイックソートは、別の再帰的ソートアルゴリズムであるマージソートとも競合しますが、最悪の場合のΘ(nlogn)実行時間の利点があります。Mergesortは、quicksortやheapsortとは異なり、安定したソートであり、リンクリストや、ディスクストレージやネットワーク接続ストレージなどのアクセスの遅いメディアに保存された非常に大きなリストを操作するように簡単に調整できます。リンクされたリストを操作するようにクイックソートを作成できますが、ランダムアクセスなしでは、ピボットの選択が不十分になることがよくあります。マージソートの主な欠点は、配列を操作する場合、最良の場合にはΘ(n)補助スペースが必要ですが、インプレースパーティション分割と末尾再帰を使用するクイックソートのバリアントはΘ(logn)スペースのみを使用することです。(リンクリストを操作する場合、mergesortが必要とするのは少量の一定量の補助記憶域だけであることに注意してください。)


7

ムー! クイックソートの方が優れているわけではなく、マージソートとは異なる種類のアプリケーションに適しています。

Mergesortは、速度が本質的なものであり、最悪の場合のパフォーマンスの低下を許容できず、追加のスペースが利用可能であるかどうかを検討する価値があります。1

あなたは彼らが«彼らは両方ともO(nlogn)[…]»だと述べました。これは間違っています。«最悪の場合、Quicksortは約n ^ 2/2の比較を使用します。»

しかし、私の経験によれば、最も重要な特性は、命令型パラダイムでプログラミング言語を使用するときにソート中に使用できる順次アクセスの簡単な実装です。

1セジウィック、アルゴリズム


Mergesortは追加のスペースを必要としないようにインプレースで実装できます。:二重のリンクリストとたとえばstackoverflow.com/questions/2938495/...
lanoxx

6

クイックソートは、実際には最速のソートアルゴリズムですが、O(n2)と同じくらいパフォーマンスを低下させる可能性のある多くの病理学的ケースがあります。

ヒープソートはO(n * ln(n))での実行が保証されており、有限の追加ストレージのみを必要とします。しかし、実際のテストでは、ヒープソートが平均でクイックソートよりも大幅に遅いことを示す多くの引用があります。


5

ウィキペディアの説明は:

通常、クイックソートは他のΘ(nlogn)アルゴリズムよりも実際に非常に高速です。これは、その内部ループがほとんどのアーキテクチャで効率的に実装でき、ほとんどの実際のデータでは、2次時間を必要とする確率を最小限に抑える設計の選択を行うことができるためです。 。

クイックソート

マージソート

クイックソートの実装にはないMergesort(Ω(n))に必要なストレージの量にも問題があると思います。最悪の場合、それらは同じアルゴリズム時間ですが、mergesortはより多くのストレージを必要とします。


クイックソートの最悪のケースはO(n)、マージソートO(n log n)なので、そこには大きな違いがあります。
paul23

1
最悪の場合のクイックソートはO(n ^ 2)です-以前のコメントを編集できず、タイプミスをしました
paul23

@ paul23コメントは削除できます。また、答えはすでにあなたの要点を扱っていました:「ほとんどの実際のデータでは、二次時間を必要とする確率を最小にする設計選択を行うことが可能です」
Jim Balter

5

既存の優れた回答に加えて、QuickSortが最良のケースから逸脱した場合のパフォーマンスとその可能性についていくつかの数学を追加したいと思います。これが、O(n ^ 2)ケースが本当ではない理由を人々が少しよく理解するのに役立つことを願っていますQuickSortのより洗練された実装への懸念。

ランダムアクセスの問題以外に、QuickSortのパフォーマンスに影響を与える可能性のある2つの主な要因があり、どちらもピボットと並べ替えられているデータとの比較に関連しています。

1)データ内の少数のキー。ピボットの位置を除くすべての値が毎回片側に配置されるため、すべて同じ値のデータセットは、バニラ2パーティションQuickSortでn ^ 2時間でソートされます。最近の実装では、3パーティションソートを使用するなどの方法でこれに対処しています。これらのメソッドは、O(n)時間ですべて同じ値のデータセットに対して実行されます。したがって、このような実装を使用すると、少数のキーを使用した入力で実際にパフォーマンス時間が向上し、もはや問題ではなくなります。

2)ピボットの選択が極端に悪いと、最悪の場合のパフォーマンスが発生する可能性があります。理想的なケースでは、ピボットは常に50%のデータが小さく、50%のデータが大きいため、各反復中に入力が半分に分割されます。これにより、O(n * logn)時間に対するlog-2(n)再帰のn回の比較とスワップ時間が得られます。

非理想的なピボット選択は実行時間にどのくらい影響しますか?

データの75%がピボットの片側にあるようにピボットが一貫して選択されているケースを考えてみましょう。まだO(n * logn)ですが、ログのベースが1 / 0.75または1.33に変更されました。ベースを変更するときのパフォーマンスの関係は、常にlog(2)/ log(newBase)で表される定数です。この場合、その定数は2.4です。したがって、このピボット選択の品質は、理想の2.4倍かかります。

これはどれほど速く悪化しますか?

ピボットの選択が(一貫して)非常に悪くなるまで、それほど速くありません:

  • 片側50%:(理想的なケース)
  • 片側75%:2.4倍の長さ
  • 片側90%:6.6倍の長さ
  • 片側95%:13.5倍の長さ
  • 片側99%:69倍の長さ

片側で100%に近づくと、実行のログ部分はnに近づき、実行全体は漸近的にO(n ^ 2)に近づきます。

QuickSortの素朴な実装では、ソートされた配列(最初の要素ピボットの場合)または逆ソートされた配列(最後の要素ピボットの場合)などのケースは、最悪の場合のO(n ^ 2)実行時間を確実に生成します。さらに、予測可能なピボット選択のある実装は、最悪の場合の実行を生成するように設計されたデータによるDoS攻撃を受ける可能性があります。最新の実装では、ソート前にデータをランダム化する、3つのランダムに選択されたインデックスの中央値を選択するなど、さまざまな方法でこれを回避しています。このランダム化の組み合わせでは、2つのケースがあります。

  • 小さなデータセット。最悪のケースは合理的に可能ですが、n(2)も小さいほどnが小さいため、O(n ^ 2)は致命的ではありません。
  • 大きなデータセット。最悪のケースは理論的には可能ですが、実際には不可能です。

ひどいパフォーマンスを見る可能性はどのくらいありますか?

チャンスはある無視できるほどに小さいです。5,000種類の値について考えてみましょう。

架空の実装では、ランダムに選択された3つのインデックスの中央値を使用してピボットを選択します。25%〜75%の範囲にあるピボットは「良好」と見なし、0%-25%または75%-100%の範囲にあるピボットは「不良」と見なします。3つのランダムインデックスの中央値を使用して確率分布を見ると、再帰ごとに11/16の確率で最終的に適切なピボットが得られます。計算を単純化するために、2つの保守的な(そして誤った)仮定をしてみましょう。

  1. 優れたピボットは常に正確に25%/ 75%の分割であり、2.4 *理想的なケースで動作します。理想的な分割、または25/75を超える分割はありません。

  2. 悪いピボットは常に最悪のケースであり、本質的にソリューションには何も貢献しません。

QuickSortの実装はn = 10で停止し、挿入ソートに切り替わるので、5,000個の値の入力をそこまで壊すには、22個の25%/ 75%ピボットパーティションが必要です。(10 * 1.333333 ^ 22> 5000)または、4990の最悪の場合のピボットが必要です。いずれかの時点で22個の優れたピボットが蓄積されると、並べ替えが完了するため、最悪の場合、またはそれに近いものは、非常に運が悪いことを覚えておいてください。n = 10にソートするために必要な22の優れたピボットを実際に達成するために88回の再帰が必要な場合、それは4 * 2.4 *理想的なケース、または理想的なケースの実行時間の約10倍になります。88回の再帰後、必要な22個の優れたピボットを達成できない可能性はどのくらいありますか?

二項確率分布はこれに答えることができ、答えは約10 ^ -18です。(nは88、kは21、pは0.6875)ユーザーは、[並べ替え]をクリックするのにかかる1秒の間に、5,000アイテムの並べ替えがさらに悪いことを確認するよりも、約1000倍稲妻に当たる可能性が高くなります。10 *理想的なケースより。データセットが大きくなると、このチャンスは小さくなります。以下は、いくつかの配列サイズと、10 *よりも長く実行される対応する可能性です。

  • 640アイテムの配列:10 ^ -13(60回の試行のうち15回の適切なピボットポイントが必要)
  • 5,000アイテムの配列:10 ^ -18(88回の試行のうち22回のピボットが必要)
  • 40,000アイテムの配列:10 ^ -23(116のうち29のピボットが必要)

これには、現実よりも悪い2つの保守的な仮定があることに注意してください。そのため、実際のパフォーマンスはさらに良くなり、残りの確率のバランスは理想よりも理想に近くなります。

最後に、他の人が述べたように、再帰スタックが深くなりすぎた場合は、ヒープのソートに切り替えることで、これらの非常にありそうもないケースでも排除できます。したがって、TLDRは、QuickSortの適切な実装では、設計されて実行がO(n * logn)時間で完了するため、最悪のケースは実際には存在しません


1
「既存の素晴らしい答え」-それらはどれですか?見つかりません。
ジムBalter

クイックソートのバリエーションは、パーティションの比較機能に通知しますか?パーティション内のすべてのアイテムでキーのかなりの部分が同じである状況を悪用できるような方法で?
スーパーキャット

4

なぜクイックソートが良いのですか?

  • QuickSortは、最悪のケースではN ^ 2をとり、平均のケースではNlogNをとります。最悪のケースは、データがソートされるときに発生します。これは、並べ替えを開始する前にランダムにシャッフルすることで軽減できます。
  • QuickSortは、マージソートで使用される余分なメモリを必要としません。
  • データセットが大きく、同じアイテムがある場合、3ウェイパーティションを使用することで、クイックソートの複雑さが軽減されます。同じアイテムの数が多いほど、並べ替えが向上します。すべてのアイテムが同一の場合、線形時間でソートされます。[これはほとんどのライブラリのデフォルトの実装です]

クイックソートは常にマージソートより優れていますか?

あんまり。

  • Mergesortは安定していますが、Quicksortは安定していません。したがって、出力の安定性が必要な場合は、Mergesortを使用します。多くの実用的なアプリケーションでは安定性が必要です。
  • 最近のメモリは安いです。そのため、Mergesortが使用する追加のメモリがアプリケーションにとって重要ではない場合、Mergesortを使用しても害はありません。

注: Javaでは、Arrays.sort()関数は、プリミティブデータ型にはQuicksortを使用し、オブジェクトデータ型にはMergesortを使用します。オブジェクトはメモリのオーバーヘッドを消費するため、Mergesortに追加された少しのオーバーヘッドは、パフォーマンスの観点では問題にならない場合があります。

参照コース3第3週のプリンストンアルゴリズムコースのQuickSortビデオを見る


「これは、並べ替えを開始する前にランダムにシャッフルすることで軽減できます。」代わりに、ランダムなピボットを使用してください。
ジムBalter

4

クイックソートはマージソートよりも優れていません。O(n ^ 2)(めったに起こらない最悪のケース)では、クイックソートはマージソートのO(nlogn)よりもはるかに遅くなる可能性があります。Quicksortはオーバーヘッドが少ないので、nが小さくて遅いコンピューターの場合は、より優れています。しかし、今日のコンピューターは非常に高速であるため、マージソートの追加のオーバーヘッドは無視でき、非常に遅いクイックソートのリスクは、ほとんどの場合、マージソートの取るに足らないオーバーヘッドをはるかに上回ります。

さらに、mergesortは、同じキーを持つアイテムを元の順序のままにします。これは便利な属性です。


2
2番目の文は、「... mergesortは... mergesortよりもはるかに遅い可能性がある」と述べています。最初の参照は、おそらくクイックソートにする必要があります。
ジョナサンレフラー

マージソートは、マージアルゴリズムが安定している場合にのみ安定します。これは保証されていません。
2014

@Clearer <=がではなく比較に使用されていることが保証されており、使用し<ない理由はありません。
ジムBalter

@JimBalter不安定なマージアルゴリズムを簡単に思いつく可能性があります(たとえば、クイックソートがその役割を果たします)。多くの場合、クイックソートがマージソートよりも高速である理由は、オーバーヘッドが減少したためではなく、クイックソートがデータにアクセスする方法のためです。
2018年

@Clearerクイックソートはマージソートではありません...私が回答した14年12月21日のステートメントは、マージソートとそれが安定しているかどうかについて厳密に述べたものです。クイックソートで、どちらが速いかは、あなたのコメントや私の応答にはまったく関係ありません。私のための議論の終わり...何度も。
ジムBalter

3

答えは、プリミティブ値のDualPivotQuickSortによってもたらされた変更に対して、クイックソートのwrtに少し傾くでしょう。Java 7java.util.Arraysをソートするために使用されます

It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.

-あなたはここでJAVA7のimplmentationを見つけることができますhttp://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java

DualPivotQuickSortのさらにすばらしい読み物-http : //permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628


3

マージソートの一般的なアルゴリズムは次のとおりです。

  1. 左側のサブ配列を並べ替える
  2. 正しいサブ配列を並べ替える
  3. 2つのソートされたサブ配列をマージします

トップレベルでは、2つのソートされたサブ配列をマージするには、N個の要素を処理する必要があります。

その1レベル下、ステップ3の各反復ではN / 2要素の処理が必要になりますが、このプロセスを2回繰り返す必要があります。したがって、まだ2 * N / 2 == N要素を処理しています。

その1つ下のレベルでは、4 * N / 4 == N個の要素をマージしています。再帰スタックのすべての深さには、その深さのすべての呼び出しにわたって、同じ数の要素をマージすることが含まれます。

代わりに、クイックソートアルゴリズムを検討してください。

  1. ピボットポイントを選択してください
  2. 配列内の正しい位置にピボットポイントを配置します。小さい要素はすべて左側に、大きい要素は右側に配置します
  3. 左サブ配列を並べ替える
  4. 右サブ配列を並べ替える

トップレベルでは、サイズNの配列を処理しています。次に、ピボットポイントを1つ選択し、正しい位置に配置して、残りのアルゴリズムでは完全に無視できます。

その1つ下のレベルでは、N-1の合計サイズを持つ2つのサブ配列を扱います(つまり、以前のピボットポイントを減算します)。各サブ配列のピボットポイントを選択すると、最大2つのピボットポイントが追加されます。

その1レベル下では、上記と同じ理由で、サイズN-3を組み合わせた4つのサブ配列を処理しています。

それからN-7 ...そしてN-15 ...そしてN-32 ...

再帰スタックの深さはほぼ同じままです(logN)。マージソートでは、再帰スタックの各レベルにわたって、常にN要素のマージを処理しています。ただし、クイックソートを使用すると、スタックを下に行くにつれて、処理する要素の数が減少します。たとえば、再帰スタックの途中で深さを見る場合、処理している要素の数はN-2 ^((logN)/ 2))== N-sqrt(N)です。

免責事項:マージソートでは、毎回配列を2つのまったく同じチャンクに分割するため、再帰的な深さはちょうどlogNです。クイックソートでは、ピボットポイントが正確に配列の中央にある可能性が低いため、再帰スタックの深さはlogNよりわずかに大きくなる可能性があります。この要素と上記の要素がアルゴリズムの複雑さで実際にどの程度の役割を果たすかを確認するための計算は行っていません。


ピボットが次のレベルの並べ替えの一部ではないことが、QSのパフォーマンスが向上する理由ではありません。追加の洞察については、他の回答を参照してください。
ジムBalter

@JimBalterあなたが言及している「その他の答え」はどれですか?一番上の答えは、QSは「追加のスペースはほとんど必要なく、優れたキャッシュの局所性を示す」というだけですが、その理由についての説明も、引用も提供していません。2番目の答えは、大規模なデータセットに対してはマージソートの方が優れているということです
RvPr

QSのパフォーマンスが高い理由から、QSがどのように動作するかについての基本的な事実を説明するまで、ゴールポストを動かしています。他の質問への答えはそれを行います:stackoverflow.com/questions/9444714/… ... それで十分だと思います。これ以上は応答しません。
ジムBalter

3

マージソートとは異なり、クイックソートは補助スペースを使用しません。Merge Sortは補助スペースO(n)を使用します。しかし、マージソートの最悪の場合の時間の複雑さはO(nlogn)ですが、クイックソートの最悪の場合の複雑度は、配列が既にソートされているときに発生するO(n ^ 2)です。


いいえ、配列がすでにソートされている場合、最初または最後の項目をピボットとして使用しない限り、QuickSortの最悪のケースは発生しませんが、誰もそれを行いません。
ジムBalter

2

クイックソートの方が平均的なケースの複雑さは優れていますが、一部のアプリケーションでは間違った選択です。クイックソートは、サービス拒否攻撃に対して脆弱です。攻撃者がソートする入力を選択できる場合、攻撃者はo(n ^ 2)の最悪の場合の時間の複雑さを取るセットを簡単に構築できます。

Mergesortの平均的なケースの複雑さと最悪のケースの複雑さは同じであり、そのため同じ問題に悩まされることはありません。マージソートのこのプロパティは、リアルタイムシステムにとっても優れた選択肢になります。これは、実行速度を大幅に低下させる病理的なケースがないためです。

これらの理由で、私はQuicksortよりもMergesortの方が好きです。


2
クイックソートの平均的なケースの複雑さはどのようになっていますか?どちらもO(nlgn)です。攻撃者がソートアルゴリズムに入力を提供することはないだろうと私は主張します...しかし、あいまいさによってセキュリティを想定しないために、可能であると想定しましょう。n ^ 2の実行時間はnlgnよりも悪いですが、Webサーバーが単一の攻撃に基づいてクラッシュすることは十分に悪くはありません。実際、すべてのWebサーバーがDDOS攻撃に対して脆弱であり、攻撃者がホストの分散ネットワーク、すべてのTCP SYNフラッディングを使用する可能性が高いため、DOS引数はほとんどnullです。
CaTalyst.X 2013

「Quicksortの方が平均ケースの複雑度が高い」-そうではありません。
ジムBalter

2

それは言うのは難しいです.MergeSortの最悪はn(log2n)-n + 1で、nが2 ^ kに等しい場合に正確です(これはすでに証明済みです)、任意のnの場合、(n lg n-n + 1)と(n lg n + n + O(lg n))ですが、quickSortの場合、nlog2n(nも2 ^ kに等しい)が最適です。MergesortをquickSortで除算すると、nが無限大の場合は1になります。So MergeSortのワーストケースがQuickSortのベストケースよりも優れているように見えますが、なぜクイックソートを使用するのですか?ただし、MergeSortが適切に配置されていないため、2nのmemeroyスペースが必要です。アルゴリズムの分析には含めないでください。つまり、MergeSortはtheroyでのクイックソートよりも優れていますが、実際には、メモリ空間を考慮する必要があります。配列コピーのコストは、マージがクイックソートよりも遅くなります。私はランダムクラスによってJavaで1000000桁を与えられた実験、マージソートでは2610ミリ秒、クイックソートでは1370ミリ秒かかりました。


2

クイックソートは最悪の場合O(n ^ 2)ですが、平均的なケースでは一貫してマージソートが実行されます。各アルゴリズムはO(nlogn)ですが、Big Oについて話すときは、複雑度の低い要素は省略していることを覚えておく必要があります。クイックソートは、一定の要素に関してマージソートよりも大幅に改善されています。

マージソートにはO(2n)メモリも必要ですが、クイックソートを実行できます(O(n)のみが必要です)。これは、一般にマージソートよりもクイックソートが優先されるもう1つの理由です。

追加情報:

ピボットの選択が不十分な場合、最悪の場合のクイックソートが発生します。次の例について考えてみます。

[5、4、3、2、1]

ピボットがグループの最小値または最大値として選択されている場合、クイックソートはO(n ^ 2)で実行されます。リストの最大25%または最小25%にある要素を選択する確率は0.5です。これにより、アルゴリズムは適切なピボットになる確率が0.5になります。典型的なピボット選択アルゴリズム(ランダム要素を選択するなど)を使用する場合、ピボットのすべての選択に対して適切なピボットを選択する可能性は0.5です。サイズが大きいコレクションの場合、常に不適切なピボットを選択する確率は0.5 * nです。この確率に基づいて、クイックソートは平均的な(そして典型的な)ケースに対して効率的です。


O(2n)== O(n)。正しい説明は、MergesortはO(n)の追加メモリを必要とするということです(より具体的には、n / 2の補助メモリが必要です)。また、これはリンクリストには当てはまりません。
ジムBalter

@JimBalter卿、あなたの素晴らしい、価値のあるアイデアを、彼らのパフォーマンスに関する質問の答えとして私たちと共有していただけませんか?前もって感謝します。
snr 2018年

2

これはかなり古い質問ですが、最近両方を扱ったので、ここに私の2cがあります。

マージソートの必要性は平均でNログN比較です。すでに(ほぼ)ソート済みのソート済み配列の場合、これは1/2 N log Nに減少します。マージする間、(ほぼ)常に「左」の部分を1/2 N回選択し、次に右の1/2 N要素をコピーするだけだからです。さらに、すでに並べ替えられた入力によってプロセッサの分岐予測子が輝き、ほとんどすべての分岐が正しく推測されるため、パイプラインのストールが防止されると推測できます。

クイックソートでは、平均で1.38 N log Nの比較が必要です。比較の観点からは既にソートされた配列から大きなメリットはありません(ただし、スワップの観点から、おそらくCPU内の分岐予測の観点からはメリットがあります)。

かなり最近のプロセッサでの私のベンチマークは以下を示しています:

比較関数が(qsort()libc実装のように)コールバック関数である場合、クイックソートはマージソートよりランダム入力で15%、64ビット整数のソート済み配列で30%遅くなります。

一方、比較がコールバックでない場合、私の経験では、クイックソートはマージソートより最大25%優れています。

ただし、(大規模な)配列に一意の値がほとんどない場合は、マージソートがどのような場合でもクイックソートよりも優先されます。

だから多分結論は:比較が高価な場合(例:コールバック関数、文字列の比較、構造の多くの部分を比較して、主に2分の3から3の "if"で違いが出る)-可能性はあなたが良くなることですマージソート付き。単純なタスクの場合、クイックソートはより高速になります。

つまり、クイックソートはN ^ 2である可能性がありますが、Sedgewickは、ランダム化された適切な実装では、N ^ 2を実行するよりも、コンピューターがソートを実行する可能性が高いと考えています-マージソートには追加のスペースが必要です


比較が安ければ、ソートされた入力でもqsortはmergesortに勝りますか?
Eonil、

2

両方の並べ替えアルゴリズムを試してみたところ、再帰呼び出しの数を数えることで、クイックソートは一貫してマージソートよりも再帰呼び出しが少なくなっています。これは、クイックソートにピボットがあり、ピボットが次の再帰呼び出しに含まれないためです。このようにして、クイックソートはマージソートよりも速く再帰ベースケースに到達できます。


ピボットは、QSの再帰呼び出しが少ない理由とは何の関係もありません。それは、QSの再帰の半分が末尾再帰であり、削除できるためです。
ジムBalter

2

これはインタビューでよく寄せられる質問です。マージソートのワーストケースのパフォーマンスは優れていますが、特に大規模な入力の場合、クイックソートはマージソートよりも優れていると考えられています。クイックソートの方が優れているため、いくつかの理由があります。

1-補助スペース:クイックソートはインプレースソートアルゴリズムです。インプレースソートとは、ソートを実行するために追加のストレージスペースが必要ないことを意味します。一方、マージソートは、ソートされた配列をマージするために一時的な配列を必要とするため、インプレースではありません。

2-最悪のケース:O(n^2)ランダムなクイックソートを使用することで、クイックソートの最悪のケースを回避できます。正しいピボットを選択することで、高い確率で簡単に回避できます。適切なピボット要素を選択してケースの平均的な動作を取得すると、パフォーマンスが向上し、マージソートと同じくらい効率的になります。

3-参照の局所性クイックソートは、特にキャッシュの局所性が優れているため、仮想メモリ環境などの多くの場合、マージソートよりも高速になります。

4-末尾再帰:マージソートはそうではありませんが、QuickSortは末尾再帰です。末尾再帰関数は、再帰呼び出しが関数によって実行される最後のものである関数です。テール再帰はコンパイラーによって最適化できるため、テール再帰関数は非テール再帰関数よりも優れていると見なされています。


1

どちらも同じ複雑性クラスに属していますが、両方が同じランタイムを持っているという意味ではありません。Quicksortは通常、Mightsortよりも高速です。これは、タイトな実装のコーディングが容易であり、その操作が高速になるためです。これは、そのクイックソートの方が一般的にマージソートの代わりに使用するより速いためです。

しかしながら!私は個人的に、クイックソートがうまくいかない場合にマージソートまたはマージソートに低下するクイックソートバリアントを頻繁に使用します。覚えておいてください。クイックソートは平均で O(n log n)のみです。最悪のケースはO(n ^ 2)です!Mergesortは常にO(n log n)です。リアルタイムのパフォーマンスまたは応答性が必須であり、入力データが悪意のあるソースからのものである可能性がある場合は単純なクイックソートを使用しないください。


1

すべてが同じであるため、ほとんどの人が最も便利に利用できるものを使用することを期待しています。それ以外は、配列で一般的な選択肢であるマージソートと同様に、配列でクイックソートが非常に高速であることが知られています。

どうして基数やバケットのソートが表示されるのがめったにないのかと思います。それらはO(n)であり、少なくともリンクリストでは、キーを序数に変換するいくつかの方法があります。(文字列とフロートは問題なく動作します。)

その理由は、コンピューターサイエンスの教え方に関係していると思います。アルゴリズム分析の講師に、O(n log(n))よりも速くソートできることを実際に示す必要さえありました。(彼はあなたがO(n log(n))よりも速くソートを比較できないという証拠を持っていました、それは真実です。)

他のニュースでは、浮動小数点数は整数として並べ替えることができますが、後で負の数を反転させる必要があります。

編集:実際、floats-as-integersをソートするさらに悪質な方法があります:http : //www.stereopsis.com/radix.html。ビットフリッピングトリックは、実際に使用するソートアルゴリズムに関係なく使用できることに注意してください...


1
基数の種類の共有を見てきました。しかし、正しく分析された場合、ランタイムは入力要素の数よりも多く依存するため O(n)ではないため、使用はかなり困難です。一般に、基数ソートが入力に関して効率的である必要があるそのような強力な予測を行うことは非常に困難です。
Konrad Rudolph、

これ O(n)で、nは要素のサイズを含む合計入力サイズです。実装できるので、多くのゼロを埋め込む必要がありますが、比較に不適切な実装を使用するのはナンセンスです。(とはいえ、実装は難しい場合があります。ymmv)
Anders Eurenius 2008年

GNU libcを使用している場合qsortは、マージソートであることに注意してください。
Jason Orendorff

正確には、必要な一時メモリを割り当てることができない場合を除き、これはマージソートです。cvs.savannah.gnu.org/viewvc/libc/stdlib/…–
Jason

1

クイックソートとマージソートへの小さな追加。

また、並べ替えアイテムの種類にも依存します。アイテムへのアクセス、スワップ、比較がプレーンメモリ内の整数の比較のような単純な操作ではない場合、マージソートが望ましいアルゴリズムになる可能性があります。

たとえば、リモートサーバーのネットワークプロトコルを使用してアイテムを並べ替えます。

また、「リンクリスト」のようなカスタムコンテナでは、クイックソートのメリットはありません。
1.リンクリストのマージソート。追加のメモリは必要ありません。2.クイックソートでの要素へのアクセスはシーケンシャルではありません(メモリ内)


0

クイックソートはインプレースソートアルゴリズムであるため、配列に適しています。一方、マージソートはO(N)の追加ストレージを必要とし、リンクされたリストにより適しています。

配列とは異なり、いいねリストでは、O(1)スペースとO(1)時間で途中にアイテムを挿入できるため、余分なスペースなしでマージソートのマージ操作を実装できます。ただし、配列に追加のスペースを割り当てたり割り当て解除したりすると、マージソートの実行時間に悪影響が及びます。マージソートでは、ランダムメモリアクセスをほとんど行わずにデータに順次アクセスするため、リンクリストも優先されます。

一方、クイックソートには大量のランダムメモリアクセスが必要です。配列を使用すると、リンクリストのように走査せずに直接メモリにアクセスできます。また、配列はメモリに連続して格納されるため、配列に使用した場合のクイックソートは参照の局所性が良好です。

両方の並べ替えアルゴリズムの平均的な複雑さはO(NlogN)ですが、通常のタスクでは通常、配列をストレージとして使用します。そのため、クイックソートが最適なアルゴリズムです。

編集:私はマージソートの最悪/最高/平均のケースが常にnlognであることを発見しましたが、クイックソートはn2(要素がすでにソートされている最悪のケース)からnlogn(ピボットが常に配列を2つに分割するときの平均/ベストケース)に変わる可能性があります半分)。


0

時間と空間の両方の複雑さを考慮してください。マージソートの場合:時間の複雑度:O(nlogn)、スペースの複雑度:O(nlogn)

クイックソートの場合:時間の複雑度:O(n ^ 2)、スペースの複雑度:O(n)

今、彼らは両方とも1つのscenerioで勝ちます。ただし、ランダムピボットを使用すると、ほとんどの場合、クイックソートの時間の複雑さをO(nlogn)に減らすことができます。

したがって、多くのアプリケーションでは、マージソートではなくクイックソートが推奨されます。


-1

c / c ++ランドでは、stlコンテナーを使用しない場合、Quicksortを使用する傾向があります。これは、ランタイムに組み込まれているのに対し、mergesortはそうではないためです。

したがって、多くの場合、それは単に最小の抵抗の道であると私は信じています。

さらに、データセット全体がワーキングセットに収まらない場合は、クイックソートを使用するとパフォーマンスが大幅に向上します。


3
実際、それがあなたが話しているqsort()ライブラリ関数である場合、それはクイックソートとして実装されている場合とされていない場合があります。
Thomas Padron-McCarthy

3
コンラッド、これについて少し肛門になって申し訳ありませんが、あなたはその保証をどこで見つけますか?ISO C標準またはC ++標準では見つかりません。
Thomas Padron-McCarthy

2
qsort要素の数が本当に巨大であるか、一時メモリが割り当てられない限り、GNU libc はマージソートです。cvs.savannah.gnu.org/viewvc/libc/stdlib/…–
Jason

-3

その理由の1つは、より哲学的です。クイックソートはトップ→ダウンの哲学です。並べ替える要素がn個ある場合、n個あります。可能性。相互に排他的なmとnmの2つのパーティションにより、可能性の数は数桁下がります。m!*(nm)!nよりも数桁小さい!一人で。想像してみてください!対3!* 2!。5!2と3の2つのパーティションの10倍の可能性があります。100万の階乗と900Kに外挿します!* 100K!vs.したがって、範囲またはパーティション内の順序を確立することを心配する代わりに、パーティションのより広いレベルで順序を確立し、パーティション内の可能性を減らします。パーティション自体が相互に排他的でない場合、範囲内で先に確立された順序は後で妨害されます。

マージソートやヒープソートなどのボトムアップ方式のアプローチは、ミクロレベルで早期に比較を開始するワーカーまたは従業員のアプローチに似ています。しかし、これらの間にある要素が後で見つかるとすぐに、この順序は失われます。これらのアプローチは非常に安定しており、非常に予測可能ですが、ある程度の余計な作業を行います。

クイックソートは、最初は注文を気にせず、注文を考慮せずに幅広い基準を満たすことのみを考慮した管理アプローチに似ています。次に、ソートされたセットが得られるまで、パーティションが狭められます。クイックソートの本当の課題は、ソートする要素について何も知らないときに、暗闇の中でパーティションまたは基準を見つけることです。そのため、中央値を見つけるために努力するか、ランダムに1つまたは任意の「管理」アプローチを選択する必要があります。完璧な中央値を見つけるにはかなりの労力が必要であり、また愚かなボトムアップアプローチにつながります。したがって、Quicksortはランダムなピボットを選択するだけであり、それが中間のどこかになるか、中央値3、5、またはそれ以上の中央値を見つけるために何らかの作業を行って、より良い中央値を見つけることを期待しますが、完全であるとは考えていません。最初の注文で時間を無駄にしない。これは、運が良かったり、中央値を取得せずにチャンスをとるだけでn ^ 2に低下したりする場合にうまくいくようです。どのような方法でもデータはランダムです。正しい。だから私はクイックソートのトップ→ダウン論理アプローチにもっと同意します、そしてピボットの選択とそれが以前に保存する比較について取る可能性は、どんな細心の徹底した安定したボトム→アップのアプローチよりもうまく機能するようですマージソート。だが 以前に保存された比較は、マージソートなどの詳細で安定したボトムからアップへのアプローチよりも多くの時間で機能するようです。だが 以前に保存された比較は、マージソートなどの詳細で安定したボトムからアップへのアプローチよりも多くの時間で機能するようです。だが


クイックソートは、ピボット選択のランダム性からメリットを得ます。ランダムなピボットは当然50:50パーティションに向かう傾向があり、極端なものの1つに一貫して向かう可能性は低いです。nlognの定数係数は、平均パーティショニングが60〜40まで、または70〜30までさえかなり低くなります。
Winter Melon、

これはまったくナンセンスです。「哲学」ではなく、そのパフォーマンスのためにクイックソートが使用されています...「注文は失われる」という主張は単に誤りです。
ジムBalter
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.