「アルゴリズム」ノート5 - クイックソート

  • 基本的なアルゴリズム
    • コード
    • セグメンテーション
    • 特長
  • アルゴリズムの改善
    • スイッチングに挿入ソート
    • 3つのサンプリングセグメンテーション
    • 3ウェイセグメンテーション - 繰り返しエレメント用に最適化

このセクションでは、間違いなく、最も広く使用されているソートアルゴリズムをクイックソートを学びます、Javaなどの多くの言語は、システムのC#のソートはクイックソートで使用されています。クイックソートシンプルですが、異なる入力データ、および他のソートアルゴリズムの典型的なアプリケーションよりもはるかに速く、多様なため。以前のいくつかのソートアルゴリズムと比較して、それはアルゴリズムをソート唯一の場所ではないクイックの利点は、(わずかな補助スタックを必要とする)、および必要な時間の長さは、配列をソートし、NはNlgN比例し、そしていくつかのアルゴリズムは、これら二つの特性の両方の前ではありません。非常に高速がマージソートが、それはスペースのコストは直線的な成長であるのです。
1960年にCAR Hoareの発明により、クイックソートは、20世紀として知られている10件のアルゴリズムの一つ、成果速くソートや他のコンピュータサイエンスのためのホーア自身が1980年チューリング賞で得られました。

基本的なアルゴリズム

クイックソートは分割統治に基づいてソートアルゴリズムであり、その基本的な考え方は、配列が半分に分割されることで、左半分のエレメントよりも常に小さい、右半分要素よりも常に大きい、プロセスは再帰的に継続します配列全体が秩序になるまで続けます。

コード

コードは以下の通りであります:

public class Quick {
    public static void sort(Comparable[] a) {
        StdRandom.shuffle(a);
        sort(a, 0, a.length - 1);
    }

    private static void sort(Comparable[] a, int lo, int hi) {
        if (lo >= hi)
            return;
        int j = partition(a, lo, hi);  //切分方法,待实现
        sort(a, lo, j - 1);  //将左边子数组排序
        sort(a, j + 1, hi);  //将右边边子数组排序
    }

    ...
}

クイックソートソートソートされている2つのサブアレイ、サブアレイに分類し、アレイ全体にマージ順序付けられソート配列をマージも相補的ではなく、マージ、再帰呼び出し前配列全体を処理するために発生;および配列クイックソートをソートそれにより、左右の両側のセグメント化されたサブアレイ素子せ未満であり、それぞれ、分割素子、最終的に全体の配列が順序付けされる性質、及びその再帰呼び出しよりも大きいが、アレイ全体を処理した後に発生します。

セグメンテーション

セグメンテーション効果パーティション()は、それが留まるべき位置に要素[j]をセグメンテーションすることであり、セグメンテーションベースの要素は、ソートの再帰サブアレイソートを残します。最初の配列は、入力フィーチャの影響からアルゴリズムの性能を可能にされ、ご注文前に動揺します。

セグメンテーションはコードです:

private static int partition(Comparable[] a, int lo, int hi) {
    int i = lo, j = hi + 1;
    Comparable v = a[i];
    while (true) {
        while (less(a[++i], v)) {   
            if (i == hi)
                break;
        }
        while (less(v, a[--j])) {  
            if (j == lo)
                break;
        }
        if (i >= j)
            break;
        exch(a, i, j);
    }
    exch(a, lo, j);
    return j;
}

図セグメンテーション手順:

[LO]スライス要素をv i、jは、それぞれ、アレイの左右両端からスキャンを開始ポインタを取り、要素が2未満である要素vを見つけるために左から右に、左から右へ、Vより大きいを発見しました要素は、スケジュールなので、為替タリア位置して、スキャンを継続、為替、左の「ポインタ」iと右の「ポインタ」J出会うまで、彼らはVしばらくvの要素のためのセグメント化プロセスを完了していませんこれは、適切な位置にスケジュールされています。セグメンテーションは、常に要素をスケジュールすることができますたびに、すべての要素が最終的に正しい場所にルーティングされます。
([++ i]は、以下でスキャンするとき V) よりもむしろ少ない([iが++]、 v)を、 私は来たが、LO割り当てられているため、LOが下から開始スキャン、セグメント化元素であります右"のポインタ" J比較コード未満(V、[ - J ])、 代わりj--、+ 1 jが最初に割り当てられているためHI。
境界要素の面では、このコードは、それをスケジュールすることができます。

特長

