マージソート
マージソートは、最も人気のある効率的なソートアルゴリズムの 1つです。これは、分割統治アルゴリズムの原理に基づいています。個々の要素に分割された配列が得られるまで、繰り返し配列を 2つの半分に分割することで動作します。個々の要素はそれ自体がソートされた配列です。マージソートは、最終的にソートされた配列が得られるまで、これらの小さなソートされた配列を繰り返しマージして、より大きなソートされたサブ配列を生成します。これは電子商取引のアプリケーションで広く使われています。
マージソートアルゴリズム
トップダウンマージソートのアプローチは、配列全体を上から見て、再帰を用いて個々の要素に向かって下に進んでいきます。
n
要素を含むソートされていない配列 A[]
があるとします。
MergeSort()
-
2つの変数
beg
とend
を取り、開始要素と終了要素のインデックスを格納します。 -
配列の中点を求めて、
mid =(beg+end)/2
の式を用いて配列を 2つに分割します。 -
配列を 2つの部分に分割する
arr[beg, .... , mid]
とarr[mid+1, .... , end]
。 -
再帰を利用して、配列を単一要素のサブ配列に繰り返し分割します。
-
マージ関数を呼び出して、ソートされた配列の構築を開始します。
Merge()
-
ソートされた出力を格納するための補助配列
output
を初期化します。 -
3つのポインタ
i
、j
、k
を初期化します。i
は最初の部分配列beg
の先頭を指します。
j
は 2 番目の部分配列mid+1
の先頭を指します。
beg
で初期化されたk
は、ソートされた配列output
の現在のインデックスを保持します。 -
arr[beg, .... ,mid]
またはarr[mid+1, .... ,end]
部分配列の終わりに到達するまでは、インデックスi
&j
の要素の中から小さい方の値をピックしてoutput
に挿入します。 -
残りの要素をピックして、配列を使い切ったら
output
に挿入します。 -
output
をarr[beg, ... , end]
にコピーします。
マージソートの例
配列 (5,3,4,2,1,6)
があるとします。マージソートアルゴリズムを使用してソートします。
アクション | (5,3,4,2,1,6) |
mergesort(0,5) |
---|---|---|
divide |
(5,3,4) (2,1,6) |
mergesort(0,2) mergesort(3,5) 。 |
divide |
(5,3) (4) (2,1) (6) |
mergesort(0,1) mergesort(2,2) mergesort(3,4) mergesort(5,5) です。 |
divide |
(5) (3) (4) (2) (1) (6) |
配列が個々の要素に分割されました |
merge |
(3,5) (4) (1,2) (6) |
merge(0,1) merge(2,2) merge(3,4) merge(5,5) 。 |
merge |
(3,4,5) (1,2,6) |
merge(0,2) merge(3,5) . |
merge |
(1,2,3,4,5,6) |
merge(0,5) |
ソートされた配列 (1 2 3 4 5 6)
が得られます。
マージソートアルゴリズムの実装
#include <iostream>
using namespace std;
void merge(int arr[], int beg, int mid, int end) {
int output[end - beg + 1];
int i = beg, j = mid + 1, k = 0;
// add smaller of both elements to the output
while (i <= mid && j <= end) {
if (arr[i] <= arr[j]) {
output[k] = arr[i];
k += 1;
i += 1;
} else {
output[k] = arr[j];
k += 1;
j += 1;
}
}
// remaining elements from first array
while (i <= mid) {
output[k] = arr[i];
k += 1;
i += 1;
}
// remaining elements from the second array
while (j <= end) {
output[k] = arr[j];
k += 1;
j += 1;
}
// copy output to the original array
for (i = beg; i <= end; i += 1) {
arr[i] = output[i - beg];
}
}
void mergeSort(int arr[], int beg, int end) {
if (beg < end) {
int mid = (beg + end) / 2;
// divide and conquer
mergeSort(arr, beg, mid); // divide : first half
mergeSort(arr, mid + 1, end); // divide: second half
merge(arr, beg, mid, end); // conquer
}
}
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";
mergeSort(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) = 2T(n/2) + θ(n)
この再帰関係の結果は T(n) = nLogn
となり、サイズ n の配列が最大の Logn
部分に分割され、各部分のマージには O(n)
の時間がかかると考えることができます。
したがって、時間の複雑さは [Big Theta]: O(nLogn)
のオーダーになります。
- 最悪のケース
最悪の場合の時間的複雑さは [Big O]: O(nLogn)
です。
- 最良のケース
最良のケースの時間複雑度は [Big Omega]: O(nLogn)
です。これは最悪の時間複雑度と同じです。
空間計算量
マージソートアルゴリズムの空間複雑度は O(n)
であるが、これはソートされた部分配列を補助配列に格納するために 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