クイックソートとヒープソート


回答:


60

この論文にはいくつかの分析があります。

また、ウィキペディアから:

クイックソートの最も直接的な競争相手はヒープソートです。ヒープソートは通常、クイックソートよりもいくらか遅いですが、最悪の場合の実行時間は常にΘ(nlogn)です。クイックソートは通常より高速ですが、不良ケースが検出されるとヒープソートに切り替わるイントロソートバリアントを除いて、最悪の場合のパフォーマンスの可能性が残っています。ヒープソートが必要になることが事前にわかっている場合、それを直接使用すると、イントロソートがそれに切り替わるのを待つよりも速くなります。


12
典型的な実装では、クイックソートもヒープソートも安定したソートではないことに注意することが重要な場合があります。
MjrKusanagi 2014年

@DVKは、あなたのリンクによるとcs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html、ヒープはソートのための2842回の比較をとり、N = 100、それは、n = 500のための53113回の比較をとります。これは、n = 500とn = 100の比率が18倍であることを意味し、ヒープソートアルゴリズムとO(N logN)の複雑さを一致させません。彼らのヒープソートの実装には、内部に何らかのバグがある可能性が非常に高いと思います。
DU Jiaen 2017年

@DUJiaen-O()は大きなNでの漸近的な振る舞いであり、可能な乗数があることを覚えておいてください
DVK

これは乗数とは関係ありません。アルゴリズムの複雑度がO(N log N)の場合、Time(N)= C1 * N * log(N)の傾向に従う必要があります。そして、Time(500)/ Time(100)を取ると、C1が消えて結果が(500 log500)/(100 log100)= 6.7に閉じられることは明らかですが、リンクからは18、つまり規模が大きすぎます。
DU Jiaen 2017年

2
リンクは
無効

123

ヒープソートは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);
}

10
いいえに関する考慮事項のための+1。のソートアルゴリズムに必要なスワップ、読み取り/書き込み操作の例
ycy

2
確定的で一定の時間ピボット選択戦略の場合、O(n ^ 2)の最悪のケースを生成する配列を見つけることができます。最小値を排除するだけでは不十分です。特定のpecrentileバンド内にあるピボットを確実に選択する必要があります。
アンチモン

1
これが、手動でコーディングしたクイックソートとC#の組み込みArray.sortの間のシミュレーションで実行したコードとまったく同じですか?私はこのコードをテストし、すべてのテストで、せいぜい手動でコーディングしたクイックソートはArray.sortと同じでした。このテストで私が制御したことの1つは、ランダム配列の2つの同一のコピーを作成することでした。結局のところ、特定のランダム化は、別のランダム化よりも有利になる可能性があります(ベストケースに傾く)。だから私はそれぞれを通して同じセットを走らせました。Array.sortは毎回タイまたはビートです(リリースビルドbtw)。
クリス

1
マージソートは、教科書からの非常に単純な実装でない限り、要素の100%をコピーする必要はありません。50%(2つのマージされた配列の左側)をコピーするだけで済むように実装するのは簡単です。また、実際に2つの要素を「スワップ」する必要があるまでコピーを延期するのは簡単です。そのため、すでにソートされたデータでは、メモリのオーバーヘッドはありません。したがって、50%でも実際には最悪のケースであり、50%と0%の間であれば何でも可能です。
ddekany 2017

1
@MarquinhoPeli 100%ではなく、ソートされたリストのサイズと比較して50%だけ多くの利用可能なメモリが必要であると言うつもりでしたが、これは一般的な誤解のようです。だから私はピーク時のメモリ使用量について話していました。リンクを与えることはできませんが、配列の既に並べ替えられた2つの半分を所定の位置にマージしようとすると、簡単に確認できます(まだ消費していない要素を上書きする問題は、左半分だけにあります)。並べ替えプロセス全体で必要なメモリコピーの量は別の問題ですが、どの並べ替えアルゴリズムでも、最悪のケースが100%を下回ることはありません。
ddekany

15

ほとんどの状況では、速いか少し速いかは関係ありません...たまにwaayyyが遅くなることを望まないだけです。QuickSortを調整して状況が遅くなるのを回避することはできますが、基本的なQuickSortの優雅さは失われます。したがって、ほとんどの場合、私は実際にはHeapSortを好みます。完全に単純な優雅さで実装でき、決して遅いソートを取得できません。

