ヒープソートは、Python で配列とリストをソートするための強力なアルゴリズムです。非常に高速で、マージ ソートやクイック ソートのような追加のスペースを必要としないため、人気があります。
ヒープソートの時間計算量は O(n*log(n)) です。
ヒープ ソートはインプレース アルゴリズムであり、データの中間状態を保存するためのデータ構造を作成しません。代わりに、元の配列が変更されます。
したがって、データが非常に大きい場合、これによりスペースが大幅に節約されます。
このアルゴリズムの唯一の欠点は、非常に不安定であることです。配列内の複数の要素が異なるインデックスで同じ値を持つ場合、並べ替え時にそれらの位置が変わります。
ヒープ ソート アルゴリズムは、最小ヒープまたは最大ヒープを再帰的に作成し、ルート ノードを取得し、それを配列内の最初の未ソートのインデックスに配置し、最後のヒープ要素をルート ノードに変換することによって機能します。
このプロセスは、ヒープ内に 1 つのノードだけが残るまで再帰的に繰り返されます。最後に、最後のヒープ要素が配列の最後のインデックスに配置されます。
考えてみると、このプロセスは、最大値または最小値を取得し、それらを並べ替えられた配列の先頭に置くという点で、選択並べ替えアルゴリズムに似ています。
ヒープソートアルゴリズムを実装する
まず、元の配列、配列の長さ、親ノードのインデックスを受け取る build_heap() 関数の実装を見てみましょう。ここで、配列を見ると、最後の親ノードのインデックスは配列内の (n//2 - 1) にあります。
同様に、その特定の親の左側の子のインデックスは 2parent_index + 1 で、右側の子のインデックスは 2parent_index + 2 です。
この例では、最大ヒープを作成しようとしています。これは、各親ノードがその子ノードよりも大きい必要があることを意味します。
これを行うには、最後の親ノードから開始してヒープのルートまで移動します。最小ヒープを作成したい場合は、すべての親ノードをその子ノードよりも小さくする必要があります。
build_heap() 関数は、左または右の子ノードが現在の親ノードより大きいかどうかを確認し、最大のノードを親ノードと交換します。
ヒープ内のすべての親ノードに対して前のプロセスを段階的に繰り返すため、この関数はそれ自体を再帰的に呼び出します。
次のコード スニペットは、Python で前述した build_heap() 関数の動作する実装を示しています。
def build_heap(arr, length, parent_index):
largest_index = parent_index
left_index = 2 * parent_index + 1
right_index = 2 * parent_index + 2
if left_index < length and arr[parent_index] < arr[left_index]:
largest_index = left_index
if right_index < length and arr[largest_index] < arr[right_index]:
largest_index = right_index
if largest_index != parent_index:
arr[parent_index],arr[largest_index] = arr[largest_index],arr[parent_index]
build_heap(arr, length, largest_index)
これで、配列内の最大値を取得し、それをヒープのルートに置く関数ができました。ソートされていない配列を取得し、build_heap() 関数を呼び出してヒープから要素を抽出する関数が必要です。
次のコード スニペットは、Python での heapSort() 関数の実装を示しています。
def heapSort(arr):
length = len(arr)
for parent_index in range(length // 2 - 1, -1, -1):
build_heap(arr, length, parent_index)
for element_index in range(length-1, 0, -1):
arr[element_index], arr[0] = arr[0], arr[element_index]
build_heap(arr, element_index, 0)
配列内の各親ノードの build_heap() 関数をステップ実行します。開始インデックスとして length//2-1 を指定し、終了インデックスとして -1 を指定し、ステップ サイズは -1 であることに注意してください。
これは、最後の親ノードから開始して、ルート ノードに到達するまでインデックスを徐々に減らしていくことを意味します。
2 番目のループ for は、ヒープから要素をフェッチします。また、配列の最後のインデックスから開始し、最初のインデックスで停止します。
このループで配列の最初と最後の要素を交換し、ルート インデックスとして 0 を渡して、新しくソートされた配列に対して build_heap() 関数を実行します。
これで、Python でヒープ ソートを実装するプログラムを作成しました。次に、配列を並べ替えて上記のコードをテストします。
#Python小白学习交流群:153708845
arr = [5, 3, 4, 2, 1, 6]
heapSort(arr)
print("Sorted array :", arr)
出力:
Sorted array : [1, 2, 3, 4, 5, 6]
ご覧のとおり、配列は完全にソートされています。これは、コードが正常に動作することを意味します。
降順で並べ替えたい場合は、上で実装した最大ヒープの代わりに最小ヒープを作成できます。
min-heap についてはこのチュートリアルの冒頭で説明したため、この記事では min-heap については説明しません。
私たちのプログラムは次のように動作します。以下のブロックは、コード実行の各段階での配列の状態を示しています。
Original Array [5, 3, 4, 2, 1, 6] # input array
Building Heap [5, 3, 6, 2, 1, 4] # after build_heap() pass 1
Building Heap [5, 3, 6, 2, 1, 4] # after build_heap() pass 2
Building Heap [6, 3, 5, 2, 1, 4] # after build_heap() pass 3
Extracting Elements [6, 3, 5, 2, 1, 4] # before swapping and build_heap pass 1
Extracting Elements [5, 3, 4, 2, 1, 6] # before swapping and build_heap pass 2
Extracting Elements [4, 3, 1, 2, 5, 6] # before swapping and build_heap pass 3
Extracting Elements [3, 2, 1, 4, 5, 6] # before swapping and build_heap pass 4
Extracting Elements [2, 1, 3, 4, 5, 6] # before swapping and build_heap pass 5
Sorted array : [1, 2, 3, 4, 5, 6] # after swapping and build_heap pass 5
ヒープ内に親が 3 つしかないため、build_heap() 関数は 3 回実行されます。
その後、要素抽出フェーズで最初の要素と最後の要素を交換し、build_heap() 関数を再度実行します。このプロセスは長さ - 1 に対して行われ、配列はソートされます。