LoginSignup
3
2

More than 3 years have passed since last update.

マージソートの処理を理解する。流れを追って細かく分解。

Last updated at Posted at 2020-10-16

マージソートとは?

ソートの一種。安定したソート。分割統治法(divide-and-conquer)を使用している。

  • ソート対象の配列を2分割する(divide)
  • 分割したものをさらに2分割するのを要素が1つになるまで繰り返す(divide)
  • それぞれの先頭要素同士を比較して、小さい数値を前方にしてマージ(solve, merge)
  • ソート済み同士の要素の最初の数値を比較して小さい方から並べてマージ(conquer, merge)

image.png

参考:鹿児島大学HP


マージソートのコード

#最少単位に分解後にmergeを行う。この処理を繰り返す
def merge_sort(arr):
  #要素が1つか0のときはその配列をそのまま返す。(比較対象がなくソートの必要性がないため)
  if len(arr) <= 1:
    return arr

  #2分割する。スライスで切り分けるため、整数部のみ抽出する
  mid = len(arr)//2

  #元の配列を2分割
  arr1 = arr[:mid]
  arr2 = arr[mid:]

  #最少単位になるまで2分割を続ける
  #returnになった時点で再帰が終了する
  arr1 = merge_sort(arr1)
  arr2 = merge_sort(arr2)

  return merge1(arr1, arr2)


#2つの要素を比較して、小さい方を先頭に並べる
def merge1(arr1,arr2):
  #マージ結果を入れる配列
  merged = []
  l_i, r_i =0,0

  while l_i < len(arr1) and r_i < len(arr2):

        if arr1[l_i] <= arr2[r_i]:
            merged.append(arr1[l_i])
            l_i += 1
        else:
            merged.append(arr2[r_i])
            r_i += 1

  if l_i < len(arr1):
      merged.extend(arr1[l_i:])
  if r_i < len(arr2):
      merged.extend(arr2[r_i:])
  return merged
確認
list=[7,4,3,5,6,1,2]
merge_sort(list)

#[1, 2, 3, 4, 5, 6, 7]


マージソートの詳細

2つの関数が走っているためわかりにくいので分解してみる。

  1. 最少単位を作る関数(div_arr)
  2. 比較して小さい方を配列に追加していく関数(merge)

最少単位を作る関数

まずは、各要素を全て分割する関数を考える

def div_arr(arr):
  #要素が1つか0のときはその配列をそのまま返す。(比較対象がなくソートの必要性がないため)
  if len(arr) <= 1:
    return print(arr)

  #2分割する。スライスで切り分けるため、整数部のみ抽出する
  mid = len(arr)//2

  #元の配列を2分割
  arr1 = arr[:mid]
  arr2 = arr[mid:]

  #最少単位になるまで2分割を続ける
  #returnになった時点で再帰が終了する
  arr1 = div_arr(arr1)
  arr2 = div_arr(arr2)
確認
list=[7,4,3,5,6,1,2]
div_arr(list)

[7]
[4]
[3]
[5]
[6]
[1]
[2]

ポイントはarr1とarr2の2つを用意していること。
関数に投入された値は2分割され、後方(arr2)は常に処理がストックされる。

このため、arr1がreturnで終了したあとも、arr2がストックされた分だけ処理され続ける。

return arrで配列が出力されることを想定していたが、何も表示されたないため、return print(arr)にしている。(なぜ表示されないか不明、、)


(参考)前列だけで再帰処理した場合

def div_arr(arr):
  if len(arr) <= 1:
    return print(arr)

  mid = len(arr)//2

  arr1 = arr[:mid]

  arr1 = div_arr(arr1)
list=[7,4,3,5,6,1,2]
div_arr(list)

[7]

arr1しか処理しないため、[7]だけが出力される。
※[7],[4],[3]とならない。再帰処理を繰り返す中で、[4],[3]はarr2に割り振られるため。


比較して小さい方を配列に追加していく関数

#2つの要素を比較して、小さい方を先頭に並べる
def merge1(arr1,arr2):
  #マージ結果を入れる配列
  #初期値0だが、mergeを繰り返す度に中身が増えていく
  merged = []

  #対比する要素の配列番号初期値
  l_i, r_i =0,0

  #ループ終了条件。どちらかの配列を全て比較仕切ったら終了
  while l_i < len(arr1) and r_i < len(arr2):

    #前方の要素の方が小さい場合
    #同じ場合は先方を優先するためイコールをつける
        if arr1[l_i] <= arr2[r_i]:
            merged.append(arr1[l_i])
            #同じ要素を再度検証しないよう配列番号に1をたす。
            l_i += 1
        else:
            merged.append(arr2[r_i])
            r_i += 1

  # while文が終了したら、追加されていない方を丸ごと答えに追加する。
  # 各配列は既に昇順にソート済みであるため、そのまま追加している。
  if l_i < len(arr1):
      merged.extend(arr1[l_i:])
  if r_i < len(arr2):
      merged.extend(arr2[r_i:])
  return merged
確認
a=[2,3]
b=[1,8,9]

merge1(a,b)

#[1, 2, 3, 8, 9]


全体の処理の流れを可視化

上記2つの処理を踏まえて、merge_sortの処理を見てみる。

#ソートする配列
arr=[7,4,3,5,6]

#1回目の処理
arr1=[7,4]
arr2=[3,5,6]

#arr1を再帰
arr1`=[7]
arr2`=[4]

##arr1の解##
merge`=[4,7]


#arr2を再帰
arr1``=[3]
arr2``=[5,6]

#arr2``を再帰
arr1```=[5]
arr2```=[6]

##arr2``の解##
merge``=[5,6]

##arr2の解##
merge```=[3,5,6]


##いよいよarrの解##
merge=[3,4,5,6,7]

##arrの解はarr1とarr2をmergeしたもの
#arr1の解 merge`=[4,7]
#arr2の解 merge```=[3,5,6]

、、ややこしい。最後の3行が肝心

  arr1 = merge_sort(arr1)
  arr2 = merge_sort(arr2)

  return merge1(arr1, arr2)

merge_sortの度にmergedした値(merge1の結果)がarr1とarr2に入る。

それがさらにmergeされていく。

これで最少単位まで分解した要素が再度組み上がる。



これ考えた人すげぇ、、そしてこれを理解している人もすげぇ

3
2
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
3
2