ほとんどの場合に最高速度が必要な状況では、QuickSortがHeapSortよりも優先される場合がありますが、どちらも正しい答えではない場合があります。速度が重要な状況では、状況の詳細を綿密に調べる価値があります。たとえば、速度が重要な一部のコードでは、データが既に並べ替えられているか、ほぼ並べ替えられていることがよくあります(複数の関連フィールドにインデックスを付けて、上下に移動したり、上下に逆方向に移動したりすることがよくあります。したがって、1つずつ並べ替えると、その他は並べ替えられるか、逆順に並べ替えられるか、または近いものになります。その場合、どちらも実装しませんでした...代わりに、ダイクストラのSmoothSortを実装しました...ソート済みまたはソート済みに近い場合はO(N)であるHeapSortバリアント...それほどエレガントではなく、理解するのも簡単ではありません。しかし速い...読んでhttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDFコーディングが少し難しい場合。


6

Quicksort-Heapsortインプレースハイブリッドも非常に興味深いものです。それらのほとんどは、最悪の場合にn * log nの比較しか必要としないためです(これらは、漸近の最初の項に関して最適であり、最悪のシナリオを回避します) Quicksort)、O(log n)の余分なスペースがあり、すでに順序付けられているデータのセットに関して、Quicksortの良好な動作の少なくとも「半分」を保持します。DikertとWeissが非常に興味深いアルゴリズムをhttp://arxiv.org/pdf/1209.4214v1.pdfに示しています。

  • sqrt(n)要素のランダムサンプルの中央値としてピボットpを選択します(これは、Tarjan&coのアルゴリズムによる最大24のsqrt(n)比較、またはより複雑なスパイダーによる5つのsqrt(n)比較で実行できます。 -Schonhageの工場アルゴリズム);
  • Quicksortの最初のステップと同様に、アレイを2つの部分に分割します。
  • 最小部分をヒープ化し、O(log n)の追加ビットを使用して、左側のすべての子が兄弟よりも大きい値を持つヒープをエンコードします。
  • 再帰的にヒープのルートを抽出し、ルートによって残されたラクーンをヒープの葉に到達するまでふるいにかけ、次に、アレイの他の部分から取得した適切な要素でラクーンを埋めます。
  • 配列の順序付けされていない残りの部分を再帰します(正確な中央値としてpが選択されている場合、再帰はありません)。

2

コンプ 間quick sortmerge sortの両方以来wrost例の時間を実行しているwrost場合との違いは、クイックソートのための時間を実行している並べ替えがある場所での一種であるO(n^2)とヒープの並べ替えのためにそれはまだあるO(n*log(n))とのデータの平均量のためのクイックソートより有用であろう。それはランダム化されたアルゴリズムなので、正しいansを取得する確率です。短時間で、選択したピボット要素の位置に依存します。

だから

良い呼び出し: LとGのサイズはそれぞれ3s / 4未満です

悪い呼び出し: LとGのいずれかのサイズが3s / 4より大きい

少量の場合は挿入ソートに、大量のデータの場合はヒープソートに使用できます。


マージソートはインプレースソートで実装できますが、実装は複雑です。私の知る限り、ほとんどのマージソート実装はインプレースではありませんが、安定しています。
MjrKusanagi 2014年

2

ヒープソートにはO(n * log(n))の実行ケースが最悪であるという利点があるため、クイックソートのパフォーマンスが低い可能性がある場合(ほとんどの場合、ソートされたデータセットが一般的)にヒープソートが優先されます。


4
クイックソートは、不十分なピボットの選択方法が選択されている場合、ほとんどソートされたデータセットに対してのみ不十分に実行されます。つまり、ピボットを選択する方法としては、常に最初または最後の要素をピボットとして選択するのが適切ではありません。毎回ランダムなピボットが選択され、繰り返し要素を処理する適切な方法が使用されている場合、最悪の場合のクイックソートの可能性は非常に小さくなります。
ジャスティンピール

1
@ジャスティン-それは本当です、私は素朴な実装について話していました。
zellio

1
@ジャスティン:真ですが、大きな減速の可能性は常にありますが、わずかです。一部のアプリケーションでは、たとえ遅いとしても、O(n log n)の動作を保証したい場合があります。
David Thornley、2010年

2

