Ghid pentru grămezi în Python

Ghid pentru grămezi în Python

Introducere

Imaginați-vă un aeroport plin de viață cu zboruri care decolează și aterizează în fiecare minut. Așa cum controlorii de trafic aerian prioritizează zborurile în funcție de urgență, grămelele ne ajută să gestionăm și să procesăm datele pe baza unor criterii specifice, asigurându-se că cea mai „urgentă” sau „importantă” parte de date este întotdeauna accesibilă în partea de sus.

În acest ghid, ne vom porni într-o călătorie pentru a înțelege grămezi de la zero. Vom începe prin a demitifica ceea ce sunt grămezile și proprietățile lor inerente. De acolo, ne vom arunca în propria implementare a heaps de către Python, the heapq modul și explorați setul său bogat de funcționalități. Așadar, dacă v-ați întrebat vreodată cum să gestionați eficient un set dinamic de date în care este necesar frecvent elementul cu cea mai mare (sau cea mai scăzută) prioritate, sunteți cu drag.

Ce este un Heap?

Primul lucru pe care ai dori să-l înțelegi înainte de a te scufunda în utilizarea mormanelor este ce este o grămadă. O grămadă iese în evidență în lumea structurilor de date ca o putere bazată pe arbore, în special calificată în menținerea ordinii și ierarhiei. Deși ar putea să semene cu un arbore binar pentru ochiul neantrenat, nuanțele din structura sa și regulile de guvernare îl deosebesc distinct.

Una dintre caracteristicile definitorii ale unui hap este natura sa ca a arborele binar complet. Aceasta înseamnă că fiecare nivel al copacului, cu excepția poate ultimul, este umplut în întregime. În acest ultim nivel, nodurile se populează de la stânga la dreapta. O astfel de structură asigură că heap-urile pot fi reprezentate și manipulate eficient folosind matrice sau liste, poziția fiecărui element în matrice oglindind plasarea acestuia în arbore.

ghid-pentru-heaps-in-python-01.png

Adevărata esență a unei grămezi, totuși, constă în ea comanda. într-un heap max, valoarea oricărui nod dat depășește sau egalează valorile copiilor săi, poziționând cel mai mare element chiar la rădăcină. Pe de altă parte, a grămadă min funcționează pe principiul opus: valoarea oricărui nod este fie mai mică, fie egală cu valorile copiilor săi, asigurându-se că cel mai mic element se află la rădăcină.

ghid-pentru-heaps-in-python-02.png

Indicații: Puteți vizualiza o grămadă ca a piramida numerelor. Pentru o grămadă maximă, pe măsură ce urcați de la bază până la vârf, numerele cresc, culminând cu valoarea maximă la vârf. În schimb, o grămadă min începe cu valoarea minimă la vârf, cu numere crescând pe măsură ce vă deplasați în jos.

Pe măsură ce progresăm, ne vom aprofunda în modul în care aceste proprietăți inerente ale grămezilor permit operațiuni eficiente și modul în care Python heapq modul integrează fără probleme grămezi în eforturile noastre de codificare.

Caracteristicile și proprietățile grămezilor

Mulțile, cu structura lor unică și principiile de ordonare, aduc un set de caracteristici și proprietăți distincte care le fac de neprețuit în diferite scenarii de calcul.

În primul rând, mormanele sunt eficient în mod inerent. Structura lor bazată pe arbore, în special formatul arborelui binar complet, asigură că operațiuni precum inserarea și extragerea elementelor prioritare (maxim sau minim) pot fi efectuate în timp logaritmic, de obicei O (jurnal n). Această eficiență este un avantaj pentru algoritmi și aplicații care necesită acces frecvent la elementele prioritare.

O altă proprietate notabilă a grămezilor este lor eficienta memoriei. Deoarece grămezile pot fi reprezentate folosind matrice sau liste fără a fi nevoie de pointeri explici către nodurile copil sau părinte, ele economisesc spațiu. Poziția fiecărui element în matrice corespunde plasării acestuia în arbore, permițând traversarea și manipularea previzibilă și simplă.

Proprietatea de ordonare a heaps, fie ca heap maxim sau min heap, asigură acest lucru rădăcina deține întotdeauna elementul cu cea mai mare prioritate. Această ordonare consecventă este ceea ce permite accesul rapid la elementul cu prioritate maximă, fără a fi nevoie să căutați prin întreaga structură.

