回答:
この論文にはいくつかの分析があります。
また、ウィキペディアから:
クイックソートの最も直接的な競争相手はヒープソートです。ヒープソートは通常、クイックソートよりもいくらか遅いですが、最悪の場合の実行時間は常にΘ(nlogn)です。クイックソートは通常より高速ですが、不良ケースが検出されるとヒープソートに切り替わるイントロソートバリアントを除いて、最悪の場合のパフォーマンスの可能性が残っています。ヒープソートが必要になることが事前にわかっている場合、それを直接使用すると、イントロソートがそれに切り替わるのを待つよりも速くなります。
ヒープソートはO(N log N)が保証されており、クイックソートの最悪の場合よりもはるかに優れています。ヒープソートは、Mergesortが必要とするように、順序付けられたデータを配置するために別の配列に追加のメモリを必要としません。それでは、商用アプリケーションはなぜQuicksortを使用するのでしょうか。他の実装よりも特別なクイックソートは何ですか?
私は自分でアルゴリズムをテストしましたが、Quicksortには確かに特別なものがあることがわかりました。これは、ヒープおよびマージアルゴリズムよりもはるかに高速に実行されます。
Quicksortの秘密は、不要な要素の交換をほとんど行わないことです。スワップには時間がかかります。
Heapsortを使用すると、すべてのデータが既に順序付けされている場合でも、要素を100%スワップして配列を順序付けます。
Mergesortを使用すると、さらに悪化します。データがすでに順序付けされている場合でも、要素の100%を別の配列に書き込み、元の配列に書き戻します。
クイックソートでは、すでに注文されているものを交換する必要はありません。データが完全に順序付けされている場合、交換するものはほとんどありません!最悪の場合については多くの手間がかかりますが、配列の最初または最後の要素を取得する以外は、ピボットの選択を少し改善することで回避できます。最初の要素、最後の要素、および中間の要素の間の中間要素からピボットを取得する場合、最悪のケースを回避するのに十分です。
クイックソートで優れているのは最悪のケースではなく、最高のケースです。最良の場合、同じ数の比較を実行しますが、ほとんど何も交換しません。平均して、HeapsortやMergesortのように、すべての要素ではなく一部の要素を交換します。それがQuicksortに最高の時間を与えるものです。スワップが少なく、スピードが速い。
リリースモードで実行している私のコンピューターのC#での以下の実装は、Array.Sortをミドルピボットで3秒、改善されたピボットで2秒速くしています(そうです、適切なピボットを取得するためのオーバーヘッドがあります)。
static void Main(string[] args)
{
int[] arrToSort = new int[100000000];
var r = new Random();
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
Console.WriteLine("Press q to quick sort, s to Array.Sort");
while (true)
{
var k = Console.ReadKey(true);
if (k.KeyChar == 'q')
{
// quick sort
Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
QuickSort(arrToSort, 0, arrToSort.Length - 1);
Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
else if (k.KeyChar == 's')
{
Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
Array.Sort(arrToSort);
Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
}
}
static public void QuickSort(int[] arr, int left, int right)
{
int begin = left
, end = right
, pivot
// get middle element pivot
//= arr[(left + right) / 2]
;
//improved pivot
int middle = (left + right) / 2;
int
LM = arr[left].CompareTo(arr[middle])
, MR = arr[middle].CompareTo(arr[right])
, LR = arr[left].CompareTo(arr[right])
;
if (-1 * LM == LR)
pivot = arr[left];
else
if (MR == -1 * LR)
pivot = arr[right];
else
pivot = arr[middle];
do
{
while (arr[left] < pivot) left++;
while (arr[right] > pivot) right--;
if(left <= right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
left++;
right--;
}
} while (left <= right);
if (left < end) QuickSort(arr, left, end);
if (begin < right) QuickSort(arr, begin, right);
}
ほとんどの状況では、速いか少し速いかは関係ありません...たまにwaayyyが遅くなることを望まないだけです。QuickSortを調整して状況が遅くなるのを回避することはできますが、基本的なQuickSortの優雅さは失われます。したがって、ほとんどの場合、私は実際にはHeapSortを好みます。完全に単純な優雅さで実装でき、決して遅いソートを取得できません。
ほとんどの場合に最高速度が必要な状況では、QuickSortがHeapSortよりも優先される場合がありますが、どちらも正しい答えではない場合があります。速度が重要な状況では、状況の詳細を綿密に調べる価値があります。たとえば、速度が重要な一部のコードでは、データが既に並べ替えられているか、ほぼ並べ替えられていることがよくあります(複数の関連フィールドにインデックスを付けて、上下に移動したり、上下に逆方向に移動したりすることがよくあります。したがって、1つずつ並べ替えると、その他は並べ替えられるか、逆順に並べ替えられるか、または近いものになります。その場合、どちらも実装しませんでした...代わりに、ダイクストラのSmoothSortを実装しました...ソート済みまたはソート済みに近い場合はO(N)であるHeapSortバリアント...それほどエレガントではなく、理解するのも簡単ではありません。しかし速い...読んでhttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDFコーディングが少し難しい場合。
Quicksort-Heapsortインプレースハイブリッドも非常に興味深いものです。それらのほとんどは、最悪の場合にn * log nの比較しか必要としないためです(これらは、漸近の最初の項に関して最適であり、最悪のシナリオを回避します) Quicksort)、O(log n)の余分なスペースがあり、すでに順序付けられているデータのセットに関して、Quicksortの良好な動作の少なくとも「半分」を保持します。DikertとWeissが非常に興味深いアルゴリズムをhttp://arxiv.org/pdf/1209.4214v1.pdfに示しています。
コンプ 間quick sort
とmerge sort
の両方以来wrost例の時間を実行しているwrost場合との違いは、クイックソートのための時間を実行している並べ替えがある場所での一種であるO(n^2)
とヒープの並べ替えのためにそれはまだあるO(n*log(n))
とのデータの平均量のためのクイックソートより有用であろう。それはランダム化されたアルゴリズムなので、正しいansを取得する確率です。短時間で、選択したピボット要素の位置に依存します。
だから
良い呼び出し: LとGのサイズはそれぞれ3s / 4未満です
悪い呼び出し: LとGのいずれかのサイズが3s / 4より大きい
少量の場合は挿入ソートに、大量のデータの場合はヒープソートに使用できます。
ヒープソートにはO(n * log(n))の実行ケースが最悪であるという利点があるため、クイックソートのパフォーマンスが低い可能性がある場合(ほとんどの場合、ソートされたデータセットが一般的)にヒープソートが優先されます。
ヒープソートヒープを構築した後、繰り返しの最大項目を抽出します。最悪のケースはO(n log n)です。
しかし、クイックソートの最悪のケースであるO(n2)が表示される場合、クイックソートは大きなデータにはあまり適していない選択肢であることがわかります。
したがって、これはソートを興味深いものにします。今日、非常に多くの並べ替えアルゴリズムが存在する理由は、それらすべてが最高の場所で「最高」であるためです。たとえば、データがソートされている場合、バブルソートはクイックソートを実行できます。または、並べ替えるアイテムについて何か知っている場合は、おそらくもっとうまくいくでしょう。
これはあなたの質問に直接答えないかもしれません、私は私の2セントを追加すると思いました。
ヒープソートは、非常に大きな入力を処理する場合に安全です。漸近分析により、最悪の場合のHeapsortの成長の順序がであることがわかります。Big-O(n logn)
これはBig-O(n^2)
、最悪の場合のQuicksortよりも優れています。ただし、ヒープソートは、実際に実装されているクイックソートよりも、ほとんどのマシンでやや低速です。Heapsortも安定したソートアルゴリズムではありません。
ヒープソートがクイックソートよりも実際に遅い理由は、データ要素が比較的近い保管場所内にあるクイックソートの参照の局所性( " https://en.wikipedia.org/wiki/Locality_of_reference ")が優れているためです。参照の局所性が強いシステムは、パフォーマンス最適化の優れた候補です。ただし、ヒープソートは大きな飛躍を扱います。これにより、入力が小さいほどクイックソートが有利になります。
私にとって、ヒープソートとクイックソートの間には非常に根本的な違いがあります。後者は再帰を使用しています。再帰アルゴリズムでは、ヒープは再帰の数とともに増加します。これは、nが小さい場合は問題になりませんが、現在、n = 10 ^ 9で2つの行列をソートしています!!。プログラムは約10 GBのRAMを消費し、追加のメモリがあると、コンピュータで仮想ディスクメモリへのスワップが開始されます。私のディスクはRAMディスクですが、それでもスワップすると速度が大きく異なります。したがって、C ++でコード化されたstatpackには、サイズが調整可能な次元マトリックスが含まれており、プログラマーには事前にサイズが不明であり、ノンパラメトリックな統計的なソートが行われているため、非常に大きなデータマトリックスでの使用による遅延を回避するために、ヒープソートを使用します。
元の質問に回答し、他のコメントのいくつかにここで対処するには:
私は、selection、quick、merge、およびheap sortの実装を比較して、それらが互いにどのようにスタックするかを確認しました。答えは、彼らはすべて彼らの欠点を持っているということです。
TL; DR:Quickは最も汎用的なソートです(かなり高速で、安定しており、ほとんどがインプレースです)。安定したソートが必要でない限り、個人的にはヒープソートを好みます。
選択-N ^ 2-要素数が20未満程度の場合にのみ有効であり、パフォーマンスが優れています。データが既に並べ替えられていない限り、またはほとんど並べ替えられていない限り。N ^ 2は本当に遅く、本当に速くなります。
クイックは、私の経験では、実際にはないことを迅速に、すべての時間。ただし、クイックソートを一般的なソートとして使用することの利点は、適度に速く、安定していることです。これもインプレースアルゴリズムですが、通常は再帰的に実装されるため、追加のスタックスペースを使用します。また、O(n log n)とO(n ^ 2)の間のどこかにあります。特に値が狭い範囲内にある場合、いくつかの種類のタイミングはこれを確認するようです。10,000,000項目の選択ソートよりもはるかに高速ですが、マージまたはヒープよりも低速です。
マージソートはデータに依存しないため、O(n log n)が保証されます。与えられた値に関係なく、それは単に何をするかを実行します。また、安定していますが、実装に注意しないと、非常に大きなソートによってスタックが破壊される可能性があります。いくつかの複雑なインプレースマージソートの実装がありますが、通常、値をマージする各レベルに別の配列が必要です。これらのアレイがスタック上にある場合、問題が発生する可能性があります。
ヒープのソートは最大O(n log n)ですが、多くの場合、値をlog nの深いヒープまで移動する必要がある距離に応じて、より高速です。ヒープは元の配列にインプレースで簡単に実装できるため、追加のメモリは必要なく、反復的であるため、再帰中のスタックオーバーフローの心配もありません。ヒープの並べ替えの大きな欠点は、安定した並べ替えではないということです。つまり、必要な場合は問題ありません。