0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

《アルゴリズムを学ぶ》22.マージソート

Posted at

Overview

マージソート(Merge Sort) は、N個の数$A_1, A_2, A_3, ..., A_N$を計算量$O(log N)$で小さい順に整列できる。大規模なデータにも適しており、かつ安定ソートであり、比較ソートの中でもシンプルかつ強力なアルゴリズムである。
1.マージソートの基本概念
2.マージソートのアルゴリズム
3.マージソートの応用
4.典型問題を解いてみる

1. マージソートの基本概念

マージソートは、以下の 2 つのステップで構成される:

  • 配列を半分に分割し、再帰的にマージソートを適用
  • 分割された 2 つの配列をマージ(結合)する

(1) 分割統治法とは?

  • 「大きな問題を小さな問題に分割し、それらを解いて最終的に結合する」
  • マージソートでは、配列を再帰的に分割し、最後に統合(マージ)する
  • 典型的な O(N log N) のアルゴリズム

マージソートのポイント

  • 計算量:O(N log N)(分割 O(log N) × マージ O(N))
  • 安定ソート(同じ値の要素の順序が保持される)
  • 追加のメモリ O(N) が必要(配列をコピーするため)
  • データの順番に依存しない(最悪 O(N log N) を保証)

2. マージソートのアルゴリズム

(1) アルゴリズム

  1. 空の列Sを用意する
  2. 配列を中央で 2 つに分割
  3. それぞれを再帰的にマージソートで整列
  4. 2 つのリストの先頭を比較し、小さい方を新しいリストに追加
  5. すべての要素がマージされるまで繰り返す

例:
列[13, 75, 34, 50, 20, 28, 62, 11]
↓(1の操作後)
列A[13, 75, 34, 50] 列B[20, 28, 62, 11]
↓(2の操作後)
列A[13, 34, 50, 75] 列B[11, 20, 28, 62]
↓(3の操作でAの先頭13とBの先頭11を比較。小さい方の値11を列Sに移す)
列A[13, 34, 50, 75] 列B[20, 28, 62] 列S[11]
↓(4を満たすまで3の操作を繰り返す)
列A[34, 50, 75] 列B[20, 28, 62] 列S[11, 13]
↓(繰り返し)
列A[34, 50, 75] 列B[28, 62] 列S[11, 13, 20]
↓(繰り返し)
列A[34, 50, 75] 列B[62] 列S[11, 13, 20, 28]
↓(繰り返し)
列A[50, 75] 列B[62] 列S[11, 13, 20, 28, 34]
↓(繰り返し)
列A[75] 列B[62] 列S[11, 13, 20, 28, 34, 50]
↓(繰り返し)
列A[75] 列B[] 列S[11, 13, 20, 28, 34, 50, 62]

列A[] 列B[] 列S[11, 13, 20, 28, 34, 50, 62, 75]
処理終了

(2) マージソートの実装

基本的なマージソート

def merge_sort(arr):
    if len(arr) <= 1:  # 配列が 1 要素ならそのまま返す(再帰の終了条件)
        return arr

    mid = len(arr) // 2  # 配列を半分に分割
    left_half = merge_sort(arr[:mid])  # 左側をソート
    right_half = merge_sort(arr[mid:])  # 右側をソート

    return merge(left_half, right_half)  # マージして返す

def merge(left, right):
    sorted_arr = []
    i = j = 0

    # 2つのリストをマージ
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            sorted_arr.append(left[i])
            i += 1
        else:
            sorted_arr.append(right[j])
            j += 1

    # 残った要素を追加
    sorted_arr.extend(left[i:])
    sorted_arr.extend(right[j:])

    return sorted_arr

# 使用例
arr = [38, 27, 43, 3, 9, 82, 10]
sorted_arr = merge_sort(arr)
print(sorted_arr)  # [3, 9, 10, 27, 38, 43, 82]
  • 計算量: O(N log N)
  • メモリ使用量: O(N)(新しい配列を作るため)

3. マージソートの応用

(1) インプレース(メモリを追加せず)でのマージ

メモリ使用量を抑えられる(O(1) に近づく)

def merge_inplace(arr, left, mid, right):
    temp = arr[left:right+1]
    i = left
    j = mid + 1
    k = left

    while i <= mid and j <= right:
        if temp[i - left] <= temp[j - left]:
            arr[k] = temp[i - left]
            i += 1
        else:
            arr[k] = temp[j - left]
            j += 1
        k += 1

    while i <= mid:
        arr[k] = temp[i - left]
        i += 1
        k += 1

(2) K番目に小さい要素を求める

K番目に小さい値を求める問題に応用できる

def kth_smallest(arr, k):
    sorted_arr = merge_sort(arr)
    return sorted_arr[k - 1]

print(kth_smallest([3, 2, 1, 5, 4], 3))  # 3番目に小さい値 → 3

4. 典型問題を解いてみる

例題1. マージソートの実装

問題: N 個の整数をマージソートでソートせよ。

参考: ABC315 B - Merge Sort

回答 
N = int(input())
arr = list(map(int, input().split()))

sorted_arr = merge_sort(arr) # 「基本的なマージソート」の記載は省略する
print(*sorted_arr)

例題2. K 番目に小さい要素を求める

問題: N 個の整数 A から K 番目に小さい値を求めよ。

参考: ABC318 B - Kth Smallest Element

回答 
N, K = map(int, input().split())
A = list(map(int, input().split()))

sorted_A = merge_sort(A) # 「基本的なマージソート」の記載は省略する
print(sorted_A[K - 1])

例題3. 数列の反転数を求める

問題: N 個の整数の 転倒数(逆順ペアの数) を求めよ。

参考: ABC319 C - Inversion Count

回答 
def merge_count(arr):
    if len(arr) <= 1:
        return arr, 0

    mid = len(arr) // 2
    left, left_inv = merge_count(arr[:mid])
    right, right_inv = merge_count(arr[mid:])
    merged, merge_inv = merge_and_count(left, right)

    return merged, left_inv + right_inv + merge_inv

def merge_and_count(left, right):
    sorted_arr = []
    i = j = inv_count = 0

    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            sorted_arr.append(left[i])
            i += 1
        else:
            sorted_arr.append(right[j])
            j += 1
            inv_count += len(left) - i

    sorted_arr.extend(left[i:])
    sorted_arr.extend(right[j:])
    
    return sorted_arr, inv_count

N = int(input())
arr = list(map(int, input().split()))
_, inv_count = merge_count(arr)
print(inv_count)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?