クイックソートクイックソートアルゴリズム

目次

  1. 序文
  2. アルゴリズムのステップ
  3. ハブ元を選択します
  4. ストライプ配列
  5. アルゴリズム
  6. 小さな配列や挿入ソート
  7. エピローグ

序文

クイックソートアルゴリズムが内蔵されたアルゴリズムをソートする多くの言語で使用される最も一般的なソートアルゴリズムである必要があり、このアルゴリズムの直接的または間接的に使用されています。

したがって、我々がソートアルゴリズムの高速学ぶことが必要です。

アルゴリズムのステップ

詳細なアルゴリズム・ステップを見る前に、私たちは、最初のアルゴリズムのクイックソートアルゴリズムの複雑さを見ることができます:

時間の複雑さ(平均) 時間の複雑さ(最悪) 宇宙複雑
$ O(nlog_2n)$ $O(n^2)$ $ O(1)$

クイックソートアルゴリズムのアルゴリズムの複雑さによって、我々はそれがかもしれないと推測することができます分割統治アルゴリズムを、実際には真です。

フラッシュアレイによってアルゴリズムをソートする場合、\(\)ソートするとき、我々は再帰的に異なるセクションに処理する必要がある、の基本的な手順:

  1. アレイの場合\(\)は、要素の数は、直接リターン1または0であります
  2. アレイ選択\(\)の任意の要素の\(P \)としてハブ要素
  3. アレイ\(\)二つの部分に分け要素:すべての要素より少ない \(P \)部分\(A_1 \)とすべての要素がより大きいかまたは等しい (P \)\部分\(A_2 \)
  4. 背面[ \(クイックソート(A_1)\) \(P \) \(クイックソート(A_2)\) ]

あなたが見ることができる、基本的な手順速いソートアルゴリズムは考慮するために、このアルゴリズムの主な問題を実現することは困難ではないですハブ元の選択とどのように分割する配列を。

ハブ元を選択します

一般的なハブ元の選択は、おそらく配列を選択するための方法であるの真ん中に簡単かつ効果的な、その要素を。

ハブ要素を選択した後、典型的には、両端の値とわずかにアルゴリズムの効率を高めることができ、不測の事態を回避するために、ハブ要素の並べ替え、必要1

public static int selectPivot(int[] arr, int left, int right) {  // 包含右边界
  int mid = (left + right) / 2;

  if (arr[left] > arr[mid]) {
    swap(arr, left, mid);
  }

  if (arr[left] > arr[right]) {
    swap(arr, left, right);
  }

  if (arr[mid] > arr[right]) {
    swap(arr, mid, right);
  }

  /* arr[left] <= arr[mid] <= arr[right] */

  swap(arr, mid, right - 1);

  return arr[right - 1];
}

値は両端とハブ要素でソートされた後、左端の値がより小さいかハブ要素の値に等しくなければならない、値がより大きい又は等しい値のハブにおける右端の要素である必要があり、我々は分割する必要があります配列になります[left + 1, right - 1]

ドルと交換ハブすると、right - 1そのハブ元の要素が配列に分割するまま2、そして、我々は配列になるだろう分割する必要があります[left + 1, right - 2]

ここでは一般的なアプローチと実際によくある間違いを導入していない、一般的な慣行上に導入:

  • 乱数値を生成するコストが計算されたコストと比較されているので、ハブは、ランダムに選択された要素は、一般的ではないはるかに高価です
  • 入力アレイが事前ソート、すべての要素を単一のグループに割り当てられるとき、ハブ要素は、一般的な誤りであるとして直接最初の要素を選択し、時間計算量となるように、再帰的に継続(\ O(N ^ 2)\)

ストライプ配列

アレイを分割する場合、最初の要素及びハブ要素の交換位置の終了を選択し、左からのすべての要素の右トラバーサルのために必要なハブ要素未満である、すべての要素がトラバースハブ員を左に右より大きい、または両方が満たされたときにより大きい元素ストップよりもハブ元は少ないときは、遭遇した要素を交換し、その後、千鳥までトラバースし続けます。

        初始状态:
            8  1  4  9  0  3  5  2  7  6
            i                       j  p

        交换前:
            8  1  4  9  0  3  5  2  7  6
            i                    j     p

        交换后:
            2  1  4  9  0  3  5  8  7  6
            i                    j     p

インターリーブ後iハブ要素の末尾要素と、今回の交換を、以下である左側の要素ハブ要素は、要素が右のそれ以上です。

        交错后:
            2  1  4  5  0  3  9  8  7  6
                           j  i        p

        交换后:
            2  1  4  5  0  3  6  8  7  9
                              i        p