În plus, mormanele sunt multilateral. În timp ce grămada binară (unde fiecare părinte are cel mult doi copii) sunt cele mai frecvente, grămada poate fi generalizată pentru a avea mai mult de doi copii, cunoscut sub numele de grămezi d-ary. Această flexibilitate permite reglarea fină pe baza cazurilor de utilizare specifice și a cerințelor de performanță.

În cele din urmă, mormanele sunt auto-reglare. Ori de câte ori sunt adăugate sau îndepărtate elemente, structura se rearanjează pentru a-și menține proprietățile. Această echilibrare dinamică asigură că heap-ul rămâne optimizat în orice moment pentru operațiunile sale de bază.

Indicații: Aceste proprietăți au făcut ca structura de date heap să fie potrivită pentru un algoritm de sortare eficient - sortare heap. Pentru a afla mai multe despre sortarea heap în Python, citiți „Sortare în grămada în Python” articol.

Pe măsură ce ne aprofundăm în implementarea Python și în aplicațiile practice, adevăratul potențial al grămezilor se va desfășura în fața noastră.

Tipuri de grămezi

Nu toate grămezile sunt create egale. În funcție de ordinea lor și de proprietățile structurale, grămezile pot fi clasificate în diferite tipuri, fiecare cu propriul său set de aplicații și avantaje. Cele două categorii principale sunt heap max și grămadă min.

Cea mai distinctivă trăsătură a a heap max este că valoarea oricărui nod dat este mai mare sau egală cu valorile copiilor săi. Acest lucru asigură că cel mai mare element din heap se află întotdeauna la rădăcină. O astfel de structură este deosebit de utilă atunci când este nevoie de a accesa frecvent elementul maxim, ca în anumite implementări de cozi de prioritate.

Omologul heap-ului maxim, a grămadă min asigură că valoarea oricărui nod dat este mai mică sau egală cu valorile copiilor săi. Aceasta poziționează cel mai mic element al grămezii la rădăcină. Min heaps sunt de neprețuit în scenariile în care cel mai mic element este de primă importanță, cum ar fi în algoritmii care se ocupă de procesarea datelor în timp real.

Dincolo de aceste categorii primare, grămezile pot fi de asemenea distinse în funcție de factorul lor de ramificare:

În timp ce heap-urile binare sunt cele mai comune, fiecare părinte având cel mult doi copii, conceptul de heaps poate fi extins la noduri care au mai mult de doi copii. Într-o grămadă d-ary, fiecare nod are cel mult d copii. Această variație poate fi optimizată pentru scenarii specifice, cum ar fi scăderea înălțimii copacului pentru a accelera anumite operațiuni.

Heap binomial este un set de arbori binomi care sunt definiti recursiv. Heap-urile binomiale sunt folosite în implementările de cozi prioritare și oferă operațiuni eficiente de îmbinare.

Numit după celebra succesiune Fibonacci, the grămada Fibonacci oferă timpi de rulare mai amortizați pentru multe operațiuni în comparație cu grămezi binare sau binomiale. Sunt deosebit de utile în algoritmii de optimizare a rețelei.

Implementarea Heap a lui Python – The heapq Module

Python oferă un modul încorporat pentru operațiuni heap – the heapq modul. Acest modul oferă o colecție de funcții legate de heap care permit dezvoltatorilor să transforme liste în heaps și să efectueze diverse operații heap fără a fi nevoie de o implementare personalizată. Să ne aprofundăm în nuanțele acestui modul și în modul în care vă aduce puterea grămezilor.

heapq modulul nu oferă un tip de date heap distinct. În schimb, oferă funcții care funcționează pe liste Python obișnuite, transformându-le și tratându-le ca mormane binare.

Această abordare este eficientă din punct de vedere al memoriei și se integrează perfect cu structurile de date existente ale Python.

Asta inseamna ca grămezile sunt reprezentate ca liste in heapq. Frumusețea acestei reprezentări este simplitatea ei – sistemul index al listei pe bază de zero servește ca un arbore binar implicit. Pentru orice element dat la poziție i, este:

  • Copilul stâng este pe poziție 2*i + 1
  • Copilul drept este pe poziție 2*i + 2
  • Nodul părinte este pe poziție (i-1)//2

ghid-pentru-heaps-in-python-03.png

Această structură implicită asigură că nu este nevoie de o reprezentare separată a arborelui binar bazat pe noduri, făcând operațiunile simple și utilizarea memoriei minimă.

