43
Algorithms and Data Structures on C 整列アルゴリズムの基礎

整列アルゴリズムの基礎fuchida/lecture/algorithm/alg...Algorithms and Data Structures on C 整列(Sorting)とは •整列という操作 •レコードの集まりを、キーの値の大小関係に従って並べ替える操作

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

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)との比較

0.1

10

1000

100000

10000000

1E+09

1E+11

1E+13

1 100 10000 1000000

n

n

n log(n)

n*n

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は並べ替えのタイプ

• sortBubble()

• sortSelection()

• sortInsertion()など

• 配列は任意のポインタの配列

• サイズは配列の要素数

• 比較関数• 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

5

4

1

3

4

1

1

5

1

7

1

3

2

1

1

6

2

7

5

4

3 4

3

3

5

3

7

3

2

2

6

1

3

7

5

4

4

5

4

7

4

3

時間

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**にキャストする

• ただし、これによりコンパイラによる型チェックの恩恵にはあずかれなくなる

void*

void*

void*

void*

void*

任意

任意

void**

整列データ配列

実際のデータ

渡すデータ

Algorithms and Data Structures on C

整列データの受け渡し

PD*

PD*

PD*

PD*

PD*

PD*

PD*

PD*

PD**

山田18

森55

中村33

今井60

整列するデータ

void sortXXXXX(void *d0,…){

void **d=(void**)d0;

整列関数

void**void*

void*

void*

void*

void*

void*

void*

void*

関数の中ではvoid*の配列として取り扱う

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

TestSort1.cc/***

*** 整列のテスト***/

#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

TestSort1.cc

// メイン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

実行結果

$ ./TestSort1

山田(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 2118 314 7 11 24 16整列前

148 3 2118 314 7 11 24 161回ループ後

1483 2118 314 7 11 24 162回ループ後

1483 2118 314 7 11 24 163回ループ後

1483 2118 314 7 11 24 164回ループ後

1483 2118 314 7 11 24 165回ループ後

先頭要素だけ整列済み

整列済みに、8を挿入(比較1回)

整列済み

整列済みに、3を挿入(比較2回)

整列済みに、18を挿入(比較1回)

整列済みに、21を挿入(比較1回)

整列済みに、4を挿入(比較4回)

・・・

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つのソートを実行し、以下の情報を測定

• 実行時間

• データの比較回数

• データの交換回数

• 与えるデータ• データ数を可変(200個~10000個)

• データの分布を可変(ランダム、ほぼ整列済み)

Sort.ccに変数を追加する(後で)

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';

}

} 続く

名前のアルファベットを1文字ずつずらす処理。“aaaaa” → “aaaab” →

“aaaac” → “aaaad” → ・・・→

“aaaaz” → “aaaba” → ・・・

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

データ数と並べ替え時間

1

10

100

1000

10000

100000

1000000

100 1000 10000

b-lap

s-lap

i-lap

n^2/100

Algorithms and Data Structures on C

データ数と比較回数

1000

10000

100000

1000000

10000000

100000000

100 1000 10000

b-comp

s-comp

i-cmop

Algorithms and Data Structures on C

データ数と交換回数

100

1000

10000

100000

1000000

10000000

100000000

100 1000 10000

b-exchg

s-exchg

i-exchg

Algorithms and Data Structures on C

比較回数・交換回数

•選択ソートはデータの交換回数が少ない

•挿入ソートはデータの比較回数が少ない

num b-lap b-comp b-exchg s-lap s-comp s-exchg i-lap i-cmop i-exchg

200 15 19900 8882.7 0 19900 199 0 9078.65 8882.7

300 15 44850 20223.45 15 44850 299 0 20519 20223.45

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

Algorithms and Data Structures on C

100

1000

10000

100000

0 0.2 0.4 0.6 0.8 1

b-lap

s-lap

i-lap

ほぼ揃ったデータの整列

ほぼ揃っている ランダム

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

課題200106

• この回に実装した整列プログラムは、void*の配列を整列するので、通常のintの配列は整列できない。

• そこで、バブルソートでintの配列を整列する関数sortBubble(int*,int,int(*)(int,int))を実装せよ。

• 実装は、Sort.hにプロトタイプ宣言し、Sort.ccにコードを追加すること。

• 降順に整列する比較関数を書き、10個のランダムな整数を降順に並べ替えるテストプログラムTestSort3.ccも示せ。

• 提出方法:• レポートはワードで作成し、メールに添付すること。• ファイル名は scXXXXXX-al200106.docx とすること。レポートには学籍番号と氏名を必ず書くこと。

• メールで渕田まで送付すること。• 提出先:[email protected]

• メールタイトル:”アルゴリズム課題200106”• メール本文にも学籍番号と氏名を必ず書くこと。• 期限:2020年1月19日(日) 24:00

Algorithms and Data Structures on C

整列アルゴリズムの基礎

終了