Index

HOME > プログラムTOP > C++



C++の壺・ソート篇

 C++の基本テクニックのTipsです。
 ソート篇では、要素の並べ替えのしくみと基本的なソートの手法を扱います。
  [index] ・単純なソート / ・クイックソート / ・qsort関数

sasaraan programming

Exposition

●単純なソート

 単純比較ソートでは、要素の先頭から順に二つの値を比較してデータを入れ替えます。その手順は下表のようになります。ちなみに、比較回数は、常に、「要素数×(要素数−1)÷2」 となります。
[手順][1][2][3][4] * 赤字 : 比較元、青字 : 比較先
1回目の比較7391  (1)と(2)を比較(7の方が大きいので入れ替え)
2回目の比較3791  (1)と(3)を比較(7の方が小さいのでそのまま)
3回目の比較3791  (1)と(4)を比較(3の方が大きいので入れ替え)
4回目の比較1793  (2)と(3)を比較(7の方が小さいのでそのまま)
5回目の比較1397  (2)と(4)を比較(7の方が大きいので入れ替え)
6回目の比較1397  (3)と(4)を比較(9の方が大きいので入れ替え)
[ソート終了]1379 * 降順の場合は大小を逆にして比較
 実際にデータを入れ替える時は、退避先の変数を一つ用意する必要があります。そこに二つのいずれかの値を退避させておいてから、順に値を代入していきます。
// 二つの値を入れ替え
void exchange(int *n1, int *n2)
{
    int temp;        // 退避用の変数
    temp = *n1;    
    *n1 = *n2;       
    *n2 = temp;     
}	
#include <stdio.h>
void main()
{
    int a[] = {10, 99};
    exchange(&a[0], &a[1]);
    printf("a[0] = %d\n", a[0]);  // =99
    printf("a[1] = %d\n", a[1]);  // =10
}	
 以上の二つのしくみを組み合わせてソートを行います。関数にすると以下のようになります。ここでは、for文を入れ子にして構成しています。なお、昇順と降順の違いは不等号の向きの違いだけです。
