English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

khách
1 / ?
trở lại bài học

Tốc Độ Tăng Trưởng, Không Phải Chi Phí Tuyệt Đối

Big O: Đo Lường Chi Phí Tăng Trưởng Bao Nhanh

Ký hiệu Big O đo lường tốc độ tăng trưởng của chi phí thuật toán — không phải chi phí tuyệt đối.

Câu hỏi mà Big O trả lời: khi kích thước đầu vào N tăng gấp đôi, công việc có tăng gấp đôi không? Tăng gấp bốn lần? Giữ nguyên?

Chữ 'O' viết tắt của 'order of magnitude' (bậc độ lớn). Biểu thức trong dấu ngoặc mô tả hình dạng của đường cong tăng trưởng.

Bảng Tăng Trưởng Big O: các phép toán ở N=10, 100, và 1.000 cho O(1), O(N), & O(N²)

Các lớp độ phức tạp chính:

- O(1) — hằng số: chi phí không tăng. Ví dụ: tìm kiếm giá trị theo chỉ số trong mảng. Dù mảng có 10 phần tử hay 10 triệu phần tử, một lần tìm kiếm vẫn là một lần.

- O(N) — tuyến tính: chi phí tăng theo đầu vào. Ví dụ: đọc từng phần tử trong danh sách một lần. Tăng gấp đôi danh sách, tăng gấp đôi lần đọc.

- O(N²) — bậc hai: chi phí tăng bình phương đầu vào. Ví dụ: so sánh từng phần tử với từng phần tử khác. Tăng gấp đôi N, tăng gấp bốn lần công việc.

Bảng so sánh tăng trưởng:

NO(1)O(N)O(N²)
10110100
100110010.000
1.00011.0001.000.000

Ở N=10 sự khác biệt trông nhỏ. Ở N=1.000 khoảng cách trở nên khổng lồ.

So Sánh O(N) và O(N²)

Sử dụng bảng so sánh tăng trưởng ở trên.

Ở N=1.000: O(N) cần 1.000 phép toán. O(N²) cần 1.000.000 phép toán.

Ở N=1.000, O(N²) cần bao nhiêu lần phép toán hơn so với O(N)? Hãy cho thấy cách tính của bạn.

Cách Cấu Trúc Mã Tiết Lộ Độ Phức Tạp

Cách Xác Định Big O Từ Mã

Big O ẩn trong hình dạng của mã của bạn. Bốn mẫu bao gồm hầu hết các trường hợp:

Một vòng lặp trên N phần tử: O(N)

# O(N): một lần qua danh sách
for item in list_of_n_items:
    process(item)

Vòng lặp lồng nhau, mỗi cái trên N phần tử: O(N²)

# O(N²): mỗi phần tử ghép với mỗi phần tử khác
for item_a in list_of_n_items:
    for item_b in list_of_n_items:
        compare(item_a, item_b)

Tìm kiếm thời gian hằng số: O(1)

Truy cập chỉ số mảng, tìm kiếm bảng băm — một bước bất kể kích thước.

Chia để trị (cắt vấn đề làm đôi mỗi bước): O(log N)

Tìm kiếm nhị phân: mỗi kiểm tra giảm một nửa phần tử còn lại.

---

Big O(N²) ẩn: một danh sách bên trong vòng lặp

# Cái này trông giống O(N) nhưng thực tế là O(N²)
visited = []
for item in list_of_n_items:
    if item not in visited:   # quét toàn bộ visited: O(N)
        visited.append(item)  # toàn bộ vòng lặp: O(N²)

Dòng if item not in visited quét từng phần tử của visited mỗi lần lặp. Quét danh sách tốn O(N). Một vòng lặp chạy N lần, mỗi lần thực hiện O(N) công việc: O(N) × O(N) = O(N²).

Mẫu này xuất hiện trong 1.000+ dự án mã nguồn mở. Sửa chữa chỉ cần một ký tự: thay thế visited = [] bằng visited = set(). Kiểm tra thành viên tập hợp tốn O(1), không phải O(N). Một thay đổi. Kết quả giống nhau. 1.000 lần nhanh hơn ở N=1.000.

Phân Loại & Sửa Chữa Mã Này

Đọc mã này:

result = []
for name in student_names:
    if name not in result:
        result.append(name)
Độ phức tạp Big O của mã này là bao nhiêu? Giải thích tại sao dòng `if name not in result` làm cho nó tốn kém. Sau đó viết lại mã để làm cho nó O(N).

Lý Thuyết Gặp Thực Hành

Big O Trong Tự Nhiên

Big O không chỉ là lý thuyết. Nó phân biệt các chương trình kết thúc trong vài giây với các chương trình mất 17 phút — trên cùng một tác vụ.

Ví dụ thực tế: phát hiện chu kỳ phụ thuộc trong hệ thống xây dựng.

Khi bạn cài đặt phần mềm, máy tính của bạn giải quyết một đồ thị phụ thuộc: gói A cần B, B cần C, v.v. Một hệ thống xây dựng kiểm tra đồ thị này cho các chu kỳ.

Một thuật toán phát hiện chu kỳ O(N²) hoạt động tốt ở N=100 gói. Ở N=50.000 gói (một dự án thực tế): mất 17 phút. Sửa chữa O(N): 10 giây.

Lỗi chính xác này tồn tại trong GHC (trình biên dịch Haskell), pip (trình quản lý gói Python), Maven (hệ thống xây dựng Java), Cargo (trình quản lý gói Rust), & 1.000+ dự án khác.

Không phải vì các tác giả của chúng lơ là. visited = [] được viết khi N nhỏ. Mã tôi nước. N tăng lên. Không ai để ý cho đến khi bản dựng mất nửa giờ.

Big O là từ vựng giúp bạn đặt tên cho điều đã xảy ra — & sửa chữa nó.

---

Muốn tìm hiểu sâu hơn? Khóa học unhamming của chúng ta bao gồm một chương đầy đủ về Big O, MOAD-0001 (một lỗi O(N²) thực tế được tìm thấy trong 1.000+ dự án mã nguồn mở), & tại sao việc đặt tên cho một mẫu giúp bạn tìm thấy nó ở khắp nơi. Xem Chương Thiếu Tích: Độ Phức Tạp Thuật Toán.

Dự Đoán Thời Gian Xây Dựng

Một hệ thống xây dựng sử dụng phát hiện chu kỳ O(N²).

Thời gian xây dựng đã đo:

- N=100 gói: 0,01 giây

- N=1.000 gói: 1 giây

Với O(N²): thời gian mở rộng theo (N_new / N_old)².

Với O(N): thời gian mở rộng theo (N_new / N_old).

Sử dụng những công thức đó & dữ liệu đo được: (A) ở N=10.000, phiên bản O(N²) mất bao lâu? (B) sau khi sửa chữa O(N), sử dụng N=1.000 làm cơ sở, phiên bản O(N) mất bao lâu ở N=10.000? Hãy cho thấy cách tính của bạn cho cả hai.