5.2.19 · Coding › C++ Programming
Intuition Ek hi core idea
Ek container ek data structure hai jisme ek chosen tradeoff hota hai . Tum kabhi sirf "data store" nahi karte — tum use is tarah store karte ho jo kuch operations ko sasta banata hai aur doosron ko mehnga. Poora STL container zoo isliye exist karta hai kyunki koi bhi ek structure har cheez mein fast nahi hota . Tumhara kaam hai: apne access pattern ko us container se match karo jiska internal memory layout us pattern ko O ( 1 ) ya O ( log n ) banata ho — O ( n ) ki jagah.
Definition Do bade families
Sequence containers — elements ko us order mein rakhte hain jisme tumne insert kiya. Position meaningful hai. → array, vector, deque, list.
Associative containers — elements ko unki value (key) ke hisaab se ordered/organised rakhte hain, insertion order ke hisaab se nahi. Position container decide karta hai. → set, multiset, map, multimap (tree-based), aur unordered_* (hash-based).
Speed kya decide karta hai? Memory layout :
Contiguous array (vector, array, deque-ish): index math O ( 1 ) random access deta hai, lekin beech mein insert karne pe sab kuch shift hota hai (O ( n ) ).
Linked nodes (list): har node agle ki taraf point karta hai, toh splicing O ( 1 ) hai, lekin tum seedha index k pe jump nahi kar sakte — tumhe walk karna padega (O ( n ) ).
Balanced binary search tree (set/map): keys ko sorted rakhta hai, toh search/insert/erase O ( log n ) hain aur tum sorted order mein iterate kar sakte ho.
Hash table (unordered_*): ek hash function keys ko buckets mein scatter karta hai, average O ( 1 ) search/insert deta hai — lekin koi order nahi , aur collisions pe worst case O ( n ) ho sakta hai.
std::array<T, N>
Ek fixed-size contiguous array jiska size N compile time pe pata hota hai. Stack pe rehta hai (koi heap allocation nahi). C array ke upar zero overhead, lekin STL interface ke saath (.size(), iterators, .at()).
std::vector<T>
Ek dynamic contiguous array . Automatically grow karta hai. O(1) random access v[i], amortised O(1) push_back, lekin beech mein insert/erase karne ke liye O(n).
push_back amortised O ( 1 ) kyun hai
Jab vector full ho jaata hai toh woh ek bada block allocate karta hai (typically capacity × 2 ) aur purane elements ko copy karta hai. Ek aisa resize O ( n ) hai — lekin yeh rarely hota hai. Cost ko saare saste pushes pe spread karo aur yeh average O ( 1 ) nikalta hai.
std::deque<T> (double-ended queue)
Fast push_front aur push_back, dono amortised O ( 1 ) , saath mein O ( 1 ) random access. Internally fixed-size chunks ki ek sequence hoti hai jise pointers ka ek "map" track karta hai — toh yeh almost contiguous hai lekin ek guaranteed block nahi hai.
std::list<T> (doubly-linked list)
Har element ek node hai jisme prev/next pointers hain. Strength: O ( 1 ) insert/erase/splice kahin bhi (ek iterator diya ho toh). Weakness: koi [] access nahi — index k dhundhna O ( n ) hai, aur cache performance kharab hoti hai (nodes memory mein scattered hote hain).
Definition Tree-based (ordered) —
set, multiset, map, multimap
Ek self-balancing binary search tree (red-black tree) ke roop mein implement kiya gaya. Keys sorted rehti hain; find, insert, erase O ( log n ) hain.
set — unique sorted keys .
multiset — sorted keys, duplicates allowed .
map — sorted key → value pairs, unique keys.
multimap — key → value, duplicate keys allowed .
Definition Hash-based (unordered) —
unordered_set, unordered_map
Ek hash table ke roop mein implement kiya gaya: ek hash(key) function bucket choose karta hai; collisions bucket ke andar chain hoti hain. find/insert/erase ke liye Average O ( 1 ) , koi ordering nahi , worst case O ( n ) .
log n kyun?
n nodes ke balanced BST ki height h ≈ log 2 n hoti hai. Har search ek level pe ek node se compare karta hai aur left/right jaata hai — toh woh zyada se zyada h nodes touch karta hai. Search space ko har step pe half karna binary search jaisi hi idea hai.
Container
Random access
Insert/erase middle
Insert front
Insert back
Search by value
Ordered?
array
O ( 1 )
— (fixed)
—
—
O ( n )
insertion
vector
O ( 1 )
O ( n )
O ( n )
amort. O ( 1 )
O ( n )
insertion
deque
O ( 1 )
O ( n )
O ( 1 )
O ( 1 )
O ( n )
insertion
list
O ( n )
O ( 1 ) *
O ( 1 )
O ( 1 )
O ( n )
insertion
set/map
—
O ( log n )
—
—
O ( log n )
sorted
unordered_*
—
O ( 1 ) avg
—
—
O ( 1 ) avg
no
* position pe iterator diya ho toh.
Worked example 1. "Words ki frequency count" ke liye choose karna
Chahiye: har word ko → count map karo, fast lookups, order matter nahi karta.
unordered_map < string, int> freq;
for ( auto& w : words) freq[w] ++ ; // O(1) average per word
Yeh step kyun? freq[w] missing hone pe value ko 0 pe default-construct karta hai, phir ++. Hash table O ( 1 ) average deta hai — N words ke liye total O ( N ) . map kaam karta lekin ek log factor add karta aur humein sorted output nahi chahiye.
Worked example 2. "Beech mein ek million baar insert karna"
list <int> L;
auto it = L. begin ();
// ... it ko jagah pe advance karo, phir:
L. insert (it, x); // O(1) — bas pointers relink hote hain
Yeh step kyun? vector ke saath, har middle insert n elements tak shift karta hai (O ( n ) ). list ke saath, iterator diya ho toh yeh pure pointer surgery hai — O ( 1 ) . (Lekin iterator tak pahunchna O ( n ) cost kar sakta hai, toh yeh tab hi faida karta hai jab tumhare paas already position ho.)
Worked example 3. "Scores ka sorted set rakho, duplicates allow ho, sabse chhota milna chahiye"
multiset <int> scores;
scores. insert ( 50 ); scores. insert ( 50 ); scores. insert ( 20 );
int smallest = * scores. begin (); // 20, O(1) after sort kept by tree
scores. erase (scores. begin ()); // remove ONE smallest, O(log n)
Yeh step kyun? multiset elements ko hamesha sorted rakhta hai, toh begin() minimum hai. Hum set ki jagah multiset use karte hain kyunki duplicate 50s rakhne hain. erase(iterator) ek single element remove karta hai; erase(value) sari copies remove kar deta.
Worked example 4. Map ko sorted key order mein iterate karna
map < string, int> m = {{ "banana" , 3 },{ "apple" , 5 }};
for ( auto& [k,v] : m) cout << k << " " << v << " \n " ;
// prints apple 5, then banana 3 (alphabetical)
Yeh step kyun? map tree-based hai ⇒ iteration keys ko sorted order mein yield karta hai free mein . unordered_map arbitrary bucket order mein print karta.
unordered_map hamesha map se fast hai, toh isse hamesha use karo."
Kyun sahi lagta hai: O ( 1 ) average O ( log n ) se better lagta hai. Catch: (1) ordering chali jaati hai; agar sorted iteration ya lower_bound chahiye, toh map sahi tool hai. (2) Hash collisions pe unordered_map worst-case O ( n ) tak degrade ho sakta hai. (3) Chhote n ke liye, constant factor + cache misses map ko practice mein faster bana sakte hain. Fix: unordered_map tab use karo jab raw key lookup chahiye aur order mein iterate nahi karna; map tab use karo jab order/range queries matter kare.
multiset pe erase(value) sirf ek element remove karta hai."
Kyun sahi lagta hai: erase singular lagta hai. Reality: ms.erase(50) har 50 remove kar deta hai. Sirf ek remove karne ke liye, ek iterator pass karo: ms.erase(ms.find(50)). Fix: iterator-erase = ek element; value-erase = saari matches.
vector mein insert karte waqt purane iterators/pointers rakhna theek hai."
Kyun sahi lagta hai: element 'wahan hota hua' lagta hai. Reality: ek push_back jo reallocation trigger kare woh poora array move kar deta hai — saare purane iterators, pointers aur references invalidate ho jaate hain (dangling). Fix: insertion ke baad iterators re-fetch karo, ya pehle se reserve() se capacity set karo. (list/deque-front-back doosre elements ko same tarah invalidate nahi karte.)
vector slow hai kyunki grow karne mein O ( n ) lagta hai."
Kyun sahi lagta hai: resize pe copy karna is O ( n ) . Reality: doubling ise amortised O ( 1 ) banata hai, aur contiguous memory excellent cache locality deti hai — real benchmarks mein aksar sabse fast container. Fix: jab tak specific reason na ho, default vector rakhna.
Recall Feynman: 12-saal ke bacche ko explain karo
Socho apne toys rakhne ke alag-alag tarike.
Vector/array ek lambi shelf hai — tum seedha toy #7 instantly grab kar sakte ho, lekin beech mein naya toy squeeze karne ke liye sabko slide karna padega.
List toys ki ek chain hai jahan woh ek doosre ka haath pakde khade hain — do haathon ke beech naya dost instantly splice ho sakta hai, lekin 7th toy dhundhne ke liye chain mein count karte chalte rehna padega.
Deque ek shelf hai jo dono ends pe khuli hai, toh front ya back mein asaani se add kar sakte ho.
Set/map ek librarian ki tarah hai jo toys ko hamesha alphabetical order mein rakhti hai — koi bhi toy dhundhna jaldi hota hai kyunki use pata hai cheezein kahan jaati hain.
Unordered_map labeled bins ki ek wall hai — tum compute karte ho kaunse bin mein toy jaayega aur seedha toss karte ho (super fast), lekin toys kisi neat order mein nahi aate.
Mnemonic Families yaad rakho
"A Very Dumb List Sits Quietly Mapping Unknown Hashes."
A rray, V ector, D eque, L ist = sequence; S et, multiset, M ap, multimap = ordered (tree); U nordered_* = hash.
Aur speed rule ke liye: "Contiguous = jump fast, shift slow; Linked = splice fast, jump slow; Tree = sorted & log; Hash = unsorted & flat-out."
Which container gives O(1) random access AND amortised O(1) push_back? std::vector
Vector ka push_back amortised O(1) kyun hai resize hone ke baavajood? Resizing capacity double karta hai, toh O(n) copies rarely hoti hain; n pushes mein total copy work < 2n hota hai, average O(1) nikalta hai.
set aur multiset mein key difference kya hai? set unique keys store karta hai; multiset duplicate keys allow karta hai (dono sorted rehte hain).
set/map ke peeche kaunsa underlying data structure hai? Ek self-balancing binary search tree (red-black tree), jo O(log n) operations aur sorted iteration deta hai.
unordered_map/unordered_set ke peeche kya hai? Ek hash table — average O(1), koi ordering nahi, worst case O(n).
map ko unordered_map ke upar kab prefer karna chahiye? Jab sorted iteration chahiye, range queries (lower_bound/upper_bound) chahiye, ya guaranteed log worst case chahiye.
vector vs list mein beech mein insert karne ki complexity (iterator ke saath)? vector O(n) (elements shift hote hain); list O(1) (pointers relink hote hain).
Kaunsa container O(1) push_front aur push_back ke saath O(1) random access bhi deta hai? std::deque.
Multiset se exactly EK element value se kaise erase karein? ms.erase(ms.find(value)) — value directly pass karna (ms.erase(value)) SAARI matches erase kar deta hai.
Vector iterators/pointers ko kya invalidate karta hai? Ek reallocation (jaise capacity se zyada push_back) array ko move karta hai; reserve() isse avoid karta hai.
std::array aur std::vector mein difference? array fixed compile-time size ka hota hai, stack pe, resize nahi hota; vector dynamic hai, heap-allocated hai, grow karta hai.
Balanced BST O(log n) search kyun deta hai? Iski height ~log2(n) hoti hai, aur search har level pe zyada se zyada ek node visit karta hai, har step pe search space half karta jaata hai.
Big-O notation — in tradeoffs ki language.
Amortised analysis — vector ke O(1) push_back ko justify karta hai.
Hash functions and collisions — kyun unordered_* average O(1) hota hai.
Red-black trees / Binary search trees — set/map ka engine.
Iterators in C++ — invalidation rules har container ke liye alag hain.
Cache locality — kyun contiguous vector aksar practice mein list se better perform karta hai.
STL algorithms (sort, find, lower_bound) — in containers pe operate karte hain.
Tradeoff: no structure fast at everything
Memory layout picks speed
Memory layout picks speed
set, map, multiset, multimap
unordered_set, unordered_map