// 昇順ソート
void sort(int *array, int length) 
{
    int i;    // 比較元のインデックス
    int j;    // 比較先のインデックス

    for (i=0; i<length-1; i++) {
        for (j=i+1; j<length; j++) {
            if (array[i] > array[j]) {
                // 入れ替え
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
} 
// 降順ソート
void reverse(int *array, int length) 
{
    int i;    // 比較元のインデックス
    int j;    // 比較先のインデックス

    for (i=0; i<length-1; i++) {
        for (j=i+1; j<length; j++) {
            if (array[i] < array[j]) {
                // 入れ替え
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
} 
// main 関数
int main() 
{
    int a[] = {7, 3, 9, 1, 5, 2, 6, 0, 8, 4};    // 並べ替えるデータ
    int i;

    sort(a, 10);                 // 昇順ソート
    for (i=0; i<10; i++) {
        printf("%d, ", a[i]);    // 出力結果 [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,]
    }
    printf("\n");

    reverse(a, 10);           // 降順ソート
    for (i=0; i<10; i++) {
        printf("%d, ", a[i]);    // 出力結果 [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,]
    }

    if (getch()) return 0;
} 	

●クイックソート

 クイックソートは、基準値を設定して、その値を元にソートを行うもので、その名の通り高速でのソートが可能です。しかし、要素数が少ない時は効果は希薄です。逆に要素数が多ければ多いほど、単純なソートよりも早く並べ替えを行います。
手順7, 3, 9, 1, 5, 2, 6, 0, 8, 4備考
(1)基準値(任意に抽出した値の平均)7, 3, 9, 1, 5, 2, 6, 0, 8, 4(int)(7+4)/2 = 5
(2)基準値より大きい値を前から探す7, 3, 9, 1, 5, 2, 6, 0, 8, 4赤色 : 比較元
(3)基準値より小さい値を後ろから探す7, 3, 9, 1, 5, 2, 6, 0, 8, 4青色 : 比較先
(4)見つかったら二つの値を交換4, 3, 9, 1, 5, 2, 6, 0, 8, 7交錯していたら(6)へ
(5)前後の添え字を一つずつ進める4, 3, 9, 1, 5, 2, 6, 0, 8, 7(2)〜(4)の繰返し
  ↓  ↓ ...
(6)両者が交錯したところでストップ4, 3, 0, 1, 5, 2, 6, 9, 8, 7←赤と青が逆転
(7)交錯地点で配列を二分化4, 3, 0, 1, 5, 2 / 6, 9, 8, 7以降はそれぞれに処理
(8)再帰処理2, 3, 0, 1/ 5, 4/ 6, 9, 8, 7(1)〜(7)の繰返し
  ↓  ↓ ...
・ソート終了0/ 1/ 2/ 3/ 4/ 5/ 6/ 7/ 8/ 9
 上記中、基準値の算出法は様々です。一例として二つの値の平均値を取っています。また、下記のサンプルコードも一例にすぎません。様々な記述やアレンジが可能です。
// クイックソート関数
// ... *array : 並べ替える配列, start : 開始インデックス, end : 終了インデックス
// ... 戻り値 : 並べ替えた後の配列
void quicksort(int *array, int start, int end) 
{
    int i = start;    // 開始インデックス
    int j = end;      // 終了インデックス
    int base = (array[start] + array[end]) / 2;  // (1)基準値(平均値を適用)

    while(1) {
        while (array[i] < base) i++;       // (2)基準値より大きい値を前から探す
        while (array[j] > base) j--;       // (3)基準値より小さい値を後ろから探す
 
        if (i >= j) break;         // (6)両者が交錯したら抜ける(交換すべき値がない)

        // (4)処理が続いていれば値の交換(小さい値を前に、大きい値を後ろに)
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;

        i++;                          // (5)交換した一つ後にインデックスを進める
        j--;                          // (5)交換した一つ前にインデックスを進める
    }
    // (7)配列を二分化してそれぞれに (8)再帰処理
    if (start < i-1) quicksort(array, start, i-1);   // 基準値より小さいグループ
    if (end > j+1)  quicksort(array, j+1, end);     // 基準値より大きいグループ
} 
// main 関数 int main() { int a[] = {7, 3, 9, 1, 5, 2, 6, 0, 8, 4}; // 並べ替えるデータ quicksort(a, 0, 9); // クイックソート(配列、最初のインデックス、最後のインデックス) int i; for (i=0; i<10; i++) { printf("%d, ", a[i]); // 出力結果 [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,] } if (getch()) return 0; }

●qsort関数

 Cではクイックソートをパッケージ化した qsort関数が用意されています。ただし、比較用の関数をプログラマが別途定義する必要があります。
qsort function
・書式 void qsort( void *array, size_t len, size_t width,
  int ( *compare ) (const void *value1, const void *value2 )
・機能クイックソートの実行
・引数array...配列、len...要素数、width...要素一個のバイト数、
compare...比較関数へのポインタ、
value1...比較元要素へのポインタ、value2...比較先要素へのポインタ
・戻り値なし (ただし、配列array を上書き)
・要件stdlib.h または search.h のインクルード
compare関数の戻り値(昇順時)は、
 *value1>*value2 の時は正の値、
 *value1==*value2 の時は0、
 *value1<*value2 の時は負の値である。(降順の時は正負逆)
 上記中、size_t は sizeof演算子の結果を表します。要素一個分のバイト数は、sizeof(データ型名) で取得できます。また、void* のところは、適宜データ型へのキャストを行うことができます。
 int (*compare)(...) は関数へのポインタです。引数なしの関数名は関数のアドレスを表しますので、実際の記述では引数の記述は必要ありません。
#include <stdio.h>     // printf使用
#include <stdlib.h>     // qsort使用
#include <conio.h>    // getch使用

// 比較関数 : 二つの int型の値を比較する
int compare(const void *value1, const void *value2)
{
    // 結果により、正の値、0、負の値のいずれかを返す
    return *(const int *)value1 - *(const int *)value2;
}

// main 関数
int main() 
{
    int a[] = {7, 3, 9, 1, 5, 2, 6, 0, 8, 4};    // 並べ替えるデータ
 
    qsort(a, 10, sizeof(int), compare);    // クイックソート

    int i;
    for (i=0; i<10; i++) {
        printf("%d, ", a[i]);    // 出力結果 [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,]
    }

    if (getch()) return 0;
} 

www.sasaraan.net

(c) morijoh