Skip to content

Bucket sort

Values spread evenly over a range drop into n buckets; each bucket holds only a few, so keeping each one sorted is cheap. O(n) on average, O(n²) when the values bunch.

Helps to know first
This topic

First, the picture

Ten values between 0 and 1, and ten buckets: 0.42 goes in bucket 4, 0.07 in bucket 0. Watch how little sorting is left once each value is in its bucket.

Sorting by dropping into buckets

Ten values between 0 and 1 in ten buckets

struct dnode {    double value;    struct dnode *next;}; void bucket_sort(double *a, int n){    struct dnode *bucket[BUCKETS] = { NULL };    for (int i = 0; i < n; i++) {        struct dnode *x = malloc(sizeof *x);        if (x == NULL)            exit(1);        x->value = a[i];        struct dnode **link = &bucket[(int)(a[i] * BUCKETS)];        while (*link != NULL && (*link)->value <= x->value)            link = &(*link)->next;        x->next = *link;        *link = x;    }    int k = 0;    for (int b = 0; b < BUCKETS; b++) {        struct dnode *p = bucket[b];        while (p != NULL) {            struct dnode *next = p->next;            a[k++] = p->value;            free(p);            p = next;        }    }}

main has started: a has room for ten doubles.

in buckets
0
read back
0

Output

0.00 / 29.30 s
Describe this picture

Two pictures stacked, with the code beside them, the line about to run marked. At the top, struct dnode *bucket[10]: ten bucket heads in a column, numbered 0 to 9, each with room for a chain of nodes running to the right. Below them, the ten cells of double a[10]: 0.42 0.07 0.93 0.48 0.15 0.61 0.44 0.88 0.03 0.56. The clip takes about 29 seconds. Each value in turn, marked i and tinted in a, has its bucket worked out in a line over the table, such as “(int)(0.42 × 10) = 4”, and that bucket lights. A new node with the value flies into its chain in order: when 0.44 arrives, bucket 4 already holds 0.42 and 0.48; 0.42 is tinted as the value steps past it, and 0.44 goes in between. Then the buckets are read from 0 to 9: each node’s value is written back into a at the marker k, and the node is freed and leaves its chain. Bucket 4, with three values, is the only one where a value had to step past another. At the end a reads 0.03 0.07 0.15 0.42 0.44 0.48 0.56 0.61 0.88 0.93 and every bucket is empty. Under the picture are one sentence on the current step and the readouts “in buckets” and “read back”. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as bucket-sort.step.

Buckets, chains and the average

For values in [0, 1) and n buckets, value v goes in bucket ⌊n·v⌋: with ten buckets, its first digit after the point. Bucket 0 takes [0, 0.1), bucket 1 [0.1, 0.2), and so on, so every value in bucket b is smaller than every value in bucket b + 1. Sort each bucket, read the buckets out in order, and the whole array is sorted.

Each bucket here is a linked list, a chain, kept sorted as values arrive: a new node is inserted after the values that are not larger than it, as in insertion sort.

If the values are spread evenly, each bucket gets about one value on average, and the insertions cost almost nothing. Dropping n values in and reading them out is then O(n) expected time. If the values bunch, one bucket gets them all, and its chain is an insertion sort of n values: O(n²).

The code and its cost

struct dnode {
    double value;
    struct dnode *next;
};

void bucket_sort(double *a, int n)
{
    struct dnode *bucket[BUCKETS] = { NULL };
    for (int i = 0; i < n; i++) {
        struct dnode *x = malloc(sizeof *x);
        if (x == NULL)
            exit(1);
        x->value = a[i];
        struct dnode **link = &bucket[(int)(a[i] * BUCKETS)];
        while (*link != NULL && (*link)->value <= x->value)
            link = &(*link)->next;
        x->next = *link;
        *link = x;
    }
    int k = 0;
    for (int b = 0; b < BUCKETS; b++) {
        struct dnode *p = bucket[b];
        while (p != NULL) {
            struct dnode *next = p->next;
            a[k++] = p->value;
            free(p);
            p = next;
        }
    }
}

link is a pointer to a link: first the address of the bucket’s head, then the address of a node’s next. The while moves it past the values that are not larger than x, and the last two lines splice x in where link points, whether that is the head, the middle or the end of the chain. This is the same pointer to a link as in Removing from a linked list.

The test <= puts a new value after equal ones, so equal values keep their input order.

The ten values

Bucket0123456789
Chain0.03 0.070.150.42 0.44 0.480.560.610.880.93

Buckets 2, 3 and 7 stay empty. buckets.c prints 0.03 0.07 0.15 0.42 0.44 0.48 0.56 0.61 0.88 0.93. Only two values had to step past another in their chain: 0.48 past 0.42, and later 0.44 past 0.42.

One detail of floating point: 0.07 × 10 is 0.7000000000000001 in a double, not 0.7, but (int) cuts it down to 0 all the same.

Worked example

Bunched values. Ten values all between 0.40 and 0.50 land in bucket 4. In ascending order each new value walks the whole chain: 0 + 1 + … + 9 = 45 comparisons, the worst case of insertion sort. bunched.c counts the value comparisons in the while: spread: 4 comparisons for the ten values above, bunched, ascending: 45 comparisons for 0.40, 0.41, …, 0.49.

Values from 0 to 1,000. Divide each by 1,001, one more than the largest possible value, before multiplying by the number of buckets. Dividing by 1,000 would send the value 1,000 to bucket ⌊n · 1⌋ = n, one past the last bucket.

Two checks. Which bucket does 0.999 go in? (9.) Which input makes bucket sort slow? (Values bunched in one bucket.)

Where it’s used

Values known to be spread evenly, such as random numbers or hash values.

Histograms, which are the first half of bucket sort: drop values into ranges and count them.

Hash tables with chaining use the same chains, but to find a key rather than to sort (Hash tables with chaining).

Reference card

Code or ideaMeaning
bucket of v⌊n·v⌋ for v in [0, 1)
struct dnode **linkthe address of a head or a next: splice in where it points
each chainkept sorted by insertion
evenly spreadO(n) expected
bunchedO(n²): one chain does an insertion sort
stableyes, with <=

Platform: C11, gcc on x86-64 Linux: int 4 bytes, double 8, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look