ソートについて

はじめに

n個の要素がある配列をソートする方法を挙げていく。

バブルソート

バブルソートは最も簡単な方法です。
単純に隣同士を比較して順番が逆なら入れ替えるという操作を
一番目の要素から順番に行っていきます。
この操作をn回行うと、要素の中でもっとも大きな要素が一番n番目に来ます。
そして、また一番目の要素からn-1回操作を行います。
これで二番目に大きな要素がn-1番目に来ます
これを繰り返してソートします。
サンプルコード
//バブルソート
void BubbleSort(int Data[], int n)
{
	int BeginPlace, ComparePlace;
	//比較を始める位置を最初からn-1まで変えていく
	for(BeginPlace = 0;BeginPlace < n;BeginPlace++)
	{
		//n - BeginPlaceまで比較する
		for(ComparePlace = 1;ComparePlace < n - BeginPlace;ComparePlace++)
		{
			//右のほうが小さかったら交換する
			if(Data[ComparePlace - 1] > Data[ComparePlace])
			{
				Swap(&(Data[ComparePlace - 1]), &(Data[ComparePlace]));
			}
		}
	}

	return;
}
//バブルソート改良版
void BubbleSortImprovedVersion(int Data[], int n)
{
	int BeginPlace, ComparePlace, LastSwapPlace = n, Temp = n;
	//比較を始める位置を最初からn-1まで変えていく
	for(BeginPlace = 0;BeginPlace < n;BeginPlace++)
	{
		//最後に交換した場所まで比較する
		for(ComparePlace = 1;ComparePlace < LastSwapPlace;ComparePlace++)
		{
			//右のほうが小さかったら交換する
			if(Data[ComparePlace - 1] > Data[ComparePlace])
			{
				Swap(&(Data[ComparePlace - 1]), &(Data[ComparePlace]));
				//交換した場所を覚える
				Temp = ComparePlace;
			}
		}
		//最後に交換した場所を覚える
		LastSwapPlace = Temp;
	}
	return;
}

シェーカーソート/双方向バブルソート

バブルソートの改良型です。
基本的に行う処理はバブルソートと同じで
並んだ数の隣同士を比較して、順番が逆ならば入れ替えて行きます。
バブルソートの時は、この処理を常に同じ方向へ行っていましたが、
シェーカーソートの場合は、両端からそろえていきます。
この方法の場合、速度がデータの状態に左右されにくく、
常に安定した速度で動作させることができます。
しかし実際のところは、バブルソートとあまり速度が変わりません。
サンプルコード
//シェーカーソート/双方向バブルソート
void ShakerSort(int *Data, int n)
{
	int ComparePlace, LastSwapPlace;
	int LeftBeginPlace, RightBeginPlace;

	LeftBeginPlace = 0;
	RightBeginPlace = n - 1;
	LastSwapPlace = n - 1;
	//比較を始める場所が交差するまで
	//左右から交互にソートを繰り返す
	while(LeftBeginPlace  < RightBeginPlace)
	{
		//右からのソートで最後に交換した場所から
		//左からのソートで最後に交換した場所まで
		//右側の要素と比較していく
		for(ComparePlace = LeftBeginPlace;ComparePlace < RightBeginPlace;ComparePlace++)
		{
			//右のほうが小さかったら交換する
			if(Data[ComparePlace] > Data[ComparePlace + 1])
			{
				Swap(&(Data[ComparePlace]), &(Data[ComparePlace + 1]));
				LastSwapPlace = ComparePlace;
			}
		}
		//次の右からのソートの開始位置を左からのソートで最後に交換した場所にする
		RightBeginPlace = LastSwapPlace;
		//左からのソートで最後に交換した場所から
		//右からのソートで最後に交換した場所まで
		//左側の要素と比較していく
		for(ComparePlace = RightBeginPlace;ComparePlace > LeftBeginPlace;ComparePlace--)
		{
			//左のほうが大きかったら交換する
			if(Data[ComparePlace - 1] > Data[ComparePlace])
			{
				Swap(&(Data[ComparePlace - 1]), &(Data[ComparePlace]));
				LastSwapPlace = ComparePlace;
			}
		}
		//次の左からのソートの開始位置を右からのソートで最後に交換した場所にする
		LeftBeginPlace = LastSwapPlace;
	}
	return;
}

選択ソート/馬鹿ソート/セレクションソート

