Menu
CoddyTech

Sắp xếp đếm

Cập nhật lần cuối

Sắp xếp đếm (counting sort) là thuật toán sắp xếp không dựa trên so sánh, dành cho các số nguyên nằm trong một khoảng đã biết và có giới hạn. Thuật toán đếm số lần xuất hiện của từng giá trị, rồi dùng các số đếm đó để ghi thẳng từng giá trị vào đúng vị trí đã sắp xếp, không cần so sánh nào. Nhấn Phát ở trên để xem các giá trị được kiểm đếm rồi đặt lại theo thứ tự.

Sắp xếp đếm chạy trong thời gian O(n + k), với k là khoảng giá trị của dữ liệu đầu vào. Khi k không lớn hơn n quá nhiều, thuật toán cực kỳ nhanh và có thể vượt các thuật toán sắp xếp bằng so sánh O(n log n), nhưng nếu khoảng giá trị quá lớn, mảng đếm O(k) khiến nó không còn thực tế.

Độ phức tạp thời gian và không gian

Tiêu chíĐộ phức tạpGhi chú
Thời gianO(n + k)n phần tử, k = khoảng giá trị
Không gianO(n + k)Mảng đếm + mảng kết quả
Ổn địnhCóKhi đặt từ phải sang trái bằng tổng tiền tố
Dựa trên so sánh?KhôngSắp xếp bằng cách đếm, không so sánh
Phù hợp nhất choKhoảng số nguyên nhỏk xấp xỉ n

Từng bước thực hiện

BướcĐiều gì xảy ra
1Tìm giá trị lớn nhất để xác định kích thước mảng đếm.
2Đếm số lần xuất hiện của từng giá trị.
3(Không bắt buộc) Chuyển số đếm thành tổng tiền tố để thuật toán ổn định.
4Ghi mỗi giá trị vào mảng kết quả đúng bằng số lần nó xuất hiện.
5Mảng kết quả giờ đã được sắp xếp hoàn toàn.

Ví dụ minh họa

Sắp xếp [1, 4, 1, 2, 4] (giá trị nằm trong khoảng từ 0 đến 4, nên mảng đếm có 5 ô):

LượtTrạng tháiThao tác
Quét đầu vàocount = [0, 2, 1, 0, 2]Kiểm đếm số lần xuất hiện: 1 xuất hiện hai lần, 2 một lần, 4 hai lần.
Tổng tiền tốcount = [0, 2, 3, 3, 5]Mỗi ô giờ chứa số lượng giá trị <= chỉ số của ô đó, cho ra vị trí cuối cùng.
Đặt 4output = [_, _, _, _, 4]Đọc từ phải sang trái: count[4] = 5, nên 4 vào chỉ số 4; giảm xuống 4.
Đặt 2output = [_, _, 2, _, 4]count[2] = 3, nên 2 vào chỉ số 2; giảm xuống 2.
Đặt 1output = [_, 1, 2, _, 4]count[1] = 2, nên 1 vào chỉ số 1; giảm xuống 1.
Đặt 4output = [_, 1, 2, 4, 4]count[4] = 4, nên 4 này vào chỉ số 3; giảm xuống 3.
Đặt 1output = [1, 1, 2, 4, 4]count[1] = 1, nên 1 này vào chỉ số 0. Mảng đã được sắp xếp.

Khi nào nên dùng sắp xếp đếm

Nên dùng khiTránh dùng khi
Bạn sắp xếp số nguyên (hoặc khóa ánh xạ được sang số nguyên) trong một khoảng nhỏ, đã biết.Khoảng giá trị k lớn hơn rất nhiều so với số phần tử n.
Bạn cần thời gian tuyến tính O(n + k) và có thể chấp nhận các mảng phụ.Bộ nhớ eo hẹp: mảng đếm tốn O(k) bất kể n là bao nhiêu.
Bạn cần một thuật toán sắp xếp ổn định làm bước con (ví dụ bên trong radix sort).Khóa là số thực, chuỗi hoặc đối tượng bất kỳ không ánh xạ được sang số nguyên.
Giá trị lớn nhất có giới hạn và tính trước được với chi phí thấp.Khoảng giá trị không biết trước hoặc không giới hạn, nên không xác định được kích thước mảng đếm.

