サイトマップ / 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言語講座−それ自体コンパイルできる教材を使った講座です−

青い直線

サイトマップ / C言語講座出入り口総目次目次:ソート>ヒープソート