<!-- ここまでコピペ -->
そんな中,O(n)で動作するようなやばいソートもあります.例えば,計数ソート,基数ソート,バケットソートなど….
■計数ソート(counting sort)
このソートでは,配列aがn個の要素a0, a1, …an-1,を持っていた時,以下を仮定します.
0≦ai<k
で,次のように処理を進めます.
1.配列aにおいて,iに等しい要素の個数ciを計算
2.1.の結果を用いて,ciがi以下の要素の個数を含むように計算
3.配列aの各要素aiがどこに位置するかをcaiで調べ,出力配列bに順次代入.
k=O(n)の時,このソートの計算量はO(n)です.
□コード
// 計数ソート
void countingSort(int[] a, int k){
int n=a.length;
int[] b=new int[n];
int[] c=new int[k];
for(int i=0; i<n; i++)
c[a[i]]++;
// c[i]はiに等しい要素の個数を含んでいる
c[0]--;
for(int i=1; i<k; i++)
c[i]+=c[i-1];
// c[i]はi以下の要素の個数-1を含んでいる
for(int i=n-1; i>=0; i--)
b[c[a[i]]--]=a[i];
System.arraycopy(b, 0, a, 0, n);
}
■基数ソート(radix sort)
基数ソートは,i桁目に関するソートを最下位桁から最上位桁まで繰り返します.
ソートは安定ソートなら何でもいいのですが,各桁の値のとる範囲が小さければ計数ソートを使うのが妥当です.
要素数がn,各桁の値の範囲が0からk-1(k種類),桁数がdであるとします.各桁の計数ソートの実行時間は,O(n+k)となります.計数ソートはd回行われるので,基数ソートの実行時間はO(dn+kd)となります.dが定数,k=O(n)ならば,結局O(n)になるので,線形時間で動作するという事になります.
□適用例
ソートする配列
329 | 457 | 657 | 839 | 436 | 720 | 355 |
1桁目(1の位)に注目してソート
720 | 355 | 436 | 457 | 657 | 329 | 839 |
2桁目(10の位)に注目してソート.安定ソートを用いているので,下2桁はソートされています.
720 | 329 | 436 | 839 | 355 | 457 | 657 |
3桁目(100の位)に注目してソート.これで完全にソートされました.
329 | 355 | 436 | 457 | 657 | 720 | 839 |
□コード
各桁の値は,10進数ではなく2進数で計算しています.2進数では,各桁の値をシフト演算で求める事が出来る,という利点があります.
// 基数ソート
void radixSort(int[] a){
for(int i=0; i<32; i++)
// 安定ソートを用いてi桁目に関する配列をソートする
countingSort(a, i);
}
void countingSort(int[] a, int d){
int n=a.length;
int[] b=new int[n];
int[] c=new int[2];
for(int i=0; i<n; i++)
c[(a[i]>>d)&1]++;
c[0]--;
c[1]+=c[0];
for(int i=n-1; i>=0; i--)
b[c[(a[i]>>d)&1]--]=a[i];
System.arraycopy(b, 0, a, 0, n);
}
■バケットソート(bucket sort)
バケットソートでは,配列の各要素が区間[0, 1)に一様分布する実数だと仮定します.
配列aの要素数をnとします.
このソートでは,区間[0, 1)をn等分し,n個の要素を各バケットに分配します.
例えばn=10なら,
[0,0.1),[0.1,0.2),[0.2,0.3),[0.3,0.4),[0.4,0.5),[0.5,0.6),[0.6,0.7),[0.7,0.8),[0.8,0.9),[0.9,1).
その後,各バケットをソートし,順に繋げていくだけです.
この時のソートアルゴリズムは挿入ソートを用います.
挿入ソートの計算量はO(n2)(n個の要素に対して)ですが,色々あってほぼΘ(1)となります.さらに,バケット数がnですので,結果的にO(n)になるのですよ.
□コード
// バケットソート
void bucketSort(double[] a){
int n=a.length;
LinkedList<Double>[] list=new LinkedList[n];
for(int i=0; i<n; i++)
list[i]=new LinkedList<Double>();
for(int i=0; i<n; i++)
list[(int)(n*a[i])].add(a[i]);
for(int i=0, k=0; i<n; i++){
Double[] b=list[i].toArray(new Double[0]);
// リストlist[i]を挿入ソートでソートする
insertionSort(b);
for(double d : b)
a[k++]=d;
}
}
// 挿入ソート
void insertionSort(Double[] a){
int n=a.length;
for(int j=1; j<n; j++){
double key=a[j];
int i=j-1;
while(i>=0&&a[i]>key){
a[i+1]=a[i];
i--;
}
a[i+1]=key;
}
}
元ネタは,アルゴリズムイントロダクション第1巻より第9章 線形時間のソーティングでした.
0 件のコメント:
コメントを投稿