サイトマップ / C言語講座>出入り口>総目次>目次:ソート>ヒープソート
/* 今日は2分木 ( ヒープ ) というデータ構造のうち、半順序木を使って行う、高速なソートのアルゴリズムであるヒープソートについて学びます。
2分木というデータ構造については、既に、学んでいますが、復習します。下記の node[ ] という配列の添字 0 は使いませんので、それは無視して下さい。数字を斜めの線で結んでいませんが、1と 2、1 と 3、2 と 4、2 と 5、3 と 6、3 と 7 が、線で結ばれているものとします。
int node[ ] = {0, 1, 2, 3, 4, 5, 6, 7, };
このようなデータ構造を、枝が一度に二つに分かれる木に見立てて、2分木といいます。一つ下の二つの要素を子、一つ上の要素を親といいます。一番上の要素を根といいます。つまり、2分木は逆さに生えている木です。
2分木では、親の値は子の値より小さく、左の子の値は右の子の値より小さいという規則があります。
数字の並び方を良く見て下さい。この配列は配列の値と添字の値が一致しています。親の値を 2 倍すると、左下の子になります。親の値の 2 倍に1 を足すと右下の子になります。従って、node[n] の左下の子は node[2n] となり、右下の子は node[2n + 1] になります。半順序木では、単に、親の値は子の値より小さいというだけで、子同志の値の大きさは問題にしません。
今回は、後に、ヒープソートを行う都合上、降順の半順序木を作ります。下記に例を示します。
int a[ ] = {0, 86 58 19 51 49 10 15 };
ソースプログラムでは、0 から 99 までの整数の乱数を発生させ、それを int 型の配列 a[ ] にしまいます。次いで、半順序木の構造にします。半順序木の根はその配列の最大値なので取り出します。その位置に、配列の末尾の要素を置きます。半順序木の構造が壊れたので、再び半順序木を構成します。
上記の処理を要素がなくなるまで続ければ、ソートが完了します。具体例を下記に示します。
初期状態 58 15 51 10 19 86 49
最下段の 10 19 86 49 は子がないので、半順序木になっています。そこで、51 を半順序木の根とみなして、子と比較します。比較対象は子の大きい方です。51 と 86 を比較すると、86 の方が大きいので入れ替えます。
58 15 86 10 19 51 49
次いで、86 と 15 の大きい方と、その親と比較します。86 の方が大きいので入れ替えます。
86 15 58 10 19 51 49
次に、15 を根とみなして、子の大きい方の要素と比較します。19 の方が大きいので、入れ替えます。
86 19 58 10 15 51 49
次に親と比較します。親の方が大きいので、入れ替えはしません。これで、半順序木になりました。ここまでが、ソートの準備です。
半順序木の根はそれを構成する要素の最大値です。根の 86 を取り出します。86 のあった位置に、配列の添字の一番大きい要素である 49 を置きます。取り出した 86 は 49 のあった位置におきます。
49 86 19 58 10 15 51
半順序木の構造が壊れたので、半順序木に再構成します。49 の子の大きい方と比較すると、58 の方が大きいので交換します。
58 86 19 49 10 15 51
49 には子が一つしかありません。49 と 51 を比較すると、51 の方が大きいので交換します。
58 86 19 51 10 15 49
これで半順序木になりました。半順序木の根は最大値なので取り出して、その位置に配列の添字の一番大きい要素である 49 を置きます。取り出した 58 は 49 のあった位置に置きます。
49 58 86 19 51 10 15
同様な操作を繰り返すと、順次最大値から取り出して半順序木の末尾に置くので、配列を前から見ると、昇順にソートしたことになります。 */
#include <stdio.h> #include <stdlib.h> /* rand( ) で必要 */ #define NUM_DATA 20 int a[NUM_DATA + 1]; void SetData(void); void Hpsort(int a[ ], int n); void DownHeap(int a[ ], int top, int bottom); void Swap(int a[ ], int i, int j); void ShowData(int a[ ], int n); void main(void); /* 0 から99 の範囲の整数の乱数をNUM_DATA個作って a[ ] にしまう */ void SetData(void) { int i; for (i = 0; i <= NUM_DATA; i++) a[i] = rand( ) % 100; } /* ヒープソートを行う */ void Hpsort(int a[ ], int n) { int leaf, root; leaf = n; /* 初期値は末尾の要素 */ root = n/2; /* 初期値はその親 */ while (root > 0 ) { /* 半順序木を構成 */ DownHeap(a, leaf, root); root--; } printf("半順序木を構成:\n"); ShowData(a, NUM_DATA); /* できあがった半順序木を表示 */ while(leaf > 0) { Swap(a, 1, leaf); /* 半順序木の根と末尾の要素を交換 */ leaf--; /* 末尾の要素を半順序木から外す */ DownHeap(a, leaf, 1); /* 半順序木を再構成する */ } } /* 半順序木 ( ヒープ ) を構成する */ void DownHeap(int a[ ], int leaf, int root) { int i; i = root * 2; while (i <= leaf) { if (i < leaf && a[i + 1] > a[i]) /* a[i] と a[i + 1] の大きい方と */ i++; if (a[root] >= a[i]) /* a[root] と比較 */ break; /* 子の方が大きければ */ Swap(a, root, i); /* 交換 */ root = i; /* 更にその子についても調べる */ i = root * 2; } } /* 配列の要素を交換する */ void Swap(int a[ ], int i, int j) { int temp; temp = a[i]; a[i] = a[j]; a[j] = temp; } /* 配列の要素を表示する */ void ShowData(int a[ ], int n) { int i; for (i = 1; i <= n; i++) { printf("a[%2d] = %2d ", i, a[i]); if(!(i % 5)) /* 要素を5つ表示したら改行 */ printf("\n"); } printf("\n"); } void main(void) { SetData( ); printf("ソート前:\n"); ShowData(a, NUM_DATA); Hpsort(a, NUM_DATA); printf("ソート後:\n"); ShowData(a, NUM_DATA); } |
/* プログラムを実行すると、乱数の配列と、構成された半順序木の配列と、ソート後の配列が表示されます。配列の添字から親子関係を確認し、半順序木になっていることを確認して下さい。 */
/* (C) 2000- YFプロ. All Rights Reserved. */ 提供:C言語講座−それ自体コンパイルできる教材を使った講座です−