バケットソート
Basisカウントソートは各整数キーを直接インデックスにマッピングすることで線形時間を達成する。バケットソートはこのアイデアを一般化する:キー値ごとに 1 スロットの代わりに少数のバケットを確保し、それぞれが値の範囲をカバーし、小さなバケットを独立してソートする。値が範囲全体に一様に分布しているとき、各バケットは平均 個の要素を受け取り、すべてのバケットのソートの合計コストは だ。
アルゴリズム
に一様分布する 個の浮動小数点値が与えられたとする:
- 作成 — 個の空のバケットを作る。バケット は区間 をカバーする。
- 分配 — 各要素 をバケット に入れる。
- ソート — 各バケットを挿入ソートでソートする(バケットは平均して小さい)。
- 結合 — バケットを順番に結合する。
const std = @import("std");
fn insertionSortF64(arr: []f64) void {
var i: usize = 1;
while (i < arr.len) : (i += 1) {
const key = arr[i];
var j = i;
while (j > 0 and arr[j - 1] > key) : (j -= 1) {
arr[j] = arr[j - 1];
}
arr[j] = key;
}
}
/// arr をインプレースでソートする。すべての値は [0, 1) 内でなければならない。
fn bucketSort(arr: []f64, allocator: std.mem.Allocator) !void {
const k = arr.len;
if (k == 0) return;
// k 個のバケットを確保する。各バケットは拡張可能なリスト。
const buckets = try allocator.alloc(std.ArrayList(f64), k);
defer {
for (buckets) |*b| b.deinit();
allocator.free(buckets);
}
for (buckets) |*b| b.* = std.ArrayList(f64).init(allocator);
// 要素をバケットに分配する。
for (arr) |x| {
const idx: usize = @intFromFloat(x * @as(f64, @floatFromInt(k)));
try buckets[@min(idx, k - 1)].append(x);
}
// 各バケットをソートして書き戻す。
var pos: usize = 0;
for (buckets) |*b| {
insertionSortF64(b.items);
for (b.items) |x| {
arr[pos] = x;
pos += 1;
}
}
}
@min(idx, k - 1) のガードは浮動小数点の丸めにより x がちょうど 1.0 になるエッジケースを処理し、インデックスを最後の有効なバケットにクランプする。
なぜ バケットか?
とすることで一様分布のもと各バケットの期待負荷が 1 要素になる。以下の分析はこの選択を使うが、任意の固定 でも動作する — を小さくするとバケットが少なく(大きく)なり、メモリが限られている場合に好ましい。
平均ケースの分析
個の入力値が から独立一様に抽出されると仮定する。 をバケット の要素数とする。期待されるソートコストは:
各要素は確率 でバケット に入るので、 は 分布に従う。この分布では:
個すべてのバケットで合計すると:期待合計時間は だ。
最悪ケースの挙動
一様分布の仮定が重要だ。 個すべての要素が同じバケットに入ると、そのバケットは挿入ソートで かかり、合計で になる。バケットソートは値がバケット全体に広がっている場合のみ効率的だ。
他のキー型への拡張
の範囲は慣習的だが必須ではない。任意の範囲 は正規化によって対応できる:。整数キーに対して各キー値に 1 要素のバケットソートはカウントソートに退化する — カウントソートは各バケットが最大 1 つの異なる値を持つバケットソートの特殊ケースだ。
計算量まとめ
| 指標 | 計算量 |
|---|---|
| 期待時間 | (一様入力、) |
| 最悪ケース時間 | |
| 余分な空間 | |
| 安定 | はい(バケット内の挿入ソートが順序を保つ) |
余分な空間はバケット構造とバケットの内容をカバーする。 のとき だ。
バケットソートを使う場面
- 入力が既知の範囲でほぼ一様に分布している。
- の余分なメモリを使える。
- キー型がバケットインデックスへの高速なマッピングをサポートしている(浮動小数点の乗算、整数の除算)。
分布が偏っているとバケットソートは劣化する。整数キーで小さな範囲ならカウントソートを、汎用の比較ベースのニーズにはクイックソートやマージソートを使う。
まとめ
- バケットソートはキー範囲によって要素を バケットに分配し、各バケットをソートして結合する。
- バケットと一様分布入力で期待時間は だ。
- すべての要素が 1 バケットに集中すると最悪ケース時間は になる。
- 空間は ;安定性はバケットごとのソートアルゴリズムによる(挿入ソートは安定)。
- カウントソートは各異なるキー値に 1 スロットを割り当てたバケットソートの特殊ケースだ。