考慮すべきトラバーサルの問題がある:遭遇し、ハブ元等しい要素がどのように対処しますか?これは、停止して停止するかしませんか?

入力仮定の要素は、私たちが最後の可能な2つの戦略を見て、同じです:

        都停止:
            8  8  8  8  8  8  8  8  8
                     j  i           p

        都不停止:
            8  8  8  8  8  8  8  8  8
            i                    j  p

あなたが見ることができるとき、それはいくつかの不要なやり取りが生成されますが、すべての使用は、戦略を停止しますが、停止していない、それは時間の複雑さが非常に高くなるだろう、非常に不均一の配列を、分割します。

したがって、より良い選択は、同じ要素が停止されている面とハブ元です。

アルゴリズム

セグメンテーション戦略とハブ元の配列を選択する方法を決定する際に、あなたは、クイックソートアルゴリズムを達成しようとすることができます。

public static void quickSort(int[] arr) {
  quickSort(arr, 0, arr.length - 1);
}

public static void quickSort(int[] arr, int left, int right) {  // 包含右边界
  if  (left >= right) {  // 元素小于等于 1 个
    return;
  }

  int i = left, j = right - 1, pivot = selectPivot(arr, left, right);

  while (i < j) {
    while (arr[++i] < pivot) {}
    while (arr[--j] > pivot) {}

    if (i < j) {
      swap(arr, i, j) ;
    }
  }

  swap(arr, i, right - 1);

  quickSort(arr, left, i - 1);
  quickSort(arr, i + 1, right);
}

public static int selectPivot(int[] arr, int left, int right) {
  int mid = (left + right) / 2;

  if (arr[left] > arr[mid]) {
    swap(arr, left, mid);
  }

  if (arr[left] > arr[right]) {
    swap(arr, left, right);
  }

  if (arr[mid] > arr[right]) {
    swap(arr, mid, right);
  }

  /* arr[left] <= arr[mid] <= arr[right] */

  swap(arr, mid, right - 1);

  return arr[right - 1];
}

public static void swap(int[] arr, int i, int j) {
  int tmp = arr[i];
  arr[i] = arr[j];
  arr[j] = tmp;
}

アルゴリズムの実装は難しいことではありませんが、コードの一部であることに留意されたいです。

while (i < j) {
  while (arr[++i] < pivot) {}
  while (arr[--j] > pivot) {}

  if (i < j) {
    swap(arr, i, j) ;
  }
}

このコードは次の形式に変更される場合、それはの顔になりますarr[i] = arr[j] = pivot無限ループに状況:

while (i < j) {
  while (arr[i] < pivot) {i++;}
  while (arr[j] > pivot) {j--;}

  if (i < j) {
    swap(arr, i, j) ;
  }
}

小さな配列や挿入ソート

クイックソートを実装する際に、小さな配列としてよくない挿入ソートクイックソートのパフォーマンスは、それゆえ、多くの場合、小さい方のアレイのような、挿入ソートでソートされますOpenJDKのはこれの実装があります。

次のように改善を達成:

public static void insertSort(int[] arr, int left, int right) {  // 包含右边界
  for (int p = left + 1; p <= right; p++) {
    int tmp = arr[p];
    for (int j = p; j > left && arr[j - 1] > tmp; j--) {
      arr[j] = arr[j - 1];
    }
    arr[j] = tmp;
  }
}

public static void quickSort(int[] arr) {
  quickSort(arr, 0, arr.length - 1);
}

public static void quickSort(int[] arr, int left, int right) {
  if  (left + 20 >= right) {  // 小数组
    insertSort(arr, left, right);
    return;
  }

  int i = left, j = right - 1, pivot = selectPivot(arr, left, right);

  while (i < j) {
    while (arr[++i] < pivot) {}
    while (arr[--j] > pivot) {}

    if (i < j) {
      swap(arr, i, j) ;
    }
  }

  swap(arr, i, right - 1);

  quickSort(arr, left, i - 1);
  quickSort(arr, i + 1, right);
}

エピローグ

このブログの内容のほとんどは、「データ構造とアルゴリズム解析- C言語記述、」から派生しているの著書7.7、フェスティバル興味を持っている見てみることができます@ _ @

脚注

1つの不測の事態は、参考-ブック「C言語で記述されたデータ構造とアルゴリズムの分析」の問題 7.38を

2私は、このアプローチは、(ミスや非効率性を避けるために)ストライプ配列は、より安全なことができますことを知って、このような行為の理由や説明を取得できませんでした

おすすめ

転載: www.cnblogs.com/rgbit/p/11069585.html
おすすめ