C++でヒープソートアルゴリズムを使って10個の要素の配列をソートする方法
ヒープソートは、二分ヒープ(バイナリヒープ)と呼ばれるデータ構造に基づいたソートアルゴリズムです。二分ヒープには2種類あります。最大ヒープでは各親ノードの子ノードが親の値以下になり、最小ヒープでは各親ノードの子ノードが親の値以上になるように構成されます。本記事では、最大ヒープを利用したヒープソートをC++で実装し、10個の要素を持つ配列を昇順に並べ替える手順を詳しく解説します。
ヒープソートの手順(具体例)
まず、ソート前の10個の要素からなる元の配列は次の通りです。
| 20 | 7 | 1 | 54 | 10 | 15 | 90 | 23 | 77 | 25 |
この配列に対してmax-heapify操作を適用し、二分最大ヒープを構築します。配列として表現された最大ヒープは以下のようになります。
| 90 | 77 | 20 | 54 | 25 | 15 | 1 | 23 | 7 | 10 |
次に、最大ヒープのルート要素(最大値)を取り出して配列の末尾に配置します。その後、残りの要素に対して再びmax-heapifyを呼び出し、ヒープ構造を維持します。この操作を繰り返すことで、最終的に次のようなソート済み配列が得られます。
| 1 | 7 | 10 | 15 | 20 | 23 | 25 | 54 | 77 | 90 |
C++プログラム例
ヒープソートアルゴリズムを使用して10個の要素の配列をソートする完全なプログラムは以下の通りです。
#include<iostream>
using namespace std;
void heapify(int arr[], int n, int i) {
int temp;
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < n && arr[l] > arr[largest])
largest = l;
if (r < n && arr[r] > arr[largest])
largest = r;
if (largest != i) {
temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
int temp;
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
for (int i = n - 1; i >= 0; i--) {
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
int main() {
int arr[] = { 20, 7, 1, 54, 10, 15, 90, 23, 77, 25};
int n = 10;
int i;
cout<<"Given array is: "<<endl;
for (i = 0; i < n; i++)
cout<<arr[i]<<" ";
cout<<endl;
heapSort(arr, n);
printf("\nSorted array is: \n");
for (i = 0; i < n; ++i)
cout<<arr[i]<<" ";
}
出力結果
Given array is: 20 7 1 54 10 15 90 23 77 25 Sorted array is: 1 7 10 15 20 23 25 54 77 90
プログラムの詳細解説
heapify()関数 ― 要素をヒープ構造に変換する
上記プログラムでは、heapify()関数が要素をヒープ構造へ変換する役割を担います。この関数は再帰的に動作し、呼び出し時のインデックスiを起点として、その要素を根とする部分木を最大ヒープに構成します。該当するコードは以下の通りです。
void heapify(int arr[], int n, int i) {
int temp;
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < n && arr[l] > arr[largest])
largest = l;
if (r < n && arr[r] > arr[largest])
largest = r;
if (largest != i) {
temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
具体的には、インデックスiの左右の子(それぞれ2*i+1と2*i+2)と比較し、最も大きい要素を特定します。最大値が親自身でない場合は値を交換し、交換先の位置に対して再帰的にheapify()を呼び出すことで、下方向へもヒープ条件を伝播させます。
heapSort()関数 ― 配列をヒープソートで並べ替える
heapSort()関数は、ヒープソートの本体であり、配列の要素を実際に並べ替えます。まず、葉ノード以外のノード(インデックスn/2−1から0まで)に対してheapify()を呼び出し、配列全体を二分最大ヒープへ変換します。この処理は以下のコードで行われます。
for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i);
続いて、forループの各反復においてルート要素(現時点での最大値)を取り出し、配列の末尾に移動します。その後、heapify()を呼び出して残りの要素が最大ヒープの条件を満たすように整えます。この操作をすべての要素に対して繰り返すことで、昇順にソートされた配列が完成します。該当するコードは以下の通りです。
for (int i = n - 1; i >= 0; i--) {
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
main()関数 ― ソートの実行と結果表示
main()関数では、まず元の配列の内容を画面に出力します。その後、heapSort()関数を呼び出して配列をソートします。該当するコードスニペットは以下の通りです。
cout<<"Given array is: "<<endl; for (i = 0; i < n; i++) cout<<arr[i]<<" "; cout<<endl; heapSort(arr, n);
最後に、ソート後の配列を出力して処理完了となります。以下にそのコードを示します。
printf("\nSorted array is: \n");
for (i = 0; i < n; ++i)
cout<<arr[i]<<" ";
計算量について
ヒープソートの時間計算量は、最良・平均・最悪のいずれの場合でもO(n log n)であり、入力データの初期状態に依存せず安定した性能を発揮します。また、追加のメモリ領域をほとんど必要としないインプレース(in-place)ソートである点も大きな特徴です。ただし、クイックソートと比べると定数倍が大きく、キャッシュ効率が劣るため、実用上は状況に応じて使い分けられます。
-
マージソートを使って配列の転倒数(反転数)を数えるC/C++プログラム
転倒数(Inversion Count)とは?与えられた配列をソートする際に発生する反転(転倒)の回数を「転倒数(Inversion Count)」と呼びます。転倒数を求める問題は古典的なアルゴリズム問題の一つで、マージソート(Merge Sort)のアルゴリズムを応用することで効率的に解くことができます。この問題では、各要素について「自分より左側にあり、かつ自分より大きな値を持つ要素」の数をすべて数え上げ、その合計を出力します。この処理は、マージソートのマージ(merge)関数の中で実装されます。理解を深めるために、マージ処理で扱う2つの部分配列を例に考えてみましょう。配列の転倒数の定義配列
-
配列の全要素を乗算するC++プログラムの解説
整数型の要素を持つ配列が与えられたとき、配列内のすべての要素を掛け合わせ、その積を表示することを考えます。本記事では、この問題をC++(C言語スタイルのコード)で解く方法を、アプローチ、アルゴリズム、サンプルコード、実行結果まで順を追って解説します。 例 入力: arr[]={1,2,3,4,5,6,7} 出力: 1 x 2 x 3 x 4 x 5 x 6 x 7 = 5040 入力: arr[]={3, 4, 6, 2, 7, 8, 4} 出力: 3 x 4 x 6 x 2 x 7 x 8 x 4 = 32256 解き方のアプローチ この問題は、累積用の一時変数を用意し、配列の要素を先頭