プログラミング
 Computer >> コンピューター >  >> プログラミング >> プログラミング

配列の転倒数(Inversion Count)をマージソートで効率的に求める方法

配列の転倒数(Inversion Count)とは、配列をソート済みの状態(昇順)へ変換するために必要な入れ替えの回数を表す指標です。添字のペア (i, j) について i < j かつ arr[i] > arr[j] が成り立つとき、そのペアは「転倒(反転)」していると定義されます。

配列がすでにソートされていれば転倒数は 0 になり、逆に配列が完全に逆順(降順)に並んでいる場合は転倒数が最大になります。

すべてのペアを総当たりで調べる素朴なアプローチでは O(n²) の計算量が必要ですが、マージソートを応用した分割統治法(Divide and Conquer)を用いることで、計算量を O(n log n) まで削減できます。本記事ではそのアルゴリズムと C++ 実装例を紹介します。

入力と出力

入力:
数列 (1, 5, 6, 4, 20)

出力:
数値を昇順に並べるために必要な転倒(入れ替え)の回数

この例での転倒数は 2 です。
1 回目の転倒:(1, 5, 4, 6, 20)
2 回目の転倒:(1, 4, 5, 6, 20)

アルゴリズム

merge 関数

入力:マージ対象となる2つの部分配列と、left・mid・right の各インデックス
出力:ソート済みの順序でマージされた配列

Begin
    i := left
    j := mid
    k := left
    count := 0

    while i <= mid - 1 かつ j <= right の間、繰り返す
        if array[i] <= array[j] then
            tempArray[k] := array[i]
            i と k を 1 ずつ増やす
        else
            tempArray[k] := array[j]
            j と k を 1 ずつ増やす
            count := count + (mid - i)   // 左側に残る要素数ぶん反転が発生
    done

    // 左側の部分配列に余った要素がある場合
    while i <= mid - 1
        tempArray[k] := array[i]
        i と k を 1 ずつ増やす
    done

    // 右側の部分配列に余った要素がある場合
    while j <= right
        tempArray[k] := array[j]
        j と k を 1 ずつ増やす
    done

    return count
End

ポイントは、右側の要素が左側の要素より小さいときに count += (mid - i) を実行する箇所です。左側の部分配列はすでにソートされているため、そこに残っている要素はすべて現在の右側要素よりも大きく、これらが一括して反転としてカウントできます。

mergeSort 関数

入力:対象の配列と作業用配列、および配列の左端・右端のインデックス
出力:ソート完了時の転倒数

Begin
    count := 0
    if right > left then
        mid := (right + left) / 2
        count := mergeSort(array, tempArray, left, mid)
        count := count + mergeSort(array, tempArray, mid+1, right)
        count := count + merge(array, tempArray, left, mid+1, right)
    return count
End

配列を半分ずつ再帰的に分割しながら、左半分・右半分それぞれの転倒数を求め、最後にマージ時に発生する「左右間の転倒」を合計することで、全体の転倒数が得られます。

C++ による実装例

#include <iostream>
using namespace std;

int merge(int arr[], int temp[], int left, int mid, int right) {
    int i, j, k;
    int count = 0;

    i = left;    // 左側配列の位置を指す
    j = mid;     // 右側配列の位置を指す
    k = left;    // マージ後の配列の位置を指す

    while ((i <= mid - 1) && (j <= right)) {
        if (arr[i] <= arr[j]) {   // 左側の要素が右側以下の場合
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
            count += (mid - i);   // 発生した反転の数を加算
        }
    }

    while (i <= mid - 1)      // 左側に残り要素があれば追加
        temp[k++] = arr[i++];

    while (j <= right)        // 右側に残り要素があれば追加
        temp[k++] = arr[j++];

    for (i = left; i <= right; i++)
        arr[i] = temp[i];     // 作業用配列を元の配列へ書き戻す

    return count;
}

int mergeSort(int arr[], int temp[], int left, int right) {
    int mid, count = 0;

    if (right > left) {
        mid = (right + left) / 2;                      // 配列の中央インデックスを求める
        count  = mergeSort(arr, temp, left, mid);      // 左側の部分配列をソート
        count += mergeSort(arr, temp, mid + 1, right); // 右側の部分配列をソート

        count += merge(arr, temp, left, mid + 1, right); // 2つの部分配列をマージ
    }
    return count;
}

int arrInversion(int arr[], int n) {
    int temp[n];
    return mergeSort(arr, temp, 0, n - 1);
}

int main() {
    int arr[] = {1, 5, 6, 4, 20};
    int n = 5;
    cout << "Number of inversions are " << arrInversion(arr, n);
}

実行結果

Number of inversions are 2

計算量

  • 時間計算量:O(n log n) ― マージソートと同様に、分割が log n 段階、各段階のマージで O(n) かかるためです。
  • 空間計算量:O(n) ― マージ用の作業用配列が必要になります。

総当たり法(O(n²))と比べて大幅に高速であり、大規模なデータセットでも実用的に転倒数を求められます。

  1. マージソートを使って配列の転倒数(反転数)を数えるC/C++プログラム

    転倒数(Inversion Count)とは?与えられた配列をソートする際に発生する反転(転倒)の回数を「転倒数(Inversion Count)」と呼びます。転倒数を求める問題は古典的なアルゴリズム問題の一つで、マージソート(Merge Sort)のアルゴリズムを応用することで効率的に解くことができます。この問題では、各要素について「自分より左側にあり、かつ自分より大きな値を持つ要素」の数をすべて数え上げ、その合計を出力します。この処理は、マージソートのマージ(merge)関数の中で実装されます。理解を深めるために、マージ処理で扱う2つの部分配列を例に考えてみましょう。配列の転倒数の定義配列

  2. Pythonで配列の反転数(転倒数)をカウントする方法

    はじめに この記事では、配列内の反転(インバージョン)をカウントする問題とその解決策について詳しく解説します。 問題定義 問題: リストが与えられたとき、その中に含まれる反転の数をカウントして表示します。 反転数とは、配列を昇順にソートされた状態にするために必要な入れ替え(スワップ)の回数を表す指標です。具体的には、i < j かつ arr[i] > arr[j] を満たす要素のペア(i, j)の総数として定義されます。 実装例 # 反転数をカウントする関数 def InvCount(arr, n): inv_count = 0 for i in range(n