比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort)...

27
Algorithms and Data Structures on C 比較によらない整列

Transcript of 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort)...

Page 1: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

比較によらない整列

Page 2: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

この回の要点

•キーの比較を行う場合、整列の計算量の下限はO(n log n)• これより速いアルゴリズムは理論上、存在しない・・・はず

•比較によらない整列• バケットソート(ビンソート)(bucket sort,bin sort)• 分布数え上げソート(distribution counting sort)• 基数ソート(radix sort)

•計算量• 時間計算量はいずれもO(n)• 記憶容量もO(n)が必要

•制約• キーが離散的でなければならない• キーの範囲の制限→基数ソートで、ある程度は解決• O(n)とO(n log n)の違いは、実はそれほどない

• 定数項の効き具合では、クイックソートのほうが速い

Page 3: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

バケットソート

•原理• キーの種類の数のバケツを用意

• キーと対応するバケツにデータを入れる

• 先頭バケツから順にデータを取り出す

23

31

1356

1

27

3

9821

442

1 2 3 4 5 6 97 98 99・・・ 100

9856

4431

2723

2113

32

1

ばらばらn個

整列!

順に取り出す

放り込んで・・・

m個のバケツ

Page 4: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

バケットソートの性質

• 時間計算量• データ数n、バケツの数mとする• データを入れる回数O(n)

• データを取り出す回数O(m)

• トータルではO(n+m)• 通常は、バケツの数mはデータ数nによって決まるので、m=O(n)とみなせる

• その場合、バケットソートの計算量はO(n)

• メモリー量• バケツの数だけ必要→O(n)

• 高速性の代償として、大きなメモリーが必要

• 安定性• 1つのバケツに複数のデータが入った場合、入った順に取り出すようにすれば、同じデータの順序は変化しない

• バケットソートは安定である

Page 5: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

バケツの構造

•同じキーのデータの処理• 同じキーのデータがなければ、1つのバケツは1つのデータを格納できれば良い。

• 同じキーのデータが複数ある場合は、1つのバケツに複数のデータを格納できる必要がある。

• 安定なソートのためには、入れた順に取り出されなければならない

• バケツはキューでなければならない• FIFO(先入れ先出し)構造

1 2 3 4 5 6 97 98 99・・・ 100

単純な配列で可能

可変長配列リスト構造など

Page 6: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

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 sortBucket(void*,int,int(*)(void*));

int getCompCount();

int getExchgCount();

#endif // __Sort__h

比較によらないソートは、比較関数の代わりにキーを返す関数を与える。

キーを返す関数:

データを与えると、そのデータの整数値を返す。

比較によらないソートでは、データは整数値を持つ必要がある。

int (*key)(void *d){

PD *pd=(PD*)d;

return pd->age;

}

上の例では、個人情報の整数値として年齢を使っている。

Page 7: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

// …(略)…

#include <limits.h>

#include <math.h>

#include <string.h>

// …(略)…

// バケットソートvoid sortBucket(void *d0,int s,int (*key)(void*)){

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

// 最大と最小を求めるint min=INT_MAX; // 最小のキーint max=INT_MIN; // 最大のキーfor(int i=0;i<s;i++){

int k=key(d[i]);

if(k<min) min=k;

if(k>max) max=k;

}

続く

Page 8: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

// キーの個数を数えるint bn=max-min+1; // キーの種類数=バケツの数int *kn=(int*)malloc(bn*sizeof(int)); // 各キーのデータの個数for(int i=0;i<bn;i++) kn[i]=0;

for(int i=0;i<s;i++) kn[key(d[i])-min]++;

// 各キーの個数分のバケツの準備void ***backet=(void***)malloc(bn*sizeof(void**)); // バケツfor(int i=0;i<bn;i++)

backet[i]=(void**)malloc(kn[i]*sizeof(void*));

// バケツに入れるint *ki=new int[bn];

for(int i=0;i<bn;i++) ki[i]=0;

for(int i=0;i<s;i++){

int k=key(d[i]);

backet[k-min][ki[k-min]++]=d[i];

}

続く

バケツの番号 = キー –最小のキー

キーが5~10の場合:

0 1 2 3 4 5

5 6 7 8 9 10キー:

バケツ:

Page 9: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

// バケツから取り出すint p=0;

for(int i=0;i<bn;i++){

for(int j=0;j<kn[i];j++)

d[p++]=backet[i][j];

}

// メモリを解放for(int i=0;i<bn;i++)

free(backet[i]);

free(backet);

free(kn);

free(ki);

}

Page 10: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

分布数え上げソート

•原理• 有限範囲の整数をキーとするデータの場合、キーの分布(どのキーが何個あるか)がわかれば、そのデータが何番目であるかもわかる。