Complexitatea spațială: Heap-urile sunt de obicei implementate ca arbori binari, dar nu necesită stocare de pointeri explici pentru nodurile copil. Acest lucru le face să fie eficiente din punct de vedere al spațiului, cu o complexitate spațială de O (n) pentru stocarea n elemente.

Este esențial să rețineți că heapq modul creează min heaps în mod implicit. Aceasta înseamnă că cel mai mic element este întotdeauna la rădăcină (sau prima poziție din listă). Dacă aveți nevoie de un heap maxim, va trebui să inversați ordinea prin înmulțirea elementelor cu -1 sau utilizați o funcție de comparare personalizată.

Python's heapq Modulul oferă o suită de funcții care permit dezvoltatorilor să efectueze diverse operațiuni heap pe liste.

Notă: Pentru a utiliza heapq modulul din aplicația dvs., va trebui să îl importați folosind simplu import heapq.

În secțiunile următoare, ne vom aprofunda în fiecare dintre aceste operațiuni fundamentale, explorând mecanica și cazurile de utilizare ale acestora.

Cum se transformă o listă într-un morman

heapify() funcția este punctul de plecare pentru multe sarcini legate de heap. Este nevoie de un iterabil (de obicei o listă) și își rearanjează elementele la locul lor pentru a satisface proprietățile unui heap min:

Consultați ghidul nostru practic și practic pentru a învăța Git, cu cele mai bune practici, standarde acceptate de industrie și fisa de cheat incluse. Opriți căutarea pe Google a comenzilor Git și de fapt învăţa aceasta!

import heapq data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
heapq.heapify(data)
print(data)

Aceasta va scoate o listă reordonată care reprezintă un heap minim valid:

[1, 1, 2, 3, 3, 9, 4, 6, 5, 5, 5]

Complexitatea timpului: Conversia unei liste neordonate într-un heap utilizând heapify funcția este o O (n) Operațiune. Acest lucru ar putea părea contraintuitiv, așa cum s-ar putea aștepta să fie O (nlogn), dar datorită proprietăților structurii arborelui, acesta poate fi realizat în timp liniar.

Cum să adăugați un element în grămada

heappush() funcția vă permite să inserați un nou element în heap, menținând în același timp proprietățile heap-ului:

import heapq heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 3)
heapq.heappush(heap, 7)
print(heap)

Rularea codului vă va oferi o listă de elemente care mențin proprietatea min heap:

[3, 5, 7]

Complexitatea timpului: Operația de inserare într-un heap, care implică plasarea unui nou element în heap, menținând în același timp proprietatea heap, are o complexitate de timp de O (logn). Acest lucru se datorează faptului că, în cel mai rău caz, elementul ar putea fi nevoit să călătorească de la frunză la rădăcină.

Cum să eliminați și să returnați cel mai mic element din grămada

heappop() funcția extrage și returnează cel mai mic element din heap (rădăcina dintr-un heap min). După eliminare, se asigură că lista rămâne un heap valid:

import heapq heap = [1, 3, 5, 7, 9]
print(heapq.heappop(heap))
print(heap)

Notă: heappop() este de neprețuit în algoritmii care necesită procesarea elementelor în ordine crescătoare, cum ar fi algoritmul Heap Sort, sau atunci când implementează cozi prioritare în care sarcinile sunt executate în funcție de urgența lor.

Aceasta va scoate cel mai mic element și lista rămasă:

1
[3, 7, 5, 9]

Aici, 1 este cel mai mic element din heap, iar lista rămasă a menținut proprietatea heap, chiar și după ce am eliminat 1.

Complexitatea timpului: Eliminarea elementului rădăcină (care este cel mai mic dintr-un heap min sau cel mai mare dintr-un heap maxim) și reorganizarea heap-ului necesită de asemenea O (logn) timp.

Cum să împingeți un articol nou și să deschideți cel mai mic element

heappushpop() funcția este o operațiune combinată care împinge un nou element în heap și apoi apare și returnează cel mai mic element din heap:

import heapq heap = [3, 5, 7, 9]
print(heapq.heappushpop(heap, 4)) print(heap)

Aceasta va ieși 3, cel mai mic element și tipăriți-l pe cel nou heap lista care include acum 4 păstrând în același timp proprietatea heap:

3
[4, 5, 7, 9]

Notă: Utilizarea heappushpop() funcția este mai eficientă decât efectuarea operațiunilor de împingere a unui nou element și de explozie pe cel mai mic separat.

Cum să înlocuiți cel mai mic element și să împingeți un articol nou

