Last Updated: 2/6/2024, 5:44:57 AM

# ヒープソート

# 概要

ここで神のごとくわかりやすい、 わくわくアカデミーの動画を引用させていただきます。 動画を見ると、だいたいの雰囲気が掴みやすいかなと思います。

# コード

# 簡単なもの

標準ライブラリ heapq (opens new window) を使えば、とても簡単に実装できます。 heapq については 二分ヒープ で解説させていただきました。

from heapq import heappush
from heapq import heappop

def linked_heap_sort(lst):
    heap = []
    while lst:
        heappush(heap, lst.pop())
    while heap:
        lst.append(heappop(heap))


lst = [3333, 5123, 9981, 1243, 7412]
linked_heap_sort(lst)
print(lst)
# [1243, 3333, 5123, 7412, 9981]

# 難しいもの

heapq のコード heapq.py (opens new window) を コピペして一部書き換えています。

def heap_sort(lst):
    # 1) ヒープ heap を作成
    heapify(lst)
    
    # 2) 先頭を取り出し末尾と取り替え
    index = len(lst) - 1
    while index:
        lst[0], lst[index] = lst[index], lst[0]
        _siftup(lst, 0, index)
        index = index - 1

    # 3) 逆さまなので反転
    lst.reverse()


#
# heapq.py
#
def _siftdown(heap, startpos, pos):
    newitem = heap[pos]
    while pos > startpos:
        parentpos = (pos - 1) >> 1
        parent = heap[parentpos]
        if newitem < parent:
            heap[pos] = parent
            pos = parentpos
            continue
        break
    heap[pos] = newitem


# heapq._siftup を
# 先頭の2行だけを書き換えました。
#
# - def _siftup(heap, pos):
# -     endpos = len(heap)
# + def _siftup(heap, pos, endpos):
# +     # endpos = len(heap)
#
def _siftup(heap, pos, endpos):
    startpos = pos
    newitem = heap[pos]
    childpos = 2 * pos + 1
    while childpos < endpos:
        rightpos = childpos + 1
        if rightpos < endpos and not heap[childpos] < heap[rightpos]:
            childpos = rightpos
        heap[pos] = heap[childpos]
        pos = childpos
        childpos = 2 * pos + 1
    heap[pos] = newitem
    _siftdown(heap, startpos, pos)


# heapq._heapify を
# 書き換え
#
# - _siftup(x, i)
# + _siftup(x, i, n)
#
def heapify(x):
    n = len(x)
    for i in reversed(range(n // 2)):
        _siftup(x, i, n)


lst = [3333, 5123, 9981, 1243, 7412]
heap_sort(lst)
print(lst)
# [1243, 3333, 5123, 7412, 9981]

# 途中結果を表示する。

ヒープとは、ざっくり言えば上より下が大きい木です。 クイックソート、マージソートは難しい方の実装の途中結果を表示しましたが、 ここでは簡単な方の実装の途中結果を表示します。

heapify の動作を表示してしまうと、ごちゃごちゃしてしまうので避けました。 ここでは heappush で詰め込んで heappop で取り出している、 ということだけなんとなく伝わればと思っています。

heapify, heappush, heappop の実装と動作については 二分ヒープ でご紹介させていただきました。

ヒープソートについては、表示が少し大掛かりになるので、 表示させてみる場合は GitHub からのダウンロードをお願いいたします。

git clone https://github.com/niconico25/sort
cd sort

# ヒープを表示するために使います
git clone https://github.com/niconico25/tree
python3 linked_heap.py

$ python3 linked_heap.py 
# リストの末尾から1つずつ要素を取り出し
# ヒープを作ります。
43 28 96 31  9 50  8




    前半のこの箇所を表示しています。
    while lst:
        heappush(heap, lst.pop())

[43, 28, 96, 31, 9, 50]
08

[43, 28, 96, 31, 9]
  08  
50    

[43, 28, 96, 31]
  08  
50  09

[43, 28, 96]
      08      
  31      09  
50            

[43, 28]
      08      
  31      09  
50  96        

[43]
      08      
  31      09  
50  96  28    

[]
      08      
  31      09  
50  96  28  43

#
# ヒープが完成しました!
#   どこの要素を見ても、
#   上より下が大きいです。
#




    ここからは
    後半のこの箇所を表示しています。
    while heap:
        lst.append(heappop(heap))

# あとはヒープから
# 1つずつ取り出していけば
# ソートの完成です。
[8]
      09      
  31      28  
50  96  43    

[8, 9]
      28      
  31      43  
50  96        

[8, 9, 28]
      31      
  50      43  
96            

[8, 9, 28, 31]
  43  
50  96

[8, 9, 28, 31, 43]
  50  
96    

[8, 9, 28, 31, 43, 50]
96

[8, 9, 28, 31, 43, 50, 96]



 8  9 28 31 43 50 96
$

# おわりに

最後にすこしだけメモリ使用量に触れました。 各アルゴリズムのメモリ使用量の一覧は Wikipedia にあります。