Radix sort (sắp xếp cơ số)
Cập nhật lần cuối
Radix sort (sắp xếp cơ số) là thuật toán sắp xếp số nguyên không dựa trên so sánh. Thay vì so sánh các giá trị, nó sắp xếp các số theo từng chữ số. Phiên bản chữ số ít quan trọng nhất trước (LSD) xử lý hàng đơn vị trước, rồi hàng chục, rồi hàng trăm, dùng counting sort ổn định ở mỗi chữ số. Vì mỗi lượt đều ổn định, khi chữ số quan trọng nhất được xử lý xong thì cả mảng đã được sắp xếp. Nhấn Phát ở trên để xem mỗi lượt theo chữ số sắp xếp lại các cột.
Radix sort chạy trong thời gian O(d·(n + k)), trong đó d là số chữ số và k là cơ số (ở đây là 10). Với số nguyên có độ dài cố định, thời gian này gần như tuyến tính, có thể nhanh hơn các thuật toán sắp xếp so sánh O(n log n), nhưng nó chỉ áp dụng được cho dữ liệu chia được thành chữ số hoặc khóa.
Độ phức tạp thời gian và không gian
| Trường hợp | Độ phức tạp | Ghi chú |
|---|---|---|
| Thời gian | O(d·(n + k)) | d chữ số, cơ số k (tuyến tính khi d cố định) |
| Không gian | O(n + k) | Mảng kết quả và bảng đếm chữ số |
| Ổn định | Có | Mỗi lượt theo chữ số là một counting sort ổn định |
| So sánh? | Không | Sắp xếp theo chữ số, không so sánh giá trị |
| Áp dụng cho | Số nguyên/khóa | Không phải mọi đối tượng so sánh được |
Từng bước
| Bước | Điều gì xảy ra |
|---|---|
| 1 | Tìm giá trị lớn nhất để biết cần xử lý bao nhiêu chữ số. |
| 2 | Bắt đầu với chữ số ít quan trọng nhất (hàng đơn vị). |
| 3 | Sắp xếp ổn định mảng theo chữ số đó bằng counting sort. |
| 4 | Chuyển sang chữ số quan trọng hơn tiếp theo. |
| 5 | Lặp lại cho đến khi xử lý xong mọi vị trí chữ số. |
Ví dụ minh họa
Sắp xếp [170, 45, 75, 90, 2, 24, 66]:
| Lượt | Mảng | Thao tác |
|---|---|---|
| Bắt đầu | [170, 45, 75, 90, 2, 24, 66] | Giá trị lớn nhất là 170, nên cần ba lượt theo chữ số. |
| Hàng đơn vị | [170, 90, 2, 24, 45, 75, 66] | Sắp xếp ổn định theo chữ số hàng đơn vị: 0, 0, 2, 4, 5, 5, 6. |
| Hàng chục | [2, 24, 45, 66, 170, 75, 90] | Sắp xếp ổn định theo chữ số hàng chục: 0, 2, 4, 6, 7, 7, 9 (170 vẫn đứng trước 75). |
| Hàng trăm | [2, 24, 45, 66, 75, 90, 170] | Sắp xếp ổn định theo chữ số hàng trăm; chỉ 170 có chữ số 1, nên nó chuyển xuống cuối. Đã sắp xếp xong. |
Khi nào nên dùng radix sort
| Nên dùng khi | Tránh dùng khi |
|---|---|
| Khóa là số nguyên hoặc chuỗi có độ dài cố định, tách được thành chữ số. | Bạn phải sắp xếp các đối tượng bất kỳ theo một hàm so sánh tùy chỉnh. |
Khóa có số chữ số d nhỏ và giới hạn, nên O(d·(n + k)) nhanh hơn O(n log n). | Khóa rất dài hoặc không giới hạn, khiến d lớn và các lượt tốn kém. |
Bạn cần sắp xếp ổn định và có thể dùng thêm O(n + k) bộ nhớ. | Bộ nhớ hạn hẹp và không chấp nhận được các vùng đệm O(n + k). |
Miền giá trị hoặc cơ số k vừa phải so với n. | k rất lớn, nên mỗi lượt counting sort chiếm phần lớn thời gian chạy. |
Code Radix Sort
Một cách cài đặt Radix Sort gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.
Code Radix Sort bằng Python
1def radix_sort(a):2 # Sort by each decimal digit, least significant first3 max_value = max(a)4 exp = 15 while max_value // exp > 0:6 a = sort_by_digit(a, exp)7 exp *= 108 return a9
10
11def sort_by_digit(a, exp):12 buckets = [[] for _ in range(10)]13 for value in a:14 digit = (value // exp) % 1015 buckets[digit].append(value)16 # Concatenating buckets 0..9 keeps the sort stable17 return [value for bucket in buckets for value in bucket]18
19
20nums = [170, 45, 75, 90, 802, 24, 2, 66]21print("Before:", nums)22print("After: ", radix_sort(nums))Code Radix Sort bằng JavaScript
1function radixSort(arr) {2 let a = [...arr];3 const max = Math.max(...a);4 // One counting pass per digit, least significant first5 for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {6 const buckets = Array.from({ length: 10 }, () => []);7 for (const x of a) {8 buckets[Math.floor(x / exp) % 10].push(x);9 }10 a = buckets.flat();11 }12 return a;13}14
15const data = [170, 45, 75, 90, 802, 24, 2, 66];16console.log("Before:", data);17console.log("Sorted:", radixSort(data));Code Radix Sort bằng Java
1import java.util.Arrays;2
3public class Main {4 static void radixSort(int[] arr) {5 int max = 0;6 for (int v : arr) max = Math.max(max, v);7 // One stable counting pass per decimal digit8 for (int exp = 1; max / exp > 0; exp *= 10) countingPass(arr, exp);9 }10
11 static void countingPass(int[] arr, int exp) {12 int n = arr.length;13 int[] out = new int[n];14 int[] count = new int[10];15 for (int v : arr) count[(v / exp) % 10]++;16 for (int i = 1; i < 10; i++) count[i] += count[i - 1];17 // Walk backwards to keep the pass stable18 for (int i = n - 1; i >= 0; i--) {19 int digit = (arr[i] / exp) % 10;20 out[--count[digit]] = arr[i];21 }22 System.arraycopy(out, 0, arr, 0, n);23 }24
25 public static void main(String[] args) {26 int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};27 System.out.println("Before: " + Arrays.toString(arr));28 radixSort(arr);29 System.out.println("After: " + Arrays.toString(arr));30 }31}Code Radix Sort bằng C++
1#include <algorithm>2#include <iostream>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10// Stable counting sort on one decimal digit (exp = 1, 10, 100, ...)11void countingPass(std::vector<int>& a, int exp) {12 std::vector<int> output(a.size());13 std::vector<int> count(10, 0);14 for (int x : a) ++count[(x / exp) % 10];15 for (int d = 1; d < 10; ++d) count[d] += count[d - 1];16 for (int i = static_cast<int>(a.size()) - 1; i >= 0; --i) {17 int digit = (a[i] / exp) % 10;18 output[--count[digit]] = a[i];19 }20 a = output;21}22
23void radixSort(std::vector<int>& a) {24 int maxVal = *std::max_element(a.begin(), a.end());25 for (int exp = 1; maxVal / exp > 0; exp *= 10) {26 countingPass(a, exp);27 }28}29
30int main() {31 std::vector<int> data = {170, 45, 75, 90, 802, 24, 2, 66};32 std::cout << "Before: ";33 printVec(data);34 radixSort(data);35 std::cout << "After: ";36 printVec(data);37 return 0;38}Code Radix Sort bằng C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9// Stable counting sort on one decimal digit (exp = 1, 10, 100, ...)10void countingPass(int a[], int n, int exp) {11 int* output = malloc(n * sizeof(int));12 int count[10] = {0};13 for (int i = 0; i < n; i++) count[(a[i] / exp) % 10]++;14 for (int d = 1; d < 10; d++) count[d] += count[d - 1];15 for (int i = n - 1; i >= 0; i--) {16 int digit = (a[i] / exp) % 10;17 output[--count[digit]] = a[i];18 }19 for (int i = 0; i < n; i++) a[i] = output[i];20 free(output);21}22
23void radixSort(int a[], int n) {24 int maxVal = a[0];25 for (int i = 1; i < n; i++) {26 if (a[i] > maxVal) maxVal = a[i];27 }28 for (int exp = 1; maxVal / exp > 0; exp *= 10) {29 countingPass(a, n, exp);30 }31}32
33int main(void) {34 int data[] = {170, 45, 75, 90, 802, 24, 2, 66};35 int n = sizeof(data) / sizeof(data[0]);36 printf("Before: ");37 printArr(data, n);38 radixSort(data, n);39 printf("After: ");40 printArr(data, n);41 return 0;42}Câu hỏi thường gặp về radix sort
Độ phức tạp thời gian của radix sort là bao nhiêu?
O(d·(n + k)), trong đó d là số chữ số và k là cơ số. Với số nguyên có độ dài cố định, thời gian này gần như là O(n), có thể nhanh hơn các thuật toán sắp xếp so sánh. Thuật toán dùng thêm O(n + k) bộ nhớ.Radix sort có ổn định không?
Có. LSD radix sort dựa vào counting sort ổn định ở mỗi chữ số; chính tính ổn định khiến cách làm theo từng chữ số cho ra kết quả sắp xếp đúng.
Khi nào có thể dùng radix sort?
Radix sort áp dụng cho dữ liệu tách được thành chữ số hoặc khóa có kích thước cố định, như số nguyên hoặc chuỗi có độ dài cố định. Nó không phải thuật toán sắp xếp so sánh đa dụng, nên không thể sắp xếp các đối tượng bất kỳ theo hàm so sánh tùy chỉnh.
Radix sort khác counting sort như thế nào?
k), nhờ đó xử lý được những miền giá trị lớn mà counting sort thuần không làm được.Tại sao LSD radix sort bắt đầu từ chữ số ít quan trọng nhất?
Bắt đầu từ chữ số ít quan trọng nhất giúp mỗi lượt ổn định giữ nguyên thứ tự đã được thiết lập bởi các chữ số ít quan trọng hơn trước đó. Khi đến lượt chữ số quan trọng nhất, các giá trị trùng chữ số đó đã được các chữ số thấp hơn sắp đúng thứ tự, nên cuối cùng cả mảng được sắp xếp hoàn toàn. Sắp xếp từ chữ số quan trọng nhất trước sẽ phá vỡ điều này và cần một cách làm đệ quy khác (MSD radix sort).
Radix sort có xử lý được số âm không?
Không trực tiếp: cách tách chữ số cơ bản giả định các số nguyên không âm. Cách khắc phục phổ biến là dịch mọi giá trị bằng cách cộng thêm trị tuyệt đối của giá trị nhỏ nhất để tất cả đều không âm, hoặc sắp xếp riêng số âm và số không âm rồi nối lại. Bỏ qua điều này là một bug thường gặp khi áp dụng radix sort cho dữ liệu thực tế.