heapreplace() funcția afișează cel mai mic element și împinge un nou element pe grămada, totul într-o singură operațiune eficientă:

import heapq heap = [1, 5, 7, 9]
print(heapq.heapreplace(heap, 4))
print(heap)

Aceasta se imprimă 1, cel mai mic element, iar lista include acum 4 și menține proprietatea heap:

1
[4, 5, 7, 9]

notițe: heapreplace() este benefic în scenariile de streaming în care doriți să înlocuiți cel mai mic element actual cu o nouă valoare, cum ar fi operațiunile cu ferestre rulante sau sarcinile de procesare a datelor în timp real.

Găsirea mai multor extreme în grămada lui Python

nlargest(n, iterable[, key]) și nsmallest(n, iterable[, key]) funcțiile sunt concepute pentru a prelua mai multe elemente mai mari sau mai mici dintr-un iterabil. Ele pot fi mai eficiente decât sortarea întregului iterabil atunci când aveți nevoie doar de câteva valori extreme. De exemplu, să presupunem că aveți următoarea listă și doriți să găsiți trei cele mai mici și trei cele mai mari valori în listă:

data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

Aici, nlargest() și nsmallest() funcțiile pot fi utile:

import heapq data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(heapq.nlargest(3, data)) print(heapq.nsmallest(3, data)) 

Acest lucru vă va oferi două liste - una conține cele trei valori mai mari și cealaltă conține cele trei valori cele mai mici din data listă:

[9, 6, 5]
[1, 1, 2]

Cum să vă construiți Heap personalizat

În timp ce al lui Python heapq modulul oferă un set robust de instrumente pentru lucrul cu heaps, există scenarii în care comportamentul implicit min heap ar putea să nu fie suficient. Indiferent dacă doriți să implementați un heap maxim sau aveți nevoie de un heap care să funcționeze pe baza unor funcții de comparare personalizate, construirea unui heap personalizat poate fi răspunsul. Să explorăm cum să adaptam grămada la nevoile specifice.

Implementarea unui Max Heap folosind heapq

În mod implicit, heapq creează min grame. Cu toate acestea, cu un truc simplu, îl puteți folosi pentru a implementa un heap maxim. Ideea este de a inversa ordinea elementelor prin înmulțirea lor cu -1 înainte de a le adăuga la grămada:

import heapq class MaxHeap: def __init__(self): self.heap = [] def push(self, val): heapq.heappush(self.heap, -val) def pop(self): return -heapq.heappop(self.heap) def peek(self): return -self.heap[0]

Cu această abordare, cel mai mare număr (în termeni de valoare absolută) devine cel mai mic, permițând heapq funcții pentru a menține o structură de heap maximă.

Multe cu funcții de comparare personalizate

Uneori, s-ar putea să aveți nevoie de o grămadă care nu se compară doar pe baza ordinii naturale a elementelor. De exemplu, dacă lucrați cu obiecte complexe sau aveți criterii de sortare specifice, o funcție de comparare personalizată devine esențială.

Pentru a realiza acest lucru, puteți împacheta elemente într-o clasă de ajutor care suprascrie operatorii de comparație:

import heapq class CustomElement: def __init__(self, obj, comparator): self.obj = obj self.comparator = comparator def __lt__(self, other): return self.comparator(self.obj, other.obj) def custom_heappush(heap, obj, comparator=lambda x, y: x < y): heapq.heappush(heap, CustomElement(obj, comparator)) def custom_heappop(heap): return heapq.heappop(heap).obj

Cu această setare, puteți defini orice funcție de comparare personalizată și o puteți utiliza cu heap.

Concluzie

Mulțile oferă performanțe previzibile pentru multe operațiuni, făcându-le o alegere fiabilă pentru sarcinile bazate pe priorități. Cu toate acestea, este esențial să luați în considerare cerințele și caracteristicile specifice ale aplicației în cauză. În unele cazuri, ajustarea implementării heap-ului sau chiar optarea pentru structuri de date alternative ar putea produce performanțe mai bune în lumea reală.

Mulțile, așa cum am parcurs, sunt mai mult decât o altă structură de date. Ele reprezintă o confluență de eficiență, structură și adaptabilitate. De la proprietățile lor fundamentale până la implementarea lor în Python heapq modul, heaps oferă o soluție robustă la o multitudine de provocări de calcul, în special cele centrate pe prioritate.

Timestamp-ul:

Mai mult de la Stackabuse