Code Counting Sort

Một cách cài đặt Counting 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 Counting Sort bằng Python

Python
1def counting_sort(a):2    # Works for non-negative integers with a small max value3    counts = [0] * (max(a) + 1)4    for value in a:5        counts[value] += 16    # Prefix sums turn counts into final positions7    for i in range(1, len(counts)):8        counts[i] += counts[i - 1]9    out = [0] * len(a)10    for value in reversed(a):  # reversed keeps equal values stable11        counts[value] -= 112        out[counts[value]] = value13    return out14
15
16nums = [4, 2, 2, 8, 3, 3, 1]17print("Before:", nums)18print("After: ", counting_sort(nums))
Chạy code này trong Playground Python

Câu hỏi thường gặp về sắp xếp đếm

Độ phức tạp thời gian của sắp xếp đếm là bao nhiêu?
Sắp xếp đếm là O(n + k), với n là số phần tử và k là khoảng các giá trị có thể có. Khi k = O(n), đây là thời gian tuyến tính. Thuật toán dùng thêm O(n + k) bộ nhớ.
Sắp xếp đếm có ổn định không?

Có thể. Phiên bản ổn định tính tổng tiền tố của các số đếm và đặt phần tử từ phải sang trái, nhờ đó giữ nguyên thứ tự tương đối của các khóa bằng nhau. Phiên bản đơn giản "ghi lại theo giá trị" ở đây cho kết quả sắp xếp đúng nhưng chủ yếu dùng cho các số nguyên thuần túy.

Khi nào nên dùng sắp xếp đếm?
Dùng khi sắp xếp số nguyên (hoặc khóa ánh xạ được sang số nguyên) trong một khoảng nhỏ, đã biết. Nếu khoảng giá trị k lớn hơn nhiều so với số phần tử, mảng đếm sẽ lãng phí bộ nhớ và một thuật toán sắp xếp bằng so sánh sẽ tốt hơn.
Sắp xếp đếm và radix sort khác nhau như thế nào?
Sắp xếp đếm sắp xếp theo toàn bộ giá trị trong một lượt và cần mảng đếm lớn bằng khoảng giá trị. Radix sort sắp xếp theo từng chữ số và thường gọi một sắp xếp đếm ổn định cho mỗi chữ số, giúp khoảng giá trị mỗi lượt luôn nhỏ (ví dụ 10 với chữ số thập phân). Radix sort xử lý được các khoảng giá trị lớn mà một lần sắp xếp đếm đơn lẻ không làm nổi.
Vì sao sắp xếp đếm không phải lúc nào cũng nhanh hơn quicksort?
Sắp xếp đếm là O(n + k), nên chỉ thắng khi khoảng giá trị k tương đương với n. Nếu k cực lớn, chẳng hạn sắp xếp 100 giá trị trong khoảng từ 0 đến 1,000,000,000, mảng đếm O(k) sẽ chiếm phần lớn chi phí và lãng phí bộ nhớ, trong khi một thuật toán sắp xếp bằng so sánh O(n log n) như quicksort vẫn nhanh và tiết kiệm bộ nhớ.
Sắp xếp đếm có xử lý được số âm không?
Có, với một độ dời nhỏ. Thay vì đánh chỉ số mảng đếm trực tiếp bằng giá trị, hãy đánh chỉ số bằng value - min, để giá trị nhỏ nhất ứng với chỉ số 0. Kích thước mảng đếm trở thành max - min + 1. Quên độ dời này là một bug phổ biến khiến chương trình bị lỗi khi gặp đầu vào âm.
Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu