クイックソート
クイックソートは、分割統治アルゴリズムの原理に基づいた高効率なソートアルゴリズムです。クイックソートは、選択されたピボット要素を中心に配列を 2つの部分に分割することで動作します。小さい要素はピボットの左側に、大きい要素は右側に移動します。この後、左右の部分が再帰的にソートされ、配列全体がソートされます。一般的なソートアルゴリズムに比べて 2
や 3
倍程度速いので、クイックソートと呼ばれています。クイックソートは、データ構造体の中での情報検索や数値計算に広く利用されています。
クイックソートアルゴリズム
ここでは、n
個の要素を含むソートされていない配列 A[]
があるとします。beg
と end
の 2つの変数を取り、開始要素と終了要素のインデックスを格納します。
Partition()
-
最後の要素(実装によっては任意)をピボット要素として選択します。
-
ポインタ
i
の値をbeg - 1
に初期化し、ピボットより小さい要素を配列の先頭に移動できるようにします。 -
配列を反復的にたどって、各要素について以下のようにします。
-
要素
A[i]
<pivot
の場合はi
をインクリメントし、A[i]
をA[j]
と入れ替える。 -
A[i]
をA[end]
と入れ替えることで、ピボット要素を正しい位置に配置する。 -
ピボット要素のインデックスを返す。
QuickSort()
-
ピボットインデックス
pi
を選択します。 -
ピボットインデックスを中心に配列を分割します。
-
ピボット要素の左辺
arr[beg,...,pi]
の要素を再帰的にソートします。 -
ピボット要素の右辺
arr[pi+1,...,end]
の要素を再帰的にソートします。
クイックソートの例
配列があるとしましょう。(6,5,1,4,2,3)
。これをクイックソートアルゴリズムを用いてソートします。
-
まず
3
をピボット要素として選択し、配列を(1,2)
-小さい要素、(6,5,4)
-大きい要素に分割する。そして、3
がソートされた位置に置かれる。そして、形成された 2つの部分配列は再帰的にソートされる。 -
部分配列
(1,2)
では、2
がピボット要素として選択されて正しい位置に置かれ、既にソートされた部分配列(1)
が形成される。 -
部分配列
(6,5,4)
の場合、4
がソートされた位置に置かれ、再帰的にソートされた部分配列(6,5)
が生成される。 -
部分配列
(6,5)
の場合、5
をピボットとして選択し、正しい位置に配置すると、(6)
が新しい部分配列となる。単一要素の部分配列(6)
は既にソートされている。 -
最後に、ソートされた配列が
(1, 2, 3, 4, 5, 6)
となる。
クイックソートアルゴリズムの実装
#include <bits/stdc++.h>
using namespace std;
int partition(int arr[], int beg, int end) {
// Select the last element as pivot element
int pivot = arr[end];
int i = (beg - 1);
// Move smaller elements to left side and larger on right side
for (int j = beg; j < end; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1],
arr[end]); // Move pivot element to its right position in array
return (i + 1);
}
void quickSort(int arr[], int beg, int end) {
if (beg < end) {
int pi = partition(arr, beg, end);
quickSort(arr, beg,
pi - 1); // Recursive Sort element on left side of partition
quickSort(arr, pi + 1,
end); // Recursive Sort element on right side of partition
}
}
int main() {
int n = 6;
int arr[6] = {5, 3, 4, 2, 1, 6};
cout << "Input array: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << "\n";
quickSort(arr, 0, n - 1); // Sort elements in ascending order
cout << "Output array: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << "\n";
}
クイックソートアルゴリズムの複雑さ
時間計算量
- 平均のケース
クイックソートに要する時間は次の再帰関係で与えられます。
T(n) = T(k) + T(n-k-1) + θ(n)
ここで k
はピボット要素より小さい/大きい要素の数を表します。
この再帰関係の結果は T(n) = nLogn
となります。時間の複雑さは [Big Theta]: O(nLogn)
のオーダーです。
- 最悪のケース
T(n) = T(n-1) + θ(n)
最悪の場合は、ピボット要素が常に配列の最大要素か最小要素のいずれかである場合です。この場合、すべての要素が 1つの部分配列に収まるため、最大の n
呼び出しを行わなければならません。最悪の場合の時間的複雑さは [Big O]:O(n2)です。
- 最良のケース
T(n) = 2T(n/2) + θ(n)
ベストケースは選択されたピボット要素が常に中央の要素であるか, 両パーティションが均等にバランスしているとき, すなわちサイズの差が 1
以下であるときです。最良の時間的複雑さは [Big Omega]: O(nLogn)
です。
空間計算量
クイックソートアルゴリズムの平均ケース空間複雑度は O(Logn)
です。これは再帰スタックが必要とする空間です。しかし、最悪の場合、配列のソートに n
の再帰を必要とする場合、空間の複雑さは O(n)
です。
Harshit Jindal has done his Bachelors in Computer Science Engineering(2021) from DTU. He has always been a problem solver and now turned that into his profession. Currently working at M365 Cloud Security team(Torus) on Cloud Security Services and Datacenter Buildout Automation.
LinkedIn