•例• キーの範囲0~9

• キー=3,7,2,4,3,5,1,2,5

• キーkは、b-1から前にa個分となる

キー k 0 1 2 3 4 5 6 7 8 9

出現回数 a 0 1 2 2 1 2 0 1 0 0

累計 b 0 1 3 5 6 8 8 9 9 9

1 2 2 3 3 4 5 5

0 1 2 3 4 5 6 7

7キー

順番 8

Page 11: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

分布数え上げソートの実行1

• 3つの配列を使用する• キーの範囲の数:n

• 累計の配列 b[n]

• データの個数:s• もとのデータの配列 d[s]

• 整列用の一時データの配列 t[s]

•手順• b[]←キーの累計• t[]←d[]• t[]を後ろからたどる i=s-1 → 0

• t[i]のキーkに対するb[k]を得る• --b[k]• d[b[k]]←t[i]

•安定性• t[]を後ろからたどることにより、同一キーの安定性が確保される

Page 12: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

分布数え上げソートの実行2

k 0 1 2 3 4 5 6 7 8 9

b[k] 0 1 3 5 6 8 8 9 9 9

b[k] 3 7

b[k] 1 2

b[k] 0 7

b[k] 5 6

b[k] 4 6

b[k] 2 5

b[k] 1 9

b[k] 4 8

b[k] 3

3 7 2 4 3 5 1 2 5

i 0 1 2 3 4 5 6 7 8

8 5

7 2

6 1

5 5

4 3

3 4

2 2

1 7

0 1 2 2 3 3 4 5 5 7

整列!

後ろからコピーする

実際に使うときは、縦に伸ばす必要はない。

累計

d[i]

t[i]

Page 13: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.h

…(略)…

// プロトタイプ宣言

// 比較によるソートvoid sortBubble(void*,int,int(*)(void*,void*));

// …(略)…

// 比較によらないソートvoid sortBucket(void*,int,int(*)(void*));

void sortCounting(void*,int,int(*)(void*));

int getCompCount();

int getExchgCount();

#endif // __Sort__h

Page 14: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

…(略)…

// 分布数え上げソートvoid sortCounting(void *d0,int s,int (*key)(void*)){

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

// データを仮配列にコピーするvoid **t=(void**)malloc(s*sizeof(void*));

memcpy(t,d,s*sizeof(void*));

// 最大と最小を求めるint min=INT_MAX; // 最小のキーint max=INT_MIN; // 最大のキーfor(int i=0;i<s;i++){

int k=key(d[i]);

if(k<min) min=k;

if(k>max) max=k;

}

続く

Page 15: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

// キーの累計を計算するint n=max-min+1; // キーの種類数int *b=(int*)malloc(n*sizeof(int)); // 各キーのデータの個数for(int i=0;i<n;i++) b[i]=0;

for(int i=0;i<s;i++) b[key(d[i])-min]++;

for(int i=1;i<n;i++) b[i]+=b[i-1];

// 並べ替えfor(int i=s-1;i>=0;i--){ // t[]のデータをd[]に移動int k=key(t[i]);

d[--b[k-min]]=t[i];

}

// メモリを解放free(b);

free(t);

}

Page 16: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

分布数え上げソートの性質

• 時間計算量• 各処理の計算量は、

• 累計をクリア:O(m)• キーの出現数をカウント:O(n)• 累計を計算:O(m)• 作業用配列にコピー:O(n)• 整列:O(n)

• トータルの計算量はO(n+m)

• メモリー量• キーの分布累計:O(m)• 作業用配列:O(n)• 高速性の代償として、大きなメモリーが必要

• 安定性• 作業用配列からコピーする際、配列の後ろからコピーすることで、同じキーの要素の順番は変わらない

• 分布数え上げソートは安定である

Page 17: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

基数ソート(Radix sort)

•バケットソート等の問題点•キーの種類が多い場合、バケツや累計用配列の大きさが大きくなりすぎる

•テストの点数で並べ替える場合• バケツは0点用~100点用の101個で済む

•銀行の預金高で並べ替える場合• 個人の預金高を最大100億円として

• 0円用~10,000,000,000円用の10,000,000,001個

• しかも、大きい値のほとんどのバケツは空であろう

•キーの種類が多すぎると、使えない•実用上は大きな問題

Page 18: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

基数ソートの考え方

•3桁の数字が書かれたカードを並べ替えたい• 10個の箱を準備し、•第1段階:一の位の数で分ける•第2段階:小さい箱から十の位の数で分ける•第3段階:小さい箱から百の位の数で分ける→整列!

0 1 2 3 4 5 6 7 8 9

第1段階 120,210,

050,580

221,421 012 043,903,

703

