マージソート

Harshit Jindal 2023年10月12日
  1. マージソートアルゴリズム
  2. マージソートの例
  3. マージソートアルゴリズムの実装
  4. マージソートアルゴリズムの複雑さ
マージソート

マージソートは、最も人気のある効率的なソートアルゴリズムの 1つです。これは、分割統治アルゴリズムの原理に基づいています。個々の要素に分割された配列が得られるまで、繰り返し配列を 2つの半分に分割することで動作します。個々の要素はそれ自体がソートされた配列です。マージソートは、最終的にソートされた配列が得られるまで、これらの小さなソートされた配列を繰り返しマージして、より大きなソートされたサブ配列を生成します。これは電子商取引のアプリケーションで広く使われています。

マージソートアルゴリズム

トップダウンマージソートのアプローチは、配列全体を上から見て、再帰を用いて個々の要素に向かって下に進んでいきます。

n 要素を含むソートされていない配列 A[] があるとします。

MergeSort()

  • 2つの変数 begend を取り、開始要素と終了要素のインデックスを格納します。
  • 配列の中点を求めて、mid =(beg+end)/2 の式を用いて配列を 2つに分割します。
  • 配列を 2つの部分に分割する arr[beg, .... , mid]arr[mid+1, .... , end]
  • 再帰を利用して、配列を単一要素のサブ配列に繰り返し分割します。
  • マージ関数を呼び出して、ソートされた配列の構築を開始します。

Merge()

  • ソートされた出力を格納するための補助配列 output を初期化します。
  • 3つのポインタ ijk を初期化します。

    i は最初の部分配列 beg の先頭を指します。
    j は 2 番目の部分配列 mid+1 の先頭を指します。
    beg で初期化された k は、ソートされた配列 output の現在のインデックスを保持します。

  • arr[beg, .... ,mid] または arr[mid+1, .... ,end] 部分配列の終わりに到達するまでは、インデックス i &j の要素の中から小さい方の値をピックして output に挿入します。
  • 残りの要素をピックして、配列を使い切ったら output に挿入します。
  • outputarr[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
Harshit Jindal avatar Harshit Jindal avatar

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

関連記事 - Sort Algorithm