データの中から一番小さい値を探して、それを1番目の値と交換し
次に2番目、3番目から最後まで繰り返して行う、
極めて単純でわかりやすいソートです。
それぞれの数値を比較する回数はバブルソートと変わらりませんが、
数値を交換する回数が少ないので、バブルソートよりも高速です。
サンプルコード
//選択ソート/馬鹿ソート/セレクションソート
void SelectionSort(int Data[], int n)
{
	int BeginPlace, ComparePlace, min;
	//比較を始める位置を0番目から(n - 1)番目まで動かしていく
	for(BeginPlace = 0;BeginPlace < n;BeginPlace++)
	{
		min = BeginPlace;
		//最小の要素を探す
		for(ComparePlace = BeginPlace + 1;ComparePlace < n;ComparePlace++)
		{
			//今まで見つかった最小のものよりも
			//小さいものが見つかったら交換する
			if(Data[min] > Data[ComparePlace])
			{
				Swap(&(Data[min]), &(Data[ComparePlace]));
			}
		}
	}
	return;
}

挿入ソート

最初にデータの1番目と2番目をソートします。
次に3番目のデータをソート済みの適切な場所に挿入し、
4番目以降も、ソート済みの場所に挿入していくことを繰り返して行きます。
最悪計算時間が遅いですが、アルゴリズムが単純で簡単なため、
しばしば用いられる手法です。
サンプルコード
//挿入ソート
void InsertionSort(int Data[], int n)
{
	int BeginPlace, ComparePlace;
	//比較を始める位置を1から順番にn - 1まで動かしていく
	for(BeginPlace = 1;BeginPlace < n;BeginPlace++)
	{
		//BeginPlace番目の要素から1番目の要素まで順番に左の要素と比較していく
		for(ComparePlace = BeginPlace;ComparePlace > 0;ComparePlace--)
		{
			//左のほうが大きかったら交換する
			if(Data[ComparePlace - 1] > Data[ComparePlace])
			{
				Swap(&(Data[ComparePlace - 1]), &(Data[ComparePlace]));
			}
		}
	}
}

シェルソート

挿入ソートの改良型。
ほぼ整列されたデータに対しては高速だが、隣り合った要素同士しか
交換しないため、あまり整列されていないデータに対して低速という欠点を
克服するために考え出されたものです。
最初に適当な間隔をあけてデータ列をソートした後に、挿入ソートを適用します。
実際には、その間隔をだんだん縮めていきながら挿入ソートを適用し続け、
最後に間隔が1(実質なし)となった時点で挿入ソートを適用し、終了します。
また、実際にソートの速さは挿入ソートよりも早いです。
サンプルコード

コムソート

挿入ソートからシェルソートに改良されたときと同じ改良を
バブルソートに施したものです。
最初に、データ列を h=n/1.3 という間隔でソートした後、間隔をh=h/1.3
として縮め、ソートすることを繰り返し、h=1になった時点でバブルソートを
適用し、終了します。
1.3という数値は、経験則によるものらしいです。
サンプルコード
//コムソート
void CombSort(int Data[], int n)
{
	int Gap, i;
	BOOL IsSwap;

	Gap = n;
	IsSwap = 1;
	//Gapが1より大きくてループの中で一回以上交換が行われている間
	while(Gap > 1 || IsSwap)
	{
		//1.3で割る
		Gap = (Gap * 10) / 13;
		//Gapが9か10なら収束するように11にする
                //これにより高速化が図れる
		if(Gap == 9 || Gap == 10)
		{
			Gap = 11;
		}
		IsSwap = 0;
		//間隔Gapでバブルソート
		for(i = 0;i < n - Gap;i++)
		{
			//右のほうが小さかったら交換する
			if(Data[i] > Data[i + Gap])
			{
				Swap(&(Data[i]), &(Data[i + Gap]));
				IsSwap = 1;
			}
		}
	}
	return;
}

ヒープソート

