web-dev-qa-db-ja.com

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

クイックソートとヒープソートの両方がインプレースソートを実行します。どちらが良いですか?どちらが望ましいアプリケーションとケースは何ですか?

75
avd

このペーパー にはいくつかの分析があります。

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

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

47
DVK

HeapsortはO(N log N)が保証されており、Quicksortの最悪の場合よりもはるかに優れています。 Heapsortは、Mergesortが必要とするように、別の配列が順序付けられたデータを置くためにこれ以上メモリを必要としません。では、なぜ商用アプリケーションはQuicksortに固執するのでしょうか?他の実装よりも特別なQuicksortの特徴は何ですか?

私は自分でアルゴリズムをテストしましたが、Quicksortには本当に特別なものがあることがわかりました。ヒープおよびマージアルゴリズムよりもはるかに高速に実行されます。

Quicksortの秘密は次のとおりです。不要な要素のスワップはほとんど行いません。スワップには時間がかかります。

Heapsortを使用すると、すべてのデータがすでに順序付けられている場合でも、要素を100%交換して配列を順序付けします。

Mergesortでは、さらに悪化します。データがすでに順序付けられていても、100%の要素を別の配列に書き込み、元の配列に書き戻します。

Quicksortを使用すると、すでに注文されているものを交換する必要はありません。データが完全に注文されていれば、ほとんど何も交換しません!最悪のケースについては多くの混乱がありますが、配列の最初または最後の要素を取得する以外に、ピボットの選択を少し改善することで回避できます。最初の要素、最後の要素、および中間の要素の間の中間要素からピボットを取得する場合、最悪のケースを回避するのに十分です。

Quicksortで優れているのは最悪のケースではなく、最良のケースです!最良の場合、同じ数の比較を行いますが、大丈夫ですが、ほとんど何も交換しません。 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);
}
87
Marquinho Peli

たいていの場合、速いか少し速いかは関係ありません...たまに動きが遅くなることを望みません。 QuickSortを微調整して低速な状況を回避できますが、基本的なQuickSortの優雅さは失われます。そのため、ほとんどの場合、実際にはHeapSortを好みます。完全にシンプルなエレガンスで実装でき、スローソートを使用することはできません。

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

14
Brian Kennedy

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

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

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

2
Manav Jain

比較_quick sort_と_merge sort_の間は両方ともインプレースソートのタイプであるため、クイックソートの場合の最悪ケースの実行時間とO(n^2)のヒープソートの実行時間には違いがあります。まだO(n*log(n))であり、平均的なデータ量のクイックソートはより便利です。ランダム化されたアルゴリズムなので、正しいansを取得する確率が高くなります。短時間で選択するピボット要素の位置に依存します。

だから

グッドコール: LとGのサイズはそれぞれ3秒/ 4未満

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

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

2
vicky garg

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

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

1
Benn

Heapsort ヒープを構築してから、最大アイテムを繰り返し抽出します。その最悪のケースはO(n log n)です。

しかし、最悪のケースである quick sort (O(n2))が表示された場合、クイックソートは大きなデータにはあまり適していません。

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

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

1
KMån

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

1
zellio

私にとって、ヒープソートとクイックソートには基本的な違いがあります。後者は再帰を使用します。再帰アルゴリズムでは、ヒープは再帰の数とともに増加します。これは、nが小さい場合は関係ありませんが、今はn= 10 ^ 9 !!で2つの行列をソートしています。プログラムはほぼ10 GBのRAMを必要とし、追加のメモリがあると、コンピューターは仮想ディスクメモリへのスワップを開始します。私のディスクはRAMディスクですが、それと交換しても速度の大きな差になります。したがって、サイズの調整可能な次元行列を含むC++でコーディングされたstatpackではプログラマーには事前に不明であり、ノンパラメトリックな統計的な並べ替えのソートは、非常に大きなデータ行列での使用の遅延を回避するためにヒープソートを好む。

1
csevcik