DLDL
Xuất bản vào

Big O Notation - Hướng Dẫn Từ A-Z

Tác giả
  • avatar
    Name
    Dat Nguyen
    Twitter

Big O Notation - Người Bạn "To Lớn" Của Developer 🚀

Hãy tưởng tượng bạn đang tìm tên crush trong danh bạ điện thoại. Nếu danh bạ có 10 người, bạn tìm nhanh. Nhưng nếu có 10,000 người thì sao? Đó chính là lúc Big O xuất hiện như một vị cứu tinh!

Big O Là Gì?

Big O Notation là cách chúng ta đo lường xem thuật toán của mình "chậm như rùa" hay "nhanh như Flash" khi lượng dữ liệu tăng lên. Nó không quan tâm máy bạn xịn cỡ nào, mà chỉ quan tâm thuật toán có "scale" được không.


🏆 Bảng Xếp Hạng Big O - Từ "Tia Chớp" Đến "Rùa Bò"

Big O Complexity

1. O(1) - Constant Time ⚡️ "Siêu Tốc"

Dù có 1 hay 1 triệu phần tử, tốc độ vẫn như nhau. Đây là cực phẩm!

// Truy cập phần tử qua index hoặc key
const getItemFromHashMap = (map: Map<string, number>, key: string): number | undefined => {
    return map.get(key); // Luôn 1 bước!
}

// Lấy phần tử theo index - LUÔN nhanh!
function getFirstStudent(students: string[]): string {
    return students[0]; // Boom! Xong ngay!
}

2. O(log n) - Logarithmic Time 🏃‍♂️ "Rất Nhanh"

Mỗi bước loại bỏ một nửa khả năng. Như trò chơi đoán số!

// Binary Search - Chia đôi mỗi bước
function binarySearch(sortedArray: number[], target: number): number {
    let left = 0;
    let right = sortedArray.length - 1;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        if (sortedArray[mid] === target) return mid;
        
        // Loại bỏ một nửa mảng
        if (sortedArray[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}
// 1,000 phần tử = ~10 bước
// 1,000,000 phần tử = ~20 bước (Quá ngon! 🎉)

3. O(n) - Linear Time 🚶‍♂️ "Bình Thường"

Càng nhiều dữ liệu, càng mất nhiều thời gian theo tỷ lệ thuận.

// Duyệt qua danh sách một lần
function findMax(numbers: number[]): number {
    let max = numbers[0];
    for (const num of numbers) { // Duyệt n phần tử
        if (num > max) max = num;
    }
    return max;
}
// 30 học sinh = 30 lần kiểm tra
// 300 học sinh = 300 lần kiểm tra

4. O(n log n) - Linearithmic Time 🚴‍♂️ "Chấp Nhận Được"

Các thuật toán sắp xếp hiệu quả như Merge Sort, Quick Sort.

// Merge Sort - Chia để trị
function mergeSort(array: number[]): number[] {
    if (array.length <= 1) return array;
    
    const mid = Math.floor(array.length / 2);
    const left = mergeSort(array.slice(0, mid));
    const right = mergeSort(array.slice(mid));
    
    return merge(left, right); // Chia log n lần, merge n phần tử
}

function merge(left: number[], right: number[]): number[] {
    const result: number[] = [];
    let i = 0, j = 0;
    
    while (i < left.length && j < right.length) {
        if (left[i] <= right[j]) {
            result.push(left[i++]);
        } else {
            result.push(right[j++]);
        }
    }
    
    return result.concat(left.slice(i)).concat(right.slice(j));
}

5. O(n²) - Quadratic Time 🐌 "Chậm"

Bắt đầu thấy "lag" khi dữ liệu lớn. Như việc so sánh từng cặp trong lớp.

// Bubble Sort hoặc vòng lặp lồng nhau
function bubbleSort(array: number[]): number[] {
    const arr = [...array];
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
            }
        }
    }
    return arr;
}
// 10 học sinh = ~45 so sánh
// 100 học sinh = ~4,950 so sánh (Ôi không! 😱)

6. O(n³) - Cubic Time 🐢 "Rất Chậm"

Ba vòng lặp lồng nhau - thường gặp trong xử lý ma trận.

// Nhân ma trận - 3 vòng lặp lồng nhau
function multiplyMatrices(a: number[][], b: number[][]): number[][] {
    const n = a.length;
    const result: number[][] = Array(n).fill(0).map(() => Array(n).fill(0));
    
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            for (let k = 0; k < n; k++) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    return result;
}

7. O(2ⁿ) - Exponential Time 💀 "Cực Kỳ Chậm"

Mỗi bước tăng gấp đôi số phép tính!

// Fibonacci đệ quy naive (không cache)
function fibonacci(n: number): number {
    if (n <= 1) return n;
    
    // Mỗi call tạo ra 2 call khác!
    return fibonacci(n - 1) + fibonacci(n - 2);
}
// n=20: ~20,000 calls
// n=40: ~2 tỷ calls 😱

8. O(n!) - Factorial Time ☠️ "Thảm Họa"

Tìm tất cả hoán vị - tăng cực nhanh!