二分木を用いて行うソートです。

  • 図 二分木の例

  •                   H   I
                       \ /
          D     E F     G
           \   /   \   /
            \ /     \ /
             B       C
              \     /
               \   /
                \ /
                 A
    
    上図ような構造のものを、二分木と言い、上の図でアルファベット表記の部分を、節と言い、
    この部分にデータを入れていくというようなイメージです。
    データは常に、たとえば上の図において、A<C,A<B,C<F・・・というような関係が
    成り立つように並べていきます。つまりこのとき、Aがデータ中の最小値であるといえます。
    次に、データの最小値を二分木の中から取り出し、取り出されてない他のデータで
    再び同じように二分木を作っていきます。そしてまた最小値を取り出して・・・
    というようにして、データをすべて取り出すまで続けていきます。
    これは、クイックソート並に早く、データの並びによってソートの速度が大きく変動する
    というようなことも起こらないという、優れたものです。
    サンプルコード
    //ヒープソート
    void HeapSort(int Data[], int n)
    {
    	int Count, Son, Parent;
    	int Number;//ヒープの要素数
    	int *Heap;
    	Heap = (int*)malloc(sizeof(int) * n);//ヒープに見立てた配列を作る
    	Number = 0;//ヒープを空にする
    	//Data[]から一つずつ順番に取り出して
    	//根元に加えてヒープを作っていく
    	for(Count = 0;Count < n;Count++)
    	{
    		Heap[Number] = Data[Count];//ヒープの一番後ろに加える
    		Number++;//ヒープの要素数を一つ増やす
    		Son = Number;//加えた要素を子とする
    		Parent = Son / 2;//その親
    		//入れ替えが起こらなくなるか、根元に到達するまで
    		//加えた要素とその親とを比較していく
    		while(Son > 1 && Heap[Son - 1] < Heap[Parent - 1])
    		{
    			//親のほうが大きかったら入れ替える
    			Swap(&(Heap[Son - 1]), &(Heap[Parent - 1]));
    			//一つ上に移る
    			Son = Parent;//今回の親を次回の子にする
    			Parent = Son / 2;//次回の親を決める
    		}
    	}
    	//ツリーから要素を根元から順番に空になるまで
    	//取り出していく
    	for(Count = 0;Number > 0;Count++)
    	{
    		Data[Count] = Heap[0];//根元を取り出す
    		Number--;//ヒープの要素数を一つ減らす
    		Heap[0] = Heap[Number];//ヒープの最後の要素を根元に持ってくる
    		Parent = 1;//根元を親とする
    		Son = Parent * 2;//その子
    		//入れ替えが起こらなくなるか、最後に到達するまで
    		//親と二つの子の内小さいほうとを比較していく
    		while(Son <= Number)//最後に到達していない間
    		{
    			//まだ次があって左の子の方が大きかったら
    			if(Son + 1 <= Number && Heap[Son - 1] > Heap[Son])
    			{
    				Son++;//Sonを右の子にする
    			}
    			//子より親が大きかったら入れ替える
    			if(Heap[Parent - 1] > Heap[Son - 1])
    			{
    				Swap(&(Heap[Parent - 1]), &(Heap[Son - 1]));
    			}
    			//一つ下に移る
    			Parent = Son;//今回の子を次回の親にする
    			Son = Parent * 2;//次回の子を決める
    		}
    	}
    	free(Heap);//ヒープを削除する
    	return;
    }
    

    マージソート

    データを二分割(二等分)して、各分割データ中でソートし、さらにそのあと
    データをつなげ、全体でソートしていくという方法です。
    場合によっては、クイックソートよりも高速ですが、データn個分の外部記憶を必要とします。
    サンプルコード
    //マージソート/併合ソート
    void MergeSort(int Data[], int n)
    {
    
    	int Center, Left, Right, TempIndex;
    	int *Temp;
    	//要素が一つしかないならすでにソートされているのでそのまま戻る
    	if(n == 1)
    	{
    		return;
    	}
    	//そうでなければ二つに分割してそれぞれをソートしてマージ(併合)する
    	else
    	{
    		//二つに分けてそれぞれをソートする
    		{
    			Center = n / 2;
    			MergeSort(Data, Center);
    			MergeSort(Data + Center, n - Center);
    		}
    		//ソートされた二つの配列をマージ(併合)する
    		{
    			Left = Right = TempIndex = 0;
    			Temp = (int*)malloc(sizeof(int) * n);
    			//両方の配列にまだ比較していない要素がある間
    			//両方の配列の同じ位置の要素の内小さいほうをTemp[]に入れていく
    			while(Left < Center && Right < n - Center)
    			{
    				if(Data[Left] > (Data + Center)[Right])
    				{
    					Temp[TempIndex++] = (Data + Center)[Right++];
    				}
    				else
    				{
    					Temp[TempIndex++] = Data[Left++];
    				}
    			}
    			//片方の配列を全て比較してしまったら
    			//もう片方の配列の残りの要素を全てTemp[]に入れる
    			{
    				//左側の要素が余ったとき
    				while(Left < Center)
    				{
    					Temp[TempIndex++] = Data[Left++];
    				}
    				//右側の要素が余ったとき
    				while(Right < n - Center)
    				{
    					Temp[TempIndex++] = (Data + Center)[Right++];
    				}
    			}
    			//Temp[]の要素をData[]に移す
    			memcpy(Data, Temp, sizeof(int) * n);
    			free(Temp);
    		}
    	}
    	return;
    }
    

    クイックソート

    データの中から、適当な数(できればデータの中央値)を選び、
    その数より大きいものを一方、小さいものを他方に分ける、
    そして二分割されたデータの中で、同じようにソートしていくという方法です。
    一般的でもっとも早いソートといわれていますが、
    データの並びや数によっては、必ずしも早いわけではありません。
    最初に数を選ぶ段階で、ある程度、遅くなることを抑えることができます。
    サンプルコード
    //クイックソート
    void QuickSort(int Data[], int n)
    {
    	int Pivot, LeftComparePlace, RightComparePlace;
    
    	//要素が一つしかないならすでにソートされているのでそのまま戻る
    	if(n == 1)
    	{
    		return;
    	}
    	//そうでなければ分割値を決めて分割以上と未満の二つの配列に分割して
    	//それぞれをソートする
    	else
    	{
    		//配列の中央の要素を分割値とする
    		Pivot = Data[n / 2];
    		LeftComparePlace = 0;
    		RightComparePlace = n - 1;
    
    		//分割値以上と未満の二つの配列に分割する
    		for(;;)
    		{
    			//左から分割値以上の要素を探す
    			while(Data[LeftComparePlace] < Pivot)
    			{
    				LeftComparePlace++;
    			}
    			//右から分割値未満の要素を探す
    			while(Data[RightComparePlace] >	Pivot)
    			{
    				RightComparePlace--;
    			}
    			//探す場所場交差していたら分割を終える
    			if(LeftComparePlace >= RightComparePlace)
    			{
    				break;
    			}
    			//見つかった二つの要素を交換する
    			Swap(&(Data[LeftComparePlace]), &(Data[RightComparePlace]));
    			//比較する場所を一つ進める
    			LeftComparePlace++;
    			RightComparePlace--;
    		}
    		//分割した二つの配列をそれぞれソートする
    		QuickSort(Data, LeftComparePlace);
    		QuickSort(Data + LeftComparePlace, n - LeftComparePlace);
    	}
    	return;
    }
    

    バケツソート/バケットソート/分布数えソート/ビンソート

    整列したいデータが何種類かあるとき、
    各種類のデータが何個あるかを数え、一気に並べ替えるソート。
    計算速度は高速ですが、データ分の外部記憶を必要とします。
    サンプルコード
    //バケツソート/バケットソート/分布数えソート/ビンソート
    //要素の範囲 0〜n
    void BucketSort(int Data[], int n)
    {
    	int BucketIndex, Count, PutPlace;
    	int *Bucket;
    	Bucket = (int*)malloc(sizeof(int) * n);//n個のバケットを作る
    	//バケットを全て空にする
    	for(BucketIndex = 0;BucketIndex < n;BucketIndex++)
    	{
    		Bucket[BucketIndex] = 0;
    	}
    	//順番に数えていく
    	for(BucketIndex = 0;BucketIndex < n;BucketIndex++)
    	{
    		Bucket[Data[BucketIndex]]++;
    	}
    	//先頭のバケットからバケットに入っている数だけ
    	//配列に入れていく
    	PutPlace = 0;//入れる場所を先頭にしておく
    	for(BucketIndex = 0;BucketIndex < n;BucketIndex++)//バケットの数だけ繰り返す
    	{
    		//バケットに入っている数だけ入れる
    		for(Count = 0; Count < Bucket[BucketIndex];Count++)
    		{
    			Data[PutPlace + Count] = BucketIndex;
    		}
    		PutPlace += Bucket[BucketIndex];//入れる場所を入れた数だけ進める
    	}
    	free(Bucket);//バケットを削除する
    	return;
    }
    

    基数ソート/ラディックスソート

    この方法は、データの種類が有限で、最大値と最小値が
    はっきりしていることを仮定しています。つまり、すべてのデータが、
    [三桁の整数]や、[二文字のアルファベット]というように決まっているときに
    適用できることになります。
    まず、入力されたデータを、いくつかのキーに分類します。
    たとえば、3桁の整数ならば、一の位、十の位、百の位という風に分類します。
    次に、それぞれのキーについて、下位のキーからソートしていきますが、
    このとき、値の範囲が有限であるため、バケットソートが効率的です。
    この時のソートの種類はバケットソートでなくても構いませんが、計算速度の遅い
    方法を用いるともちろん遅くなってしまうので注意が必要です。
    サンプルコード
    //基数ソート/ラディックスソート
    //要素の範囲 0〜n
    void RadixSort(int Data[], int n)
    {
    	int *Temp;
    	int Bucket[10];
    	int Figure, Digit, Radix, Count;
    	Temp = (int*)malloc(sizeof(int) * n);
    	//桁数を求める
    	Figure = (int)log10(n) + 1;
    
    	//最下位から順番にバケットソートでソートしていく
    	for(Digit = 0, Radix = 1;Digit < Figure;Digit++, Radix *= 10)
    	{
    		//Bucket[]を'0'で埋める
    		for(Count = 0;Count < 10;Count++)
    		{
    			Bucket[Count] = 0;
    		}
    		//いくつあるか数える
    		for(Count = 0;Count < n;Count++)
    		{
    			Bucket[((Data[Count] / Radix) % 10)]++;
    		}
    
    		//そのBucketの一つ前のBucketを加える
    		for(Count = 1;Count < 10;Count++)
    		{
    			Bucket[Count] += Bucket[Count - 1];
    		}
    		//Dataの要素をTemp[]に入れていく
    		for(Count = n - 1;Count >= 0;Count--)
    		{
    			Temp[--Bucket[((Data[Count] / Radix) % 10)]] = Data[Count];
    		}
    		//Temp[]の要素を全てData[]へ移す
    		for(Count = 0;Count < n;Count++)
    		{
    			Data[Count] = Temp[Count];
    		}
    	}
    	free(Temp);
    	return;
    }
    

    逆写像ソート

    サンプルコード
    //逆写像ソート
    void MapSort(int Data[], int n)
    {
    	int Count, j, x;
    	int *Index, *Temp, *OutPut;
    	Index = (int*)malloc(sizeof(int) * n);
    	Temp = (int*)malloc(sizeof(int) * n);
    	OutPut = (int*)malloc(sizeof(int) * n);
    	//-1で埋める
    	for(Count = 0; Count < n; Count++)
    	{
    		Index[Count] = -1;
    	}
    	for(Count = n - 1;Count >= 0;Count--)
    	{
    		Temp[Count] = Index[Data[Count]];
    		Index[Data[Count]] = Count;
    	}
    	j = 0;
    	for(x = 0;x < n;x++)
    	{
    		Count = Index[x];
    		while(Count >= 0)
    		{
    			OutPut[j] = Data[Count];
    			j++;
    			Count = Temp[Count];
    		}
    	}
    	//OutPut[]の要素を全てData[]へ移す
    	for(Count = 0;Count < n;Count++)
    	{
    		Data[Count] = OutPut[Count];
    	}
    	free(Index);
    	free(Temp);
    	free(OutPut);
    	return;
    }
    

    バイトニックソート

    バイトニックソートはバイトニック列を利用してソートします。

    図1 int Data[] = {2, 4, 6, 7, 10, 8, 5, 3, 1};
    バイトニック列
    図2 int Data[] = {7, 10, 8, 5, 3, 1, 2, 4, 6};
    バイトニック列
    図1のように増えていく部分と減っていく部分がある列をバイトニック列といいます。
    また、下のように要素をシフトさせると図1のようになるものもバイトニック列といいます。
    バイトニックソートのやり方は、マージソートのように
    小さなブロックに分割してソートしていきます。
    その時に昇順にソートされたブロックと
    降順にソートされたブロックが交互に並ぶようにしていきます。
    こうすることで、隣り合った二つのブロックをバイトニック列とみなすことができます。
    バイトニック列なら大きい値と小さい値の二つのグループに簡単に分けることができます。
    そして、二つに分けたグループもそれぞれバイトニック列になっています。
    こうして、グループをどんどん分けていくことでその二つのブロックをソートして、
    一つのブロックにして、ブロックのサイズを大きくしていきます。
    そして、ブロックのサイズがnになったらソート終了です。
    バイトニックソートは比較回数が少ないのが特徴です。
    バイトニックソートは2のn乗個の要素数でないとソートできません。
    そこで下のサンプルコードでは適当に要素を入れて
    要素数を増やしておいて後で取り除いています。
    サンプルコード
    //バイトニックソート
    //2のn乗個以外でも動く
    void BitonicSort(int Data[], int n)
    {
    	int Count, ComparePlace, BlockSize, Interval, TempSize, Power;
    	int Direction;//昇順 = 1 降順 = -1
    	int *Temp;
    	int MaxNumber = n;
    	int DataSize = n;
    	int Block;
    
    	//作業用配列の要素数をData[]の要素数以上で最小の2のn乗にする
    	for(Count = 1, Power = 0;Count < n;Count *= 2)
    	{
    		Power++;
    	}
    	TempSize = (int)pow(2, Power);
    	Temp = (int*)malloc(sizeof(int) * TempSize);//作業用の配列Temp[TempSize]を作る
    	//作業用配列にデータを移す
    	for(Count = 0;Count < n;Count++)
    	{
    		Temp[Count] = Data[Count];
    	}
    	//増えた部分に一番大きな数を入れていく
    	for(Count = n;Count < TempSize;Count++)
    	{
    		Temp[Count] = MaxNumber;
    	}
    	//ブロックサイズを2からnまで大きくしていく
    	for(BlockSize = 2;BlockSize <= n;BlockSize *= 2)
    	{
    		Direction = 1;//最初の向きを昇順にする
    		n += n % BlockSize;//要素数をBlockSizeで割り切れるように増やす
    		//ブロック(2のn乗)ごとにソートする
    		for(Count = 0;Count < n / BlockSize;Count++)//ブロックの数だけ繰り返す
    		{
    			//BlockSize / 2から1まで間隔を半分に狭めながら並べ替える
    			for(Interval = BlockSize / 2;Interval >= 1;Interval = Interval / 2)
    			{
    				//並べ替える
    				for(ComparePlace = 0;ComparePlace + Interval < BlockSize;ComparePlace++)
    				{
    					//左か右が大きかったら交換する
    					if(Temp[BlockSize * Count + ComparePlace] * Direction > Temp[BlockSize * Count + ComparePlace + Interval] * Direction)
    					{
    						Swap(&(Temp[BlockSize * Count + ComparePlace]), &(Temp[BlockSize * Count + ComparePlace + Interval]));
    					}
    					//同じ場所を比較しないようにする
    					if(Interval == ComparePlace + 1)
    					{
    						ComparePlace+= Interval;
    					}
    				}
    			}
    			//1ブロックソートするごとに
    			//ソートする向きを反転する
    			Direction *= -1;
    		}
    	}
    	//ソートしたデータをData[]に戻す
    	for(Count = 0;Count < DataSize;Count++)
    	{
    		Data[Count] = Temp[Count];
    	}
    	free(Temp);//作業用配列を削除する
    	return;
    }
    

    考察

    各ソートのスピードは次のとおりでした(単位:ms)

    アルゴリズム






    n

    バブルソート バブルソート改良版 シェーカーソート 選択ソート シェルソート 挿入ソート マージソート クイックソート ヒープソート バケットソート 基数ソート コムソート 逆写像ソート バイトニックソート
    100022221110000001
    10000214220145219948441104106
    5000056845731379753622409251723681218243
    1000002157522255150461836891078604511115251161384
    500000------26656165827597115552
    1000000------539115442166622052591298
    10000000-------1248-204-24452826-

    実験用PCのスペック
    コンパイラMicrosoft Visual C++ 7.1
    メモリ512MB
    CPUPentium (R) 4 3.20GHz
    OSMicrosoft Windows XP Professional SP2

    見てわかる通り、バケットソートが圧倒的でした。
    値の範囲を0〜nにしたのが、その原因のようです。
    その後何も考えずにバケットソートで要素数一億に挑戦してみたら
    メモリが溢れかけ、およそ3分かかりました。
    バケットソートではデータ分の外部記憶を必要とすることをすっかり忘れておりました。
    単純計算、要素数一億のint型データだったので、
    100000000 * 4 = 400000000 = 400MB のメモリを使用したことになります。
    実験したのPCのメモリは512MBだったことを考えると、かなり危なかったです。