整列アルゴリズムの基礎

Algorithms and Data Structures on C
整列アルゴリズムの基礎
Algorithms and Data Structures on C
この回の要点
• 整列とは?
• 整列処理の意味
• 整列の種類
• 整列の計算量
• 整列の安定性
• 単純な整列アルゴリズム
• バブルソート
• 選択ソート
• 挿入ソート
• それぞれのソートの特徴と実装
Algorithms and Data Structures on C
整列(Sorting)とは
• 整列という操作
• レコードの集まりを、キーの値の大小関係に従って並べ替える操作
• 小さい→大きい=昇順(ascending order)
• 大きい→小さい=降順(descending order)
• データには、同じキーを持つものがあってもよい
• 同じキーのデータは連続して整列される
• 整列アルゴリズムの種類
• 比較による整列
• バブルソート、選択ソート、挿入ソート:O(n2)
• シェルソート:O(n1.5)
• ヒープソート、マージソート、クイックソート:O(n log n)
• 比較によらない整列(デジタルソート)
• バケットソート(ビンソート)、分布数え上げソート、基数ソート:O(n)
Algorithms and Data Structures on C
整列の計算量
• 比較による整列
• n個のデータを整列するときに必要な処理
• 2つのデータの大小を判定する(比較)
• 必要ならばそれらを入れ替える(交換)
• アルゴリズムによって、2つの処理の回数の比率が異なる
• 扱うデータがどちらにコストがかかるか、によって使い分ける
• データのサイズが大きい場合、データそのものを交換するのにコストが
かかる
• キーの構造が複雑な場合、比較にコストがかかる
• 計算量だけでなく、定数項部分も重要
• 高速なアルゴリズムは処理が複雑である傾向がある
• nが小さいときは、単純なアルゴリズムのほうが速いこともある
• 比較によらない整列
• 時間的にはO(n)で整列可能であるが、メモリ量もO(n)である
• 時間的にも、O(n)とO(n log n)とは、実用的にはそれほど差はない
Algorithms and Data Structures on C
O(n)とO(n log n)とO(n2)との比較
1E+13
1E+11
1E+09
n
n log(n)
n*n
10000000
100000
1000
10
0.1 1
100
10000
1000000
Algorithms and Data Structures on C
安定な整列
• 安定な(stable)整列とは
• 同じキーを持つデータがある場合に、それらの順序関係が保たれる整列
• すべての整列アルゴリズムが安定であるとは限らない
• 単純な(遅い)アルゴリズムは安定の傾向
• 複雑な(速い)アルゴリズムは非安定の傾向
• ただし、サブキーを利用することで、確実に安定にできる
• 安定性の利点
• 複数のエントリーを持つレコードを、1つのキーによって整列するような場合
整列前 : 7
9
5a
4
8
5b
2
安定なソートは
必ずこうなる
整列後①: 2
4
5a
5b
7
8
9
整列後②: 2
4
5b
5a
7
8
9
非安定なソートは
どちらになるか
わからない
Algorithms and Data Structures on C
単純な整列アルゴリズム
• 以下の3種類
• バブルソート
• 選択ソート
• 挿入ソート
• いずれもO(n2)の計算量
• プログラムとして実装した場合、データ数nに対して2重のforループ構造とな
る。
• nが大きいと遅くて使いものにならない
• アルゴリズムが簡単ですぐに実装できる
• 少ないデータを整列するには重宝
Algorithms and Data Structures on C
ソート関数の形
• 任意のポインタの配列を並べ替える関数
• sortXXXXX(配列,サイズ,比較関数)
• XXXXXは並べ替えのタイプ
• 配列は任意のポインタの配列
• サイズは配列の要素数
• 比較関数
• int *compare(void *d1,void *d2)
• d1=d2ならばゼロ
• d1<d2ならば負の数
• d1>d2ならば正の数、を返す
Algorithms and Data Structures on C
Sort.h
#ifndef __Sort__h
#define __Sort__h
/***
*** 並べ替え用
***/
#include <stdio.h>
#include <stdlib.h>
// プロトタイプ宣言
void sortBubble(void*,int,int(*)(void*,void*));
void sortSelection(void*,int,int(*)(void*,void*));
void sortInsertion(void*,int,int(*)(void*,void*));
#endif // __Sort__h
Algorithms and Data Structures on C
バブルソート
• 原理
• 配列の後ろから先頭に向かってスキャンし、隣り合う要素の大小関係
が逆なら入れ替える
6
2
7
1
5
1
4
1
1
1
3
3
4
5
7
1
1
1
6
2
2
2
7
3
5
3
4
3
3
4
5
7
2
6
3
3
3
7
4
5
4
4
5
4
7
時間
Algorithms and Data Structures on C
Sort.cc
/*** ソートの実装 ***/
#include "Sort.h"
// バブルソート
void sortBubble(void *d0,int s,int (*comp)(void*,void*)){
void **d=(void**)d0;
次のスライドで説明
for(int i=0;i<s-1;i++){
for(int j=s-1;j>i;j--){
if(comp(d[j-1],d[j])>0){
void *t=d[j];
②
①
③
d[j]=d[j-1];
d[j-1]=t;
}
}
}
}
Algorithms and Data Structures on C
整列データの受け渡し
• 整列するためのデータ
• 任意のポインタであるvoid*の配列
• これは、void**と表すことができる
• 実際に並べ替えるデータ
• PDのポインタ配列であるPD**など
• PD**からvoid**への変換が必要となる
• この変換は暗黙には行われない
• そのための工夫
•
•
•
•
sortXXXXX(void*,int,…)のようにvoid*で受ける
void*は任意のポインタであるからPD**でも良い
これを受け取った後にvoid**にキャストする
ただし、これによりコンパイラによる型チェックの恩恵にはあずかれなくなる
Algorithms and Data Structures on C
整列データの受け渡し
PD**
PD*
山田
18
PD*
整列関数
void sortXXXXX(void *d0,…){
void **d=(void**)d0;
PD*
PD*
PD*
void**
void*
void*
void*
void*
void*
void*
void*
void*
PD*
森
55
中村
33
PD*
PD*
整列するデータ
関数の中ではvoid*の配列として取り扱う
今井
60
Algorithms and Data Structures on C
バブルソートの計算量
• 二重ループ構造(①と②)
• ①のループは n-1 回 → O(n-1)
• ②のループは、平均 n/2 回 → O(n/2)
• ③の処理は、nに無関係 → O(1)
• 計算量
• O(n-1)×O(n/2)×O(1)=O(n2) となる
• 安定な整列アルゴリズム
• 「前の要素が大きければ入れ替える」
• 等しい場合は入れ替わらない
• 等しい要素の順番は保存される
• データの交換回数が多い
• ループ②の中で、データが上位に上がるとデータの交換が起こる
• 処理効率の低下
Algorithms and Data Structures on C
SortTest.java
/***
*** 整列のテスト
***/
#include "Sort.h"
#include "PD.h"
// 比較関数
int comp(void *d1,void *d2){
PD *pd1=(PD*)d1;
PD *pd2=(PD*)d2;
return pd1->age-pd2->age;
}
// 表示
void print(PD **d,int s){
for(int i=0;i<s;i++)
printf("%s(%d)-",d[i]->name,d[i]->age);
printf("¥n");
}
続く
Algorithms and Data Structures on C
SortTest.java
// メイン
int main(int argc,char **argv){
PD *data[6];
data[0]=makePD("山田",18);
data[1]=makePD("森",55);
data[2]=makePD("中村",33);
data[3]=makePD("石田",27);
data[4]=makePD("東村",31);
data[5]=makePD("牧",12);
print(data,6);
// 並べ換える前の表示
sortBubble(data,6,comp); // 並べ換え
print(data,6);
// 並べ換えた後の表示
}
Algorithms and Data Structures on C
実行結果
$ ./TestSort
山田(18)-森(55)-中村(33)-石田(27)-東村(31)-牧(12)牧(12)-山田(18)-石田(27)-東村(31)-中村(33)-森(55)-
Algorithms and Data Structures on C
選択ソート
• 原理
• 整列されていない部分から最小の要素を選び、それを先頭に移動する
•
•
•
•
•
a[0]~a[n-1]の最小の要素をa[0]と交換
a[1]~a[n-1]の最小の要素をa[1]と交換
a[2]~a[n-1]の最小の要素をa[2]と交換
・・・
a[n-2]~a[n-1]の最小の要素をa[n-2]と交換
整列済み
未整列
最小を探す
整列済み
先頭に持ってくる(交換)
Algorithms and Data Structures on C
Sort.cc(続き)
// 選択ソート
void sortSelection(void *d0,int s,int (*comp)(void*,void*)){
void **d=(void**)d0;
for(int i=0;i<s-1;i++){
int min=i;
for(int j=i+1;j<s;j++){
if(comp(d[j],d[min])<0)
③
②
min=j;
①
}
void *t=d[i];
d[i]=d[min];
④
d[min]=t;
}
}
Algorithms and Data Structures on C
選択ソートの計算量
• 二重ループ構造
•
•
•
•
①のループは n-1 回 → O(n-1)
②のループは、平均 n/2 回 → O(n/2)
③の処理は、nに無関係 → O(1)
④の処理も、nに無関係 → O(1)
• 計算量
• O(n-1)×O(n/2)×O(1) ×O(1)=O(n2) となる
• 非安定な整列アルゴリズム
• 先頭要素との交換時に、同じ値を持つ要素の並びが変化する
• データの交換回数が少ない
• ループ①が1回まわると、交換が1回だけ起こる
• バブルソートより高速
Algorithms and Data Structures on C
挿入ソート
• 原理
• 配列の一部を整列済みにし、残りの要素を1つずつその中の適切な位
置に挿入する
整列前
14 8
3 18 21 4 31 7 11 24 16
先頭要素だけ整列済み
1回ループ後
8 14 3 18 21 4 31 7 11 24 16
整列済みに、8を挿入(比較1回)
2回ループ後
3
8 14 18 21 4 31 7 11 24 16
整列済みに、3を挿入(比較2回)
3回ループ後
3
8 14 18 21 4 31 7 11 24 16
整列済みに、18を挿入(比較1回)
4回ループ後
3
8 14 18 21 4 31 7 11 24 16
整列済みに、21を挿入(比較1回)
5回ループ後
3
4
整列済みに、4を挿入(比較4回)
・
・
・
8 14 18 21 31 7 11 24 16
整列済み
Algorithms and Data Structures on C
InsertionSort.java
// 挿入ソート
void sortInsertion(void *d0,int s,int (*comp)(void*,void*)){
void **d=(void**)d0;
for(int i=1;i<s;i++){
for(int j=i;j>0 && comp(d[j-1],d[j])>0;j--){
void *t=d[j];
d[j]=d[j-1];
②
①
③
d[j-1]=t;
}
}
}
Algorithms and Data Structures on C
挿入ソートの計算量
• 二重ループ構造
• ①のループは n-1 回 → O(n-1)
• ②のループは、平均 n/2 回 → O(n/2)
• ③の処理は、nに無関係 → O(1)
• 計算量
• O(n-1)×O(n/2)×O(1) =O(n2) となる
• 安定な整列アルゴリズム
• 挿入するとき、1つ前の要素が大きければ入れ替える
• 同じの場合は入れ替えないので、同じ要素の順番は保持される
• ほとんど整列済みのデータに有利
• 既に並んでいるデータについては比較と交換が発生しない
• この場合、ループ②は1回もまわらない
• ほとんど整列済みのデータに対しては、ほぼO(n)で整列可能
Algorithms and Data Structures on C
単純な整列アルゴリズムの比較
• 実験用プログラム
• 3つのソートを実行し、以下の情報を測定
• 実行時間
• データの比較回数
• データの交換回数
Sort.ccに変数を作成する(後で)
• 与えるデータ
• データ数を可変(200個~10000個)
• データの分布を可変(ランダム、ほぼ整列済み)
Algorithms and Data Structures on C
比較と交換の回数のカウント(1)
• Sort.ccにカウント用の変数を追加
• 整列前に0に初期化する
• 各アルゴリズムのsortXXXX()関数内で、比較と交換の回数をカウント
/***
*** ソートの実装
***/
#include "Sort.h"
int comp_count;
int exchg_count;
Sort.hの中でextern宣言して
ないので、Sort.ccの中でしか
使えない変数
// 比較回数
// 交換回数
続く
Algorithms and Data Structures on C
比較と交換の回数のカウント(2)
// バブルソート
void sortBubble(void *d0,int s,int (*comp)(void*,void*)){
comp_count=exchg_count=0;
void **d=(void**)d0;
for(int i=0;i<s-1;i++){
for(int j=s-1;j>i;j--){
comp_count++;
if(comp(d[j-1],d[j])>0){
exchg_count++;
void *t=d[j];
d[j]=d[j-1];
d[j-1]=t;
}
}
}
}
続く
Algorithms and Data Structures on C
比較と交換の回数のカウント(3)
// 選択ソート
void sortSelection(void *d0,int s,int (*comp)(void*,void*)){
comp_count=exchg_count=0;
void **d=(void**)d0;
for(int i=0;i<s-1;i++){
int min=i;
for(int j=i+1;j<s;j++){
comp_count++;
if(comp(d[j],d[min])<0)
min=j;
}
exchg_count++;
void *t=d[i];
d[i]=d[min];
d[min]=t;
}
}
続く
Algorithms and Data Structures on C
比較と交換の回数のカウント(4)
// 挿入ソート
void sortInsertion(void *d0,int s,int (*comp)(void*,void*)){
comp_count=exchg_count=0;
void **d=(void**)d0;
for(int i=1;i<s;i++){
for(int j=i;j>0;j--){
comp_count++;
if(comp(d[j-1],d[j])>0){
exchg_count++;
カウントするために、判断
void *t=d[j];
構造を少し変えた
d[j]=d[j-1];
本質的には同じ
d[j-1]=t;
}
else
break;
}
}
}
続く
Algorithms and Data Structures on C
比較と交換の回数のカウント(5)
• 各回数を得るための関数を追加
// 比較回数を得る
int getCompCount(){ return comp_count; }
// 交換回数を得る
int getExchgCount(){ return exchg_count; }
Algorithms and Data Structures on C
比較と交換の回数のカウント(6)
• Sort.hにプロトタイプ宣言を追加
#ifndef __Sort__h
#define __Sort__h
/***
*** 並べ替え用
***/
#include <stdio.h>
#include <stdlib.h>
// プロトタイプ宣言
void sortBubble(void*,int,int(*)(void*,void*));
void sortSelection(void*,int,int(*)(void*,void*));
void sortInsertion(void*,int,int(*)(void*,void*));
int getCompCount();
int getExchgCount();
#endif // __Sort__h
Algorithms and Data Structures on C
テスト用TestSort2.cc
/***
*** 並べ換えのテスト2
***/
#include "Sort.h"
#include "PD.h"
#include <string.h>
#include <time.h>
// 比較関数
// 名前で比較し、同じなら年齢で比較する
int compare(void *d1,void *d2){
PD *pd1=(PD*)d1;
PD *pd2=(PD*)d2;
int c=strcmp(pd1->name,pd2->name);
if(c==0) return pd1->age-pd2->age;
return c;
}
続く
Algorithms and Data Structures on C
テスト用TestSort2.cc
// メイン
int main(int argc,char **argv){
int loop=1;
double rand_rate=1.0;
// 引数処理
if(argc!=3 && argc!=4 && argc!=5){
fprintf(stderr,
"Usage: %s type max [rand=1.0] [loop=1]",argv[0]);
exit(1);
}
int type=atoi(argv[1]);
int max=atoi(argv[2]);
if(argc>=4) rand_rate=atof(argv[3]);
if(argc>=5) loop=atoi(argv[4]);
PD **data=(PD**)malloc(max*sizeof(PD*));
srand(time(NULL));
続く
Algorithms and Data Structures on C
テスト用TestSort2.cc
// 実験
double lap=0;
double comp_count=0;
double exchg_count=0;
for(int i=0;i<loop;i++){
// シリアルデータの準備
char s[]={ 'a','a','a','a','a',0 };
for(int i=0;i<max;i++){
int a=rand()%60+10;
// 年齢はランダム
data[i]=makePD(s,a);
for(int j=4;j>=0;j--){
if(s[j]<'z'){
s[j]++;
break;
}
s[j]='a';
}
}
続く
Algorithms and Data Structures on C
テスト用TestSort2.cc
// シャッフル
int c=(int)(max*rand_rate);
for(int i=0;i<c;i++){
int r1=(int)(((double)rand()/RAND_MAX)*max);
int r2=(int)(((double)rand()/RAND_MAX)*max);
PD *t=data[r1];
data[r1]=data[r2];
data[r2]=t;
}
続く
Algorithms and Data Structures on C
テスト用TestSort2.cc
// 並べ替え
clock_t t1=clock();
switch(type){
case 0: sortBubble(data,max,compare); break;
case 1: sortSelection(data,max,compare); break;
case 2: sortInsertion(data,max,compare); break;
}
clock_t t2=clock();
lap+=((double)t2-t1)/CLOCKS_PER_SEC;
comp_count+=getCompCount();
exchg_count+=getExchgCount();
}
printf("%f %f %f ",lap*1000,comp_count/loop,exchg_count/loop);
}
Algorithms and Data Structures on C
データ数と並べ替え時間
1000000
b-lap
100000
s-lap
i-lap
10000
n^2/100
1000
100
10
1
100
1000
10000
Algorithms and Data Structures on C
データ数と比較回数
100000000
10000000
1000000
100000
b-comp
s-comp
10000
i-cmop
1000
100
1000
10000
Algorithms and Data Structures on C
データ数と交換回数
100000000
b-exchg
10000000
s-exchg
1000000
i-exchg
100000
10000
1000
100
100
1000
10000
Algorithms and Data Structures on C
比較回数・交換回数
• 選択ソートはデータの交換回数が少ない
• 挿入ソートはデータの比較回数が少ない
num
b-lap
b-comp
b-exchg
s-lap
s-comp s-exchg i-lap
i-cmop
9078.65
i-exchg
200
15
19900
8882.7
0
19900
199
0
8882.7
300
15
44850
20223.45
15
44850
299
0
500
46
124750
56795.45
31
124750
499
15 57289.95 56795.45
700
32
244650
110607.8
62
244650
699
31 111303.3 110607.8
1000
171
499500
223885.2
140
499500
999
78 224880.3 223885.2
2000
703
1999000
907912.4
546
1999000
1999
265 904197.5 902201.7
3000 1593
4498500
2035679
1203
4498500
2999
546 2032459 2029464
5000 4452 12497500
5659682
3296 12497500
4999 1453 5642875 5637880
7000 8751 24496500 11069605
6422 24496500
6999 2859 11110157 11103161
10000 18300 49995000 22607757
13332 49995000
9999 5828 22507657 22497662
20519 20223.45
Algorithms and Data Structures on C
ほぼ揃ったデータの整列
100000
ランダム
ほぼ揃っている
10000
1000
b-lap
s-lap
i-lap
100
0
0.2
0.4
0.6
0.8
1
Algorithms and Data Structures on C
ほぼそろったデータに対する
比較回数・交換回数
• データ数は10000に固定
• 挿入ソートは、比較回数が極端に少ない
• それに応じて、交換回数も少なくなる
rand b-lap
b-comp
b-exchg
s-lap
s-comp s-exchg i-lap
i-cmop
i-exchg
0.01 11453 49995000
657297 8736 49995000
9999
171
650484
640485
0.05 12141 49995000
3085772 9079 49995000
9999
749
3052251
3042252
0.1 12814 49995000
5715629 9623 49995000
9999 1499
5780676
5770677
0.2 14172 49995000 10128342 10500 49995000
9999 2718 10134480 10124482
0.5 16875 49995000 17734631 12306 49995000
9999 4577 17701200 17691204
1 18301 49995000 22551811 13383 49995000
9999 5891 22645149 22635154
ランダム率
Algorithms and Data Structures on C
課題150105
• この回に実装した整列プログラムは、void*の配列を整列するので、通常の
intの配列は整列できない。
• そこで、バブルソートでintの配列を整列する関数
sortBubble(int*,int,int(*)(int,int))を実装せよ。
• 実装は、Sort.hにプロトタイプ宣言し、Sort.ccにコードを追加すること。
• 降順に整列する比較関数を書き、10個のランダムな整数を降順に並べ替
えるテストプログラムTestSort3.ccも示せ。
• 提出方法:
• レポートはワードで作成し、メールに添付すること。
• ファイル名は scXXXXXX-al150105.docx とすること。レポートには学籍番号と氏名
を必ず書くこと。
• メールで渕田まで送付すること。
• 提出先:[email protected]
• メールタイトル:”アルゴリズム課題150105”
• メール本文にも学籍番号と氏名を必ず書くこと。
• 期限:2015年1月18日(日)
Algorithms and Data Structures on C
整列アルゴリズムの基礎
終了