375 438,798,

888,378

第2段階 903,703 210, 012 120,

221,421

438 043 050 375,378 580,888 798

第3段階 012,043,

050

120 210,221 375,378 421,

438

580 703,798 888 903

043,438,798,120,012,888,378,210,903,375,221,050,421,580,703

Page 19: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

なぜうまくいくのか?

•各桁の整列の安定性が必要•安定な整列ならば、前の桁の順序を保って整列する•前の桁の整列が生きる

120,210,050,580,221,421,012,043,903,703,375,438,798,888,378

903,703,210,012,221,120,421,438,043,050,375,378,580,888,798

012,043,050,120,210,221,375,378,421,438,580,703,798,888,903

十の位で整列

百の位で整列十の位は整列されたまま

一の位で整列

一の位は整列されたまま

Page 20: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.h

…(略)…

// プロトタイプ宣言

// 比較によるソートvoid sortBubble(void*,int,int(*)(void*,void*));

// …(略)…

// 比較によらないソート// sortXXX(void *d0,int s,int (*key)(void*));

void sortBucket(void*,int,int(*)(void*));

void sortCounting(void*,int,int(*)(void*));

void sortRadix(void*,int,int(*)(void*),int=10);

int getCompCount();

int getExchgCount();

#endif // __Sort__h

デフォルトの基数は10

(プロトタイプ宣言の仮引数の後に=に続いて値を書くと、その引数

は省略可能であり、省略した場合はこの値が使われる)

Page 21: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc

…(略)…

// 桁cを分布数え上げソート// d : 元データ配列、t : 仮データ配列、s : データ数、 r : 基数// c : 桁、 b : キーの個数用配列、 key : キー関数void radixSort(void **d,void **t,int s,int r,int c,int *b,int

(*key)(void*)){

int a=(int)pow(r,c); // r進数c桁の数

// 仮配列にCOPYmemcpy(t,d,s*sizeof(void*));

// キーの累計を計算するfor(int i=0;i<r;i++) b[i]=0;

for(int i=0;i<s;i++) b[(key(t[i])/a)%r]++;

for(int i=1;i<r;i++) b[i]+=b[i-1];

// 並べ替えfor(int i=s-1;i>=0;i--){

int k=(key(t[i])/a)%r;

d[--b[k]]=t[i];

}

}続く

実際の並べ替えは分布数え上げソート

Page 22: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

Sort.cc// 基数ソート// r : 基数[10]

void sortRadix(void *d0,int s,int (*key)(void*),int r){

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

// 最大を求めるint max=INT_MIN; // 最大のキーfor(int i=0;i<s;i++){

int k=key(d[i]);

if(k>max) max=k;

}

// 基数ソートする回数int rsc=(int)ceil(log(max+1)/log(r));

// 各桁のソートint *b=(int*)malloc(r*sizeof(int)); // キーの個数配列void **t=(void**)malloc(s*sizeof(void*)); // 仮配列for(int c=0;c<rsc;c++)

radixSort(d,t,s,r,c,b,key);

free(t);

free(b);

}

基数が10で、最大値が1000だった場合、4回の基数ソートが必要

41001log

31000log

10

10

Page 23: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

比較によらないソートの性能

0.1

1

10

100

1000

10000 100000 1000000

bucket

counting

radix

O(N logN)

O(N)

N人のPDデータキーは年齢(10~99)

lap [m

s]

num

Page 24: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

比較によらないソートの性能(2)

0.1

1

10

100

1000

10000 100000 1000000

bucket big

counting big

radix big

O(N logN)

O(N)

基数は100

N人のPDデータキーは、名前の4文字分を整数化した264 (=331776)通り

lap [m

s]

num

Page 25: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

整列のまとめ

•比較による整列• バブルソート

• 選択ソート

• 挿入ソート(★遅い並べ替えではおすすめ)

• シェルソート(挿入ソートの改良)

• ヒープソート

• マージソート

• クイックソート(★整列アルゴリズムの王様)

•比較によらない整列• バケットソート

• 分布数え上げソート(★整数キーの整列ではおすすめ)

• 基数ソート

Page 26: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

整列アルゴリズムの比較

0.01

0.1

1

10

100

1000

10000

100 1000 10000 100000 1000000

bubble selection insertion heap merge

shell(knuth) shell(tokuda) quick1 quick2 bucket

counting radix bucket big counting big radix big

O(N) O(N logN) O(N^2)

lap [m

s]

num

Page 27: 比較によらない整列fuchida/lecture/algorithm/alg...基数ソート(Radix sort) •バケットソート等の問題点 •キーの種類が多い場合、バケツや累計用配列の大きさが

Algorithms and Data Structures on C

比較によらない整列

終了