Skip to content

Performance Analysis — Big O

Hiểu Time Complexity của mỗi operation giúp bạn chọn đúng container cho từng use case.

Big O Refresher

NotationNameExample
O(1)ConstantArray access, hash lookup
O(log n)LogarithmicBinary search, balanced tree
O(n)LinearLinear search, iteration
O(n log n)LinearithmicEfficient sorting
O(n²)QuadraticNested loops
┌─────────────────────────────────────────────────────────────────┐
│                    TIME COMPLEXITY GROWTH                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Operations │                                                   │
│       ▲     │                                     O(n²)        │
│       │     │                                   /              │
│       │     │                                 /                │
│       │     │                           O(n log n)             │
│       │     │                         /                        │
│       │     │                   O(n)                           │
│       │     │              /─────────────                      │
│       │     │        O(log n)                                  │
│       │     │    ──────────────                                │
│       │     │  O(1)                                            │
│       │     ├──────────────────────────────────────────▶       │
│       │                    n (elements)                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Sequence Containers

std::vector

OperationComplexityNote
operator[], at()O(1)Random access
push_back()O(1) amortizedOccasional O(n) reallocation
pop_back()O(1)
insert() at endO(1) amortized
insert() at middleO(n)Shift elements
erase() at endO(1)
erase() at middleO(n)Shift elements
find() (unsorted)O(n)Linear search
find() (sorted)O(log n)With std::binary_search
size(), empty()O(1)

std::deque

OperationComplexityNote
operator[], at()O(1)Random access
push_front()O(1)✅ Better than vector
push_back()O(1)
pop_front()O(1)✅ Better than vector
pop_back()O(1)
insert() at middleO(n)

std::list (Doubly Linked)

OperationComplexityNote
Access by indexO(n)❌ No random access
push_front/back()O(1)
pop_front/back()O(1)
insert() at iteratorO(1)✅ No shifting
erase() at iteratorO(1)✅ No shifting
find()O(n)
splice()O(1)Move elements between lists

Associative Containers

std::map / std::set (Red-Black Tree)

OperationComplexity
insert()O(log n)
erase()O(log n)
find()O(log n)
operator[]O(log n)
lower_bound()O(log n)
iterationO(n) sorted

std::unordered_map / std::unordered_set (Hash Table)

OperationAverageWorst
insert()O(1)O(n)
erase()O(1)O(n)
find()O(1)O(n)
operator[]O(1)O(n)

📌 Worst Case

Worst case O(n) xảy ra khi nhiều keys collide vào cùng bucket. Với good hash function, gần như không xảy ra.


Container Comparison Matrix

┌────────────────────────────────────────────────────────────────────────┐
│                    CONTAINER SELECTION GUIDE                           │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  Need random access?                                                   │
│     YES ─────► vector, deque, array                                   │
│     NO  ─────► Need fast insert/delete at middle?                     │
│                   YES ─────► list                                     │
│                   NO  ─────► vector (still good)                      │
│                                                                        │
│  Need key-value?                                                       │
│     YES ─────► Need sorted keys?                                      │
│                   YES ─────► map                                      │
│                   NO  ─────► unordered_map (faster!)                  │
│     NO  ─────► Need unique elements?                                  │
│                   YES ─────► set / unordered_set                      │
│                   NO  ─────► vector                                   │
│                                                                        │
│  Need LIFO/FIFO?                                                       │
│     LIFO ─────► stack                                                 │
│     FIFO ─────► queue                                                 │
│     Priority ───► priority_queue                                      │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

Comprehensive Comparison Table

ContainerAccessInsert BeginInsert EndInsert MidEraseFind
vectorO(1)O(n)O(1)*O(n)O(n)O(n)
dequeO(1)O(1)O(1)O(n)O(n)O(n)
listO(n)O(1)O(1)O(1)†O(1)†O(n)
arrayO(1)----O(n)
set-O(log n)O(log n)O(log n)O(log n)O(log n)
mapO(log n)O(log n)O(log n)O(log n)O(log n)O(log n)
unordered_set-O(1)‡O(1)‡O(1)‡O(1)‡O(1)‡
unordered_mapO(1)‡O(1)‡O(1)‡O(1)‡O(1)‡O(1)‡

* Amortized
† Với iterator đã có
‡ Average case


Real-world Benchmarks

cpp
#include <vector>
#include <list>
#include <set>
#include <unordered_set>
#include <chrono>
#include <iostream>

template<typename Container>
void benchmark(const std::string& name, int n) {
    Container c;
    
    auto start = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < n; ++i) {
        c.insert(c.end(), i);
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    std::cout << name << ": " << duration.count() << " μs" << std::endl;
}

int main() {
    const int N = 100000;
    
    benchmark<std::vector<int>>("vector", N);
    benchmark<std::list<int>>("list", N);
    benchmark<std::set<int>>("set", N);
    
    return 0;
}

Typical Results (insert 100k elements):

vector: 1,200 μs    ← Usually fastest due to cache
list: 8,500 μs      ← Node allocations slow
set: 12,000 μs      ← Tree rebalancing

📌 Key Insight

std::vector thường nhanh hơn expected vì cache-friendly. Contiguous memory = fewer cache misses.


When to NOT Use Default

SituationInstead of vector, use...
Frequent insert/delete at frontstd::deque
Frequent insert/delete in middlestd::list (nếu không cần random access)
Need unique elements sortedstd::set
Need fast key lookupstd::unordered_map
Fixed size known at compile timestd::array
Threads sharing accessConsider std::list (stable iterators)

📚 Tổng kết

PrincipleGuideline
Default choicestd::vector
Key-value faststd::unordered_map
Key-value sortedstd::map
Unique elementsstd::set or std::unordered_set
Cache mattersContiguous containers win
Measure, don't guessProfile real workloads

➡️ Tiếp theo

Tiếp theo: Iterators — Abstraction for traversing containers.