// Tìm tất cả hoán vị (permutations)
function getPermutations(array: string[]): string[][] {
    if (array.length <= 1) return [array];
    
    const result: string[][] = [];
    for (let i = 0; i < array.length; i++) {
        const current = array[i];
        const remaining = array.filter((_, index) => index !== i);
        const perms = getPermutations(remaining);
        
        for (const perm of perms) {
            result.push([current, ...perm]);
        }
    }
    return result;
}
// 10 items = 3,628,800 permutations!

📊 So Sánh Trực Quan

Big On = 10n = 100n = 1,000Đánh Giá
O(1)111⚡️ Tuyệt vời!
O(log n)~3~7~10✨ Xuất sắc
O(n)101001,000👍 Tốt
O(n log n)~30~700~10,000🤔 OK
O(n²)10010,0001,000,000😰 Chậm
O(2ⁿ)1,0241.2×10³⁰♾️💀 Kinh khủng
O(n!)3,628,8009.3×10¹⁵⁷☠️☠️ Thảm họa

📊 Cheat Sheet

Các Cấu Trúc Dữ Liệu Phổ Biến

Data StructureAccessSearchInsertDelete
ArrayO(1)O(n)O(n)O(n)
Linked ListO(n)O(n)O(1)O(1)
Hash TableN/AO(1)O(1)O(1)
Binary Search TreeO(log n)O(log n)O(log n)O(log n)

Các Thuật Toán Sắp Xếp

AlgorithmBestAverageWorstSpace
Bubble SortO(n)O(n²)O(n²)O(1)
Quick SortO(n log n)O(n log n)O(n²)O(log n)
Merge SortO(n log n)O(n log n)O(n log n)O(n)
Heap SortO(n log n)O(n log n)O(n log n)O(1)

🎯 Ví Dụ Thực Tế: Quản Lý Lớp Học

interface Student {
    id: string;
    name: string;
    homework: boolean;
    score: number;
}

class ClassroomManager {
    private students: Student[] = [];
    private studentMap: Map<string, Student> = new Map();
    
    // O(1) - Kiểm tra sĩ số (chỉ đọc thuộc tính)
    getClassSize(): number {
        return this.students.length; // Instant!
    }
    
    // O(1) với Map - Tìm học sinh theo ID (thông minh!)
    findStudentById(id: string): Student | undefined {
        return this.studentMap.get(id); // Tra cứu trực tiếp!
    }
    
    // O(n) - Điểm danh cả lớp
    checkAttendance(): string[] {
        const presentStudents: string[] = [];
        
        // Phải gọi tên TỪNG bạn
        for (const student of this.students) {
            presentStudents.push([student.name](http://student.name));
        }
        
        return presentStudents;
    }
    
    // O(n) với Array - Tìm học sinh theo ID (cách cũ)
    findStudentByIdSlow(id: string): Student | undefined {
        // Phải lục tung cả danh sách
        return this.students.find(student => [student.id](http://student.id) === id);
    }
    
    // O(n log n) - Sắp xếp học sinh theo điểm
    sortByScore(): Student[] {
        return [...this.students].sort((a, b) => b.score - a.score);
    }
    
    // O(n²) - So sánh từng cặp học sinh
    findStudyPairs(): Array<[Student, Student]> {
        const pairs: Array<[Student, Student]> = [];
        
        for (let i = 0; i < this.students.length; i++) {
            for (let j = i + 1; j < this.students.length; j++) {
                // Logic ghép cặp học tập
                if (Math.abs(this.students[i].score - this.students[j].score) < 10) {
                    pairs.push([this.students[i], this.students[j]]);
                }
            }
        }
        
        return pairs;
    }
}

💡 Tips Vàng Để Nhớ

Cách Nhớ Đơn Giản:

  • O(1): Như mở tủ lấy đồ - biết chính xác vị trí
  • O(log n): Như tra từ điển - mở giữa, rồi quyết định trái/phải
  • O(n): Như đọc một cuốn sách - phải đọc từng trang
  • O(n log n): Như sắp xếp bài - chia nhóm rồi gộp lại
  • O(n²): Như so sánh từng đôi giày với từng bộ quần áo - mệt!
  • O(2ⁿ): Như cây phả hệ - mỗi người có 2 con, cháu tăng theo cấp số nhân
  • O(n!): Như xếp hàng chụp ảnh - càng nhiều người càng nhiều cách xếp

Lưu Ý Thực Tế:

📌 Cần nhớ: Trong thực tế, với data nhỏ (n < 100), thuật toán O(n²) đôi khi còn nhanh hơn O(n log n) do overhead thấp. Nhưng khi n lớn, Big O mới thực sự "hiện nguyên hình"!

⚠️ Cảnh báo: Lần sau khi viết vòng lặp trong vòng lặp, hãy nhớ rằng Big O đang nhìn bạn và thì thầm: "Bạn chắc chứ? 🤔"


🚀 Kết Luận

Big O không phải là thứ gì đó cao siêu. Nó chỉ là cách giúp bạn đánh giá xem code của mình có "chịu nổi" khi phải xử lý data lớn không.

Happy coding! Và nhớ rằng: Code nhanh không quan trọng bằng code nhanh khi data lớn! 🚀