まあ、アーキテクチャレベルに行くと...キャッシュメモリのキューデータ構造を使用するので、キューで利用できるものはすべてソートされます。クイックソートと同様に、配列を任意の長さに分割しても問題はありませんが、ヒープでは問題ありません。並べ替え(配列を使用)すると、キャッシュで使用可能なサブ配列に親が存在しない場合があり、キャッシュメモリに配置する必要があります。これには時間がかかります。クイックソートが一番です!!😀


1

ヒープソートヒープを構築した後、繰り返しの最大項目を抽出します。最悪のケースはO(n log n)です。

しかし、クイックソートの最悪のケースであるO(n2)が表示される場合、クイックソートは大きなデータにはあまり適していない選択肢であることがわかります。

したがって、これはソートを興味深いものにします。今日、非常に多くの並べ替えアルゴリズムが存在する理由は、それらすべてが最高の場所で「最高」であるためです。たとえば、データがソートされている場合、バブルソートはクイックソートを実行できます。または、並べ替えるアイテムについて何か知っている場合は、おそらくもっとうまくいくでしょう。

これはあなたの質問に直接答えないかもしれません、私は私の2セントを追加すると思いました。


1
バブルソートは絶対に使用しないでください。データがソートされると合理的に考える場合は、挿入ソートを使用するか、データをテストしてソートされているかどうかを確認することもできます。bubblesortを使用しないでください。
vy32 14

非常に大きなRANDOMデータセットがある場合、最善の策はクイックソートです。部分的に順序付けられている場合はそうではありませんが、巨大なデータセットでの作業を開始する場合は、少なくともこれらについて理解している必要があります。
Kobor42 2014

1

ヒープソートは、非常に大きな入力を処理する場合に安全です。漸近分析により、最悪の場合のHeapsortの成長の順序がであることがわかります。Big-O(n logn)これはBig-O(n^2)、最悪の場合のQuicksortよりも優れています。ただし、ヒープソートは、実際に実装されているクイックソートよりも、ほとんどのマシンでやや低速です。Heapsortも安定したソートアルゴリズムではありません。

ヒープソートがクイックソートよりも実際に遅い理由は、データ要素が比較的近い保管場所内にあるクイックソートの参照の局所性( " https://en.wikipedia.org/wiki/Locality_of_reference ")が優れているためです。参照の局所性が強いシステムは、パフォーマンス最適化の優れた候補です。ただし、ヒープソートは大きな飛躍を扱います。これにより、入力が小さいほどクイックソートが有利になります。


2
クイックソートも安定していません。
アンチモン

1

私にとって、ヒープソートとクイックソートの間には非常に根本的な違いがあります。後者は再帰を使用しています。再帰アルゴリズムでは、ヒープは再帰の数とともに増加します。これは、nが小さい場合は問題になりませんが、現在、n = 10 ^ 9で2つの行列をソートしています!!。プログラムは約10 GBのRAMを消費し、追加のメモリがあると、コンピュータで仮想ディスクメモリへのスワップが開始されます。私のディスクはRAMディスクですが、それでもスワップすると速度が大きく異なります。したがって、C ++でコード化されたstatpackには、サイズが調整可能な次元マトリックスが含まれており、プログラマーには事前にサイズが不明であり、ノンパラメトリックな統計的なソートが行われているため、非常に大きなデータマトリックスでの使用による遅延を回避するために、ヒープソートを使用します。


1
平均でO(logn)メモリのみが必要です。再帰のオーバーヘッドは、ピボットで不運にならないことを前提として、取るに足らないものです。この場合、より大きな問題が心配されます。
アンチモン

-1

元の質問に回答し、他のコメントのいくつかにここで対処するには:

私は、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の深いヒープまで移動する必要がある距離に応じて、より高速です。ヒープは元の配列にインプレースで簡単に実装できるため、追加のメモリは必要なく、反復的であるため、再帰中のスタックオーバーフローの心配もありません。ヒープの並べ替えの大きな欠点は、安定した並べ替えではないということです。つまり、必要な場合は問題ありません。


クイックソートは安定したソートではありません。それ以上に、この種の質問は意見に基づく対応を促し、戦争や議論の編集につながる可能性があります。意見に基づく対応を求める質問は、SOガイドラインで明示的に推奨されていません。回答者は、たとえ豊富な経験と知恵があっても、回答する誘惑を回避する必要があります。それらにフラグを立てて閉じるか、十分な評判のある人がフラグを立てて閉じるのを待つかのいずれかです。このコメントは、あなたの知識や回答の妥当性を反映するものではありません。
MikeC
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.