Non-Comparison Sorting: Counting, Radix, Bucket

Counting, Radix, Bucket sort

Read: ~30 min Intermediate 6 practice problems Live code runner

1. The Ω(n log n) Lower Bound — and How to Beat It

Any comparison-based sort has lower bound Ω(n log n) — provable from the n! possible orderings and a binary decision tree of depth log₂(n!) ≈ n log₂ n. But if we don't compare — if we use the values themselves as indices — we can sort in O(n).

যেকোনো comparison-based sort-এর lower bound n log n। কিন্তু যদি input-এর কাঠামো (range, digits, distribution) ব্যবহার করতে পারি, তখন O(n)-ও সম্ভব। Counting / Radix / Bucket — তিনটিই এই trick-এর প্রয়োগ।

2. Counting Sort — O(n + k)

For integers in range [0, k]: count how many of each value, then write back. Stable variant uses prefix sums to assign output positions.

input a: 2 5 3 0 2 3 0 3 count[v]: 2 0 2 3 0 1 output: 0 0 2 2 3 3 3 5 Figure 14.1 — Counting sort: tally each value's occurrences, then emit in order.
counting_sort.cpp
#include <bits/stdc++.h>
using namespace std;

vector<int> countingSort(vector<int>& a, int k) {
    vector<int> cnt(k+1, 0);
    for (int x : a) cnt[x]++;
    for (int i = 1; i <= k; i++) cnt[i] += cnt[i-1];   // prefix sums
    vector<int> out(a.size());
    for (int i = a.size()-1; i >= 0; i--)        // reverse → stability
        out[--cnt[a[i]]] = a[i];
    return out;
}

int main() {
    vector<int> a = {2,5,3,0,2,3,0,3};
    for (int x : countingSort(a, 5)) cout << x << " ";
}
When NOT to use If k is huge (e.g. 32-bit ints), counting sort needs O(2³²) memory. Use radix sort instead.

3. Radix Sort — O(d · (n + b))

Sort by digit (or byte) at a time, least significant first (LSD), using counting sort as the inner stable sort. d = number of digits, b = base (10 for decimal, 256 for byte). For 32-bit ints with byte radix: 4 passes of counting sort over base 256 → O(n).

একটি সংখ্যার প্রতিটি অঙ্ক ধরে ধরে sort করুন — সর্বনিম্ন significant অঙ্ক থেকে শুরু করে। প্রতিটি ধাপে stable counting sort ব্যবহার করতে হয়। যদি stability না থাকে, radix কাজ করবে না।
radix_byte.cpp
#include <bits/stdc++.h>
using namespace std;

void radixByte(vector<uint32_t>& a) {
    int n = a.size();
    vector<uint32_t> tmp(n);
    for (int shift = 0; shift < 32; shift += 8) {
        int cnt[257] = {};
        for (auto x : a) cnt[1 + ((x >> shift) & 0xff)]++;
        for (int i = 1; i < 256; i++) cnt[i+1] += cnt[i];
        for (auto x : a) tmp[cnt[(x >> shift) & 0xff]++] = x;
        a.swap(tmp);
    }
}

int main() {
    vector<uint32_t> a = {170, 45, 75, 90, 802, 24, 2, 66};
    radixByte(a);
    for (auto x : a) cout << x << " ";
}

4. Bucket Sort — Uniform Distributions

Distribute n elements into n buckets by a hash of the value (e.g. floor(x · n) for x ∈ [0, 1)), sort each bucket, concatenate. Average O(n) when input is uniform.

✅ Use Counting / Radix / Bucket

  • Integers in a small / known range
  • Fixed-width keys (digits, bytes, IPs)
  • Uniformly distributed floats in [0, 1)

⚠️ Stick to comparison sort

  • Custom comparator (sort by name, then by age)
  • Huge value range with few items
  • Generic objects with no integer key

5. Practice Problems

  1. Sort an array of phone numbers (10-digit ints) using LSD radix.
    10-digit ফোন নম্বরগুলো LSD radix দিয়ে sort করুন।
    ✨ Show Answer (উত্তর দেখুন)

    Approach: use base 10 with 10 passes (one per digit), or base 256 with 4 passes if stored as uint64_t. The byte-radix code above works as-is.

  2. Sort grades A, B, C, D, F by counting (note: only 5 categories).
    ৫টি grade — counting sort দিয়ে sort করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a2.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        string g = "BCAFADBCBAF";
        int cnt[26] = {};
        for (char c : g) cnt[c-'A']++;
        string out;
        for (int i = 0; i < 26; i++) out.append(cnt[i], 'A'+i);
        cout << out;
    }
  3. Bucket-sort floats uniformly distributed in [0, 1).
    [0,1)-এ uniformly বিতরণিত float-গুলো bucket sort করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a3.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        vector<double> a = {0.42, 0.32, 0.23, 0.52, 0.25, 0.47, 0.51};
        int n = a.size();
        vector<vector<double>> bk(n);
        for (double x : a) bk[(int)(x * n)].push_back(x);
        vector<double> out;
        for (auto& b : bk) {
            sort(b.begin(), b.end());
            for (double x : b) out.push_back(x);
        }
        for (double x : out) cout << x << " ";
    }
  4. Maximum gap between consecutive sorted values, in O(n) (Pigeonhole + bucket).
    Sort না করেই — পরপর দুই sorted value-এর সর্বোচ্চ পার্থক্য O(n)-এ বের করুন।
    ✨ Show Answer (উত্তর দেখুন)

    Approach: with n elements, divide [min, max] into n−1 buckets of size (max−min)/(n−1). Pigeonhole: maximum gap can't lie inside a bucket — must lie between max-of-bucket-i and min-of-bucket-(i+1).

  5. Sort a string by character frequency (descending), breaking ties by smaller char first.
    Frequency descending — counting sort variant দিয়ে sort করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a5.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        string s = "tree";
        int cnt[128] = {};
        for (char c : s) cnt[(int)c]++;
        vector<pair<int,char>> v;
        for (int i = 0; i < 128; i++) if (cnt[i]) v.push_back({-cnt[i], (char)i});
        sort(v.begin(), v.end());
        string out;
        for (auto& [c, ch] : v) out.append(-c, ch);
        cout << out;
    }
  6. Why does radix sort fail if the inner sort is NOT stable? Give a small example.
    Radix sort-এর inner sort stable না হলে কেন ভেঙে পড়ে?
    ✨ Show Answer (উত্তর দেখুন)

    Answer: after sorting by units, ties (same units digit) preserve their relative order so a tie-break by tens digit later still respects units. If the inner sort is unstable, two values with the same units digit could be reordered randomly, then sorting by tens won't recover the units-level order. Example: sort [21, 13, 23, 11] by units gives [11, 21, 13, 23]; an unstable variant might give [21, 11, 23, 13] — and sorting by tens then gives [11, 13, 21, 23] only by luck.

Summary — Module 14

When the data has structure — small range, fixed-width keys, uniform distribution — we can sort in O(n). Counting sort is O(n + k); radix sort is O(d·(n + b)); bucket sort is O(n) on average for uniform input. All require stable inner sorting.

Comparison-based sort-এর সীমা n log n — কিন্তু input-এ structure থাকলে আমরা O(n)-ই করতে পারি। তবে stability নিশ্চিত না হলে radix ভেঙে পড়ে।

Next Module → Heaps & Heapsort — array-backed priority queue।