Binary Heap C# | Data Structure
Binary Heap
data structure ma'lumotlarni to'plamga juda tez qo'shish O(log N)
va shu to'plamdagi eng katta/kichik elementni konstant O(1)
vaqtda o'qish imkonini beradigan complete tree]. U heap sort
, priority queue
va Dijkstra's
algoritmlarini o'zagi hisoblanadi.
Intro 👋
Uni 2 ta asosiy hususiyatlari bor:
✔️ Complete Tree ✔️ Heap Property
Ushbu post orqali siz Binary Heap
ni C# dasturlash tilida eng sodda usulda tushintirilishi visual elementlar orqali o'rganasiz 🚀
Note
Ish maydonini tayyorlab olish uchun Heap.cs
nomli fayl yarating va uni ichida empty Heap
deb nomlangan class
yozing.
public class Heap<T> where T : IComparable<T>
Heap
strukturaasi ichida istalgan turdagi ma'lumot saqlay olishni ta'minlash uchun uni genericHeap<T>
qilib e'lon qilamiz.- Heapdagi
where T : IComparable<T>
constrainti heap propertyni saqlash uchun solishtirishda ishlatiladi.
Node class
🥬
Data Stucture olamida node iborasi juda ko'p ishlatiladi. Ular strukturani o'zagi bo'lib ma'lumotlarni o'rab turishda ishlatialdi.
Binary Heap
strukturasida har bir node quyidagi elementlarni o'z o'chiga oladi.
💡 Node
klasi faqat Heap
kaslini ichki amallarida ishlatigani uchun uni private
qilib Heap class ichida elon qilamiz.
public class Heap<T> where T : IComparable<T>
{
private class Node(T value)
{
public T Value { get; set; } = value;
private Node right = default;
public ref Node Right => ref right;
private Node left = default;
public ref Node Left => ref left;
DETAIL
💡 Node
classida C# 12dagi primary constructor hususiyati ishlatilgan.
private class Node(T value)
Right
vaLeft
child-nodelarni keyinchalik to'liq manipulatsiya qila olishimiz va C# da propertylargaref
kalit so'zini qo'yish mumkin emasligi uchunref
orqali private backing fieldlar e'lon qilib ma'lumotlarni shu fieldlarda saqlaymiz. Keyin u fieldlarga propertylar orqali murojat qilamiz.
private Node right = default;
public ref Node Right => ref right;
private Node left = default;
public ref Node Left => ref left;
💡 Keling endi Node
classiga keynchalik asqotadigan helper methodlar qo'shib chiqamiz.
Node.Size
📏
DETAIL
public int Size => (left?.Size ?? 0) + (right?.Size ?? 0) + 1;
Node
classidagi Size
propertysi recursive tarzda joriy Node hajmini aniqlaydi. Har bir nodening sizei 1 + Left.Size + Right.Size
ga teng.
Node.SmallerChild
⭐ Endi Node
classini asosiy methodlariga o'tamiz.
Node.Heapify()
DETAIL
Heapify
funksiyasi heap propertyni saqlab qolishni ta'minlaydi.
public void Heapify()
{
if(Left is not null && Left.Value.CompareTo(Value) < 0)
SwapValueWith(Left);
if(Right is not null && Right.Value.CompareTo(Value) < 0)
SwapValueWith(Right);
}
Warning
Rasmda Max-heap
ifodalangani uchun 3 va 1 qiymatlari swap qilinishi kerak.
Node.SwapValueWith(Node other)
DETAIL
Ushbu funksiya joriy node qiymatini berilgan node qiymati bilan alishtiradi.
public void SwapValueWith(Node other) => (other.Value, Value) = (Value, other.Value);
- E'tibor bering, faqat qiymatlar alishtirilgani uchun bu funksiyada hech qanday
ref
kalit so'zi ishlatilmagan.
⭐ Ana endi navbat Heap
classini o'ziga keldi. Uni memberlari haqida birma-bir to'xtalamiz.
Heap.root
Heap
classida root
nomli private field yasab olamiz. U data strukturamiz boshlanish nuqtasi vazifasini bajaradi va birinchi nodega reference bo'ladi. Har safar Heap
ni traverse qilish shu objectdan boshlanadi.
private Node head = default;
Heap
classida tashqariga ko'rinuvchi 3ta asosiy public methodlari mavjud. Ularni har biri haqida quyida batafsil to'xtalamiz.
Print()
-Heap
classini traverse qilib har bir node qiymatini chop etish uchun.Enqueue(T value)
- yangi qiymatni kerakli joyga joylash uchun.T Dequeue()
- eng tepadagi birichi elementni qaytarib uni o'chirib yuboradi va heap propertyni qayta hisoblab qo'yadi.
Heap.Enqueue
public void Enqueue(T value) => EnqueueRecursive(ref head, new Node(value));
private void EnqueueRecursive(ref Node current, Node newNode)
{
if(current is null)
current = newNode;
else if(current.Left is null)
current.Left = newNode;
else if(current.Right is null)
current.Right = newNode;
else
EnqueueRecursive(ref current.SmallerChild, newNode);
current.Heapify();
}
DETAIL
Ko'rib turganingizdek Enqueue()
public methodi shunchaki EnqueueRecursive
ga kerakli parametrlarni uzatib yuboradi holos. Asosiy logika ushbu private recursive method ichida amalga oshadi.
Yangi elementi qo'shish O(log n)
vaqt talab qiladi.
Logikaga e'tibor bering, shunchaki recursive tarzda
null reference
ya'niHeap
tubiga yetib tushmaguncha kichikroq shox tanlab, shu shox bo'ylab tushib boriladi.null reference
ga yetib kelganda, yangi node shu yerga joylanadi.ref
imkoniyati bilan, current obyekti qiymatinull
bolsa, ushbu obyekt referenci yangi yaratilgan nodega qaratib qo'yiladi. Let that sink in! 🤯.Method oxirida
current.Heapify();
chaqiruvi heapni eng tubiga tushib ortga rekursiv qaytish vaqtida har bir nodening heap propertysini qayta hisoblab qo'yadi 💥.
Heap.Dequeue
public T Dequeue()
{
if(head is null)
throw new InvalidOperationException("Heap is empty.");
var temp = head.Value;
HeapifyDownRecursive(ref head);
return temp;
}
private void HeapifyDownRecursive(ref Node current)
{
if(current.Left is null && current.Right is null)
current = null;
else if(current.Right is null)
{
current.SwapValueWith(current.Left);
HeapifyDownRecursive(ref current.Left);
}
else if(current.Left is null)
{
current.SwapValueWith(current.Right);
HeapifyDownRecursive(ref current.Right);
}
else if(current.Left.Value.CompareTo(current.Right.Value) < 0)
{
current.SwapValueWith(current.Left);
HeapifyDownRecursive(ref current.Left);
}
else
{
current.SwapValueWith(current.Right);
HeapifyDownRecursive(ref current.Right);
}
}
DETAIL
Dequeue()
- bu root-node o'chiriladi va uni o'rniga ikkita childdan biri priority bo'yicha tanlab olinadi, va huddi shu tartibda uni childlari ham bittadan yuqoriga siljiydi
Dequeue ham Enqueue kabi
O(log n)
** vaqt talab qiladi.Avvalo heap empty bo'lsa
InvalidOperationException
otamiz.Yo'qsa, rekursiv tarzda eng kichik qiymatli elementni
current
bilan swap qilib, pastga qarab ketaveramiz.leaf-nodega yetib borganimizda, uni
null reference
ga tenglab qo'yamiz.Bu orqali osha eng oxiridagi nodeni o'chirgan bo'lamiz. Xotirani bo'shatishni
GC
o'zi qoyillatadi.
Heap.Print
public void Print()
{
var queue = new Queue<Node>();
if(head is not null)
queue.Enqueue(head);
while(queue.Any())
{
var node = queue.Dequeue();
Console.Write("{0} ", node.Value);
if(node.Left is not null)
queue.Enqueue(node.Left);
if(node.Right is not null)
queue.Enqueue(node.Right);
}
Console.WriteLine();
}
DETAIL
Print
funksiyasida qiymatlarni to'g'ri ketma ketlikda chop etish uchun Queue
strukturasidan foydalanilgan.
Queue
da nodelar qolmaguncha birinchi nodeni olib chop etamiz va uni child nodelarini queuega qo'shib ketaveramiz.- Shu orqali har bir qatordagi node chapdan o'ngga qarab bir xilda chop etib ketaveramiz.
Heap.Print
Vanihoyat Heap
klasimizdan obyekt olib uni sinab ko'rsak bo'ladi 😎
// Program.cs: testing
var heap = new Heap<int>();
int[] sonlar = [2, 3, 1, 5, 0, 6, 4];
foreach(var son in sonlar)
heap.Enqueue(son);
heap.Print();
for(int i = 0; i < 10; i++)
Console.WriteLine(heap.Dequeue());
Console.WriteLine(heap.Dequeue()); // Exception
⭐ To'liq kodni mana bu yerda topasiz. ⭐ |
---|
Kontent yoqqan bo'lsa unga izoh qoldirish va do'slaringizga ulashish orqali sifatli o'zbek tilidagi kontentni qo'llab quvvatlashingizni so'raymiz ❤️.
HINT
Keyingi maqolada Binary Heap Data Structureni eng ko'p uchraydigan use caselarini misollar orqali o'rganamiz.
Shu kabi sifatli o'zbek tilidagi kontentni qo'llab quvvatlash uchun bizni Telegram Kanalimizga obuna bo'ling.