迅速な選別のための性能特性は、その理想的な状況を仮定することができ、両方のスライスは、各アレイは、半分に分割することができるので、同様のバイナリマージソート、各Nの比較が必要であること、総NLgN時間。

私の本では、とき10万ランダムな整数ソート、速度比較のソートやクイックソートマージ次のとおりです。

Merge, 0.249
Quick, 0.195

アルゴリズムの改善

スイッチングに挿入ソート

クイックソートやマージソート、次に挿入ソートアルゴリズムに切り替えること小規模アレイのパフォーマンスを向上させることができ、再帰の使用と同様です。

public class QuickX {
    private static int CUTOFF = 7;
    ...
    private static void sort(Comparable[] a, int lo, int hi) {
        if (lo + CUTOFF >= hi) {
            insertionSort(a, lo, hi);
            return;
        }
        int j = partition(a, lo, hi);
        sort(a, lo, j - 1);
        sort(a, j + 1, hi);
    }

    private static void insertionSort(Comparable[] a, int lo, int hi) {
        for (int i = lo; i <= hi; i++) {
            for (int j = i; j > lo && less(a[j], a[j - 1]); j--) {
                exch(a, j, j - 1);
            }
        }
    }
}

3つのサンプリングセグメンテーション

スライスされたクイックソート、パフォーマンスに大きな影響をもたらす、理想的なフロントケースを想定し、各アレイのセグメンテーションが半分に分割することができる;しかし、最小の要素から第1の時間スライスカットする場合、ように第二番目に小さい要素点から切り出し、そして、各コールは、この場合には性能が非常に悪くなり、要素を削除します。まず、ソートの開始前に動揺も、それは同じような状況を避けるためです。3つのサンプルポイントを切断する分割素子アレイは、左端、右端、中央値は3つの中間要素を選択する選択するとき、スライスの残高を増加させる方法です。

次のように3つのサンプル・コードは次のとおりです。

public class QuickX {
    private static int partition(Comparable[] a, int lo, int hi) {
        int n = hi - lo + 1;
        int m = median3(a, lo, lo + n / 2, hi);  //三取样切分
        exch(a, lo, m);
        int i = lo, j = hi + 1;
        Comparable v = a[i];
        while (true) {
            while (less(a[++i], v)) { // less(a[i++], v)
                if (i == hi)
                    break;
            }
            while (less(v, a[--j])) { // less(v, a[j--])
                if (j == lo)
                    break;
            }
            if (i >= j)
                break;
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;
    }

    private static int median3(Comparable[] a, int i, int j, int k) {
        return (less(a[i], a[j]) ? (less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i)
                : (less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i));
    }
}

挿入後にソートセグメンテーションと3つのサンプルを最適化し、その後、10万ランダムな整数ソート、ソートおよび最適化速い速度比較クイックソートにしようとすると:

Quick, 0.203
QuickX, 0.159

3ウェイセグメンテーション - 繰り返しエレメント用に最適化

多くの場合、アレイは、素子サブアレイは、すべての反復を有する場合、等生年月日、性別、などの実用的なアプリケーションは、入力のこのタイプの繰り返しの要素の多くを含み、そのセグメント化アルゴリズムを実行し続け、行が存在するであろう時間の無駄を考えると、この状況をさらに最適化することができます。
:三セグメンテーション配列は、コード、素子アレイ要素を分割するよりも大きい、等しい、より少ないそれぞれ3つの部分に切断されます

public class Quick3Way {
    ...

    private static void sort(Comparable[] a, int lo, int hi) {
        if (hi <= lo) return;
        int lt = lo, gt = hi;
        Comparable v = a[lo];
        int i = lo + 1;
        while (i <= gt) {
            int cmp = a[i].compareTo(v);
            if      (cmp < 0) exch(a, lt++, i++);
            else if (cmp > 0) exch(a, i, gt--);
            else              i++;
        }

        // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. 
        sort(a, lo, lt-1);
        sort(a, gt+1, hi);
    }
}

それぞれ順次アレイを走査する、二つのポインタのLTのGTを維持し、[lo..lt-1] V未満であるように、[lt..i-1]は、Vに等しく、[GT + 1..hi]でありますVより大きい、[i..gt]要素は、要素の比較ではまだありません。

最後に、100個の整数、各繰り返しデータ1,000,000 10,000入力として得られる効果を試してみてください。

Quick, 0.408
Quick3Way, 0.03

おすすめ

転載: www.cnblogs.com/zhixin9001/p/11502945.html