3.4.11 · Coding › Trees
Ek heap basically ek array hai jise hum pretend karte hain ki ek complete binary tree hai. Kisi random array ko valid heap mein convert karne ka naive tarika hai "ek-ek item insert karo" — woh O ( n log n ) hota hai. Lekin ek zyada smart move hai: tree ke bottom se start karo aur cheezein neeche sift karo . Iska genius yeh hai ki zyaatar nodes bottom ke paas hote hain, jahan sift-down sasta hota hai. Kuch hi mehenge nodes (root ke paas) hote hain, aur woh rare hain. Yeh sasta-lekin-zyada vs mehenga-lekin-kam wala trade-off hi exact wajah hai ki total cost ==O ( n ) == tak girr jaati hai, O ( n log n ) ki jagah.
Definition Build-heap (bottom-up heapify)
Ek arbitrary array A[0..n-1] diya gaya hai jo ek complete binary tree ki tarah interpret hota hai, heapify use aise rearrange karta hai ki woh heap property satisfy kare (max-heap ke liye: har parent ≥ uske children). Bottom-up method har non-leaf node pe siftDown call karta hai, nodes ko last internal node se root (index 0 ) tak process karta hai.
Array ↔ tree index map (0-based):
i ka parent: ⌊( i − 1 ) /2 ⌋
i ka left child: 2 i + 1
i ka right child: 2 i + 2
Last internal (non-leaf) node: index ⌊ n /2 ⌋ − 1 (iske baad sab leaf hain).
⌊ n /2 ⌋ − 1 se kyun start karo?
Leaves already trivially valid heaps hain (ek single node ke koi children nahi jo kuch violate kar sakein). Toh unhe sift karne ka koi fayda nahi. Pehla node jiska child ho sakta hai woh last element ka parent hai, jo index ⌊ n /2 ⌋ − 1 pe hota hai.
siftDown(i) yeh assume karta hai ki i ke children ki roots wali subtrees already valid heaps hain, aur A[i] ko tab tak neeche push karta hai jab tak heap property hold na kare.
siftDown(A, i, n):
while True:
l = 2*i + 1
r = 2*i + 2
largest = i
if l < n and A[l] > A[largest]: largest = l
if r < n and A[r] > A[largest]: largest = r
if largest == i: break # parent already biggest
swap(A[i], A[largest])
i = largest # follow the swap down
buildHeap(A, n):
for i from floor(n/2)-1 down to 0:
siftDown(A, i, n)
Upar ki taraf kyun process karo?
siftDown(i) ko chahiye ki uske children ki subtrees already heaps hon. Agar hum bottom se upar jaate hain, toh jab tak hum node i tak pahunchte hain, uske dono children ki subtrees pehle wali iterations mein fix ho chuki hoti hain. Yeh ek chhota sa bottom-up dynamic-programming order hai: pehle chhoti subtrees solve karo, phir unhe badi mein combine karo.
Setup. n nodes wale complete binary tree ki height h = ⌊ log 2 n ⌋ hoti hai. Node height ko bottom se measure karo: leaves ki height 0 hoti hai, root ki height h .
Ek siftDown ki cost. Height k wala node zyada se zyada k levels neechie sink ho sakta hai, toh uska kaam O ( k ) hai.
Har height pe nodes count karo. Ek (lagbhag-)complete tree mein height k pe zyada se zyada ⌈ n / 2 k + 1 ⌉ nodes hote hain.
n / 2 k + 1 kyun?
Height-0 nodes (leaves) tree ka ~aadha hissa hain: n /2 . Height-1 nodes ~ek chauthai hain: n /4 . Har level upar jaane par count aadha ho jaata hai. Toh height k pe ≈ n / 2 k + 1 nodes hain. Unche nodes exponentially rare hain.
Total kaam ka sum karo.
T ( n ) = k = 0 ∑ h count 2 k + 1 n ⋅ cost O ( k ) = O ( n k = 0 ∑ h 2 k + 1 k ) ≤ O ( 2 n k = 0 ∑ ∞ 2 k k ) .
Ab ek key infinite sum evaluate karo. Standard identity use karo (neeche derive ki gayi hai):
k = 0 ∑ ∞ k x k = ( 1 − x ) 2 x , ∣ x ∣ < 1.
x = 2 1 plug karo:
k = 0 ∑ ∞ 2 k k = ( 1 − 1/2 ) 2 1/2 = 1/4 1/2 = 2.
Isliye
T ( n ) ≤ O ( 2 n ⋅ 2 ) = O ( n ) .
Yeh series ek constant tak converge karti hai (yeh n ke saath nahi badhti). Jo "log " tum expect karte, woh khatam ho jaata hai kyunki mehenga nodes exponentially kam hote hain. Isliye bottom-up build strictly linear hai, aur repeated insertion ke O ( n log n ) se behtar hai.
Worked example Example 1 —
[3, 9, 2, 1, 4, 5] se max-heap banao
n = 6 , last internal node = ⌊ 6/2 ⌋ − 1 = 2 . i = 2 , 1 , 0 process karo.
Tree (indices): 0=3, 1=9, 2=2, 3=1, 4=4, 5=5.
i=2 (value 2, children idx 5=5): 5>2 → swap. Array: [3,9,5,1,4,2].
Yeh step kyun? Node 2 ka ek hi child (5) bada hai, toh parent ko woh value leni chahiye.
i=1 (value 9, children idx 3=1, 4=4): dono chhote hain → koi swap nahi. [3,9,5,1,4,2].
Yeh step kyun? 9 already dono children se ≥ hai — heap property locally hold karti hai.
i=0 (value 3, children idx 1=9, 2=5): largest 9 hai → idx 1 se swap. [9,3,5,1,4,2]. Ab idx 1 pe 3 ko sift karo: children idx 3=1, 4=4 → 4 largest hai → swap. [9,4,5,1,3,2]. Node ab idx 4 pe leaf hai → ruk jao.
Yeh step kyun? Root pe swap karne ke baad, demoted value (3) neeche bhi property violate kar sakti hai, toh hum usse follow karte rahe neeche tak.
Result: [9,4,5,1,3,2] — valid max-heap (check: 9≥4,5; 4≥1,3; 5≥2 ✓).
Worked example Example 2 —
[5, 1, 8, 2, 7] se min-heap banao
n = 5 , last internal = ⌊ 5/2 ⌋ − 1 = 1 . i = 1 , 0 process karo. (Min-heap: parent ≤ children.)
i=1 (value 1, children idx 3=2, 4=7): 1 already sabse chhota → koi swap nahi.
i=0 (value 5, children idx 1=1, 2=8): sabse chhota 1 hai → idx 0,1 swap → [1,5,8,2,7]. idx 1 pe 5 ko sift karo: children idx 3=2, 4=7 → 2 chhota → swap → [1,2,8,5,7]. idx 3 leaf hai → ruk jao.
Result: [1,2,8,5,7] ✓ (1≤2,8; 2≤5,7).
O ( n log n ) hai kyunki har siftDown O ( log n ) hai."
Kyun sahi lagta hai: n nodes × O ( log n ) har ek = n log n . Bilkul tight lagta hai.
Fix: Woh bound loose hai. Woh pretend karta hai ki har node poori height tak sink hota hai. Lekin height-k node sirf O ( k ) cost karta hai, aur aadhe nodes leaves hain jo 0 cost karte hain. Weighted sum ∑ ( n / 2 k + 1 ) ⋅ k converge karta hai, O ( n ) deta hai. Bound O ( n log n ) ek sahi upper bound hai — bas tight nahi hai.
Common mistake Leaves pe siftDown call karna / galat start index.
Kyun sahi lagta hai: "Har node process karo" safe lagta hai.
Fix: Leaves already heaps hain; unhe sifting karna wasted work hai aur children index karna out of bounds ja sakta hai agar guard na ho. Hamesha ⌊ n /2 ⌋ − 1 se start karo.
Common mistake Top-down process karna (pehle root).
Kyun sahi lagta hai: Hum tree ko top-to-bottom padhte hain, toh pehle root fix kyun na karein?
Fix: siftDown(i) ko zaroori hai ki children ki subtrees already valid hon. Top-down jaana us assumption ko tod deta hai aur non-heap produce karta hai. (Agar top-down jaana zaroori hai, toh siftUp chahiye aur poori cheez slower insertion build ban jaati hai.)
Common mistake Insertion build (
siftUp har naye element ke liye) "same cheez" hai.
Kyun sahi lagta hai: Woh bhi ek heap deta hai.
Fix: siftUp numerous leaves ko root ki taraf move karta hai — yeh mehanga direction hai — toh O ( n log n ) deta hai. Bottom-up siftDown mehanga kaam sirf rare unche nodes ko deta hai → O ( n ) .
Recall Feynman: ek 12-saal ke bacche ko samjhao
Ek tournament bracket socho jo ek pyramid ki tarah drawn hai jisme log hain, sabse bada aur taqatwar upar hona chahiye. Zyaatar log bottom rows mein khade hain — bahut saare hain, lekin har ek ko sirf ek ya do log se larna padta hai jo seedha unke upar hain: sasta. Upar jaate jaate, bahut kam log hote hain, aur sirf unhe hi lambi charhaayi karni padti hai. Kyunki bheedbhad wala bottom sasta hai aur lambi charhaiyan rare hain, total kushti sirf "har insaan ke liye lagbhag ek match" hoti hai — yeh log ki sankhya ke saath seedhi line mein badhti hai, zyada tezi se nahi.
"Leaves aaram karte hain, roots bhaari kaam karte hain." Leaves ke baad se shuru karo (⌊ n /2 ⌋ − 1 ), neechein sift karo, upar jao. Aur yeh magic number yaad rakho: ∑ k / 2 k = 2 → constant → O ( n ) .
Bottom-up heapify ki time complexity kya hai? O ( n ) (linear), loose O ( n log n ) bound se tighter.
Bottom-up build-heap kis index se start karta hai? Last internal node, ⌊ n /2 ⌋ − 1 (iske baad sab leaf hain).
Build-heap mein nodes ko bottom se top kyun process karte hain? siftDown(i) ke liye zaroori hai ki dono children ki subtrees already valid heaps hon; bottom-up yeh ordering guarantee karta hai.
Height k wale node pe siftDown ki cost? O ( k ) — woh zyada se zyada k levels sink ho sakta hai.
Height k pe kitne nodes hote hain (zyada se zyada)? Lagbhag n / 2 k + 1 ; unche nodes exponentially rare hain.
Kaun sa infinite sum O ( n ) bound prove karta hai, aur uski value? ∑ k = 0 ∞ k / 2 k = 2 , ek constant.
∑ k x k derive karo.∑ x k = 1/ ( 1 − x ) ko differentiate karo taaki ∑ k x k − 1 = 1/ ( 1 − x ) 2 mile, phir x se multiply karo: ∑ k x k = x / ( 1 − x ) 2 .
Insertion-based build O ( n log n ) kyun hai lekin siftDown build O ( n ) kyun? siftUp numerous leaves ko root ki taraf move karta hai (mehenga direction); siftDown lambi path sirf rare unche nodes ko deta hai.
Children/parent index formulas (0-based)? left = 2 i + 1 , right = 2 i + 2 , parent = ⌊( i − 1 ) /2 ⌋ .
Leaves kyun skip kiye jaate hain? Ek single node already valid heap hai; use sift karna wasted work hai aur out-of-bounds child access ka risk hai.
Binary Heap — woh data structure jo heapify produce karta hai.
Heapsort — build-heap (O ( n ) ) use karta hai phir n extract-max (O ( n log n ) ).
Priority Queue — heaps iska standard implementation hain.
Complete Binary Tree — array↔tree index mapping.
Geometric Series / Amortized Analysis — linear bound ke peeche ka summation trick.
siftUp vs siftDown — direction complexity kyun decide karta hai.
ensures children heaps first
Array as complete binary tree
Heap property parent >= children
Start at last internal node