Ghid pentru stive în Python

Ghid pentru stive în Python

Introducere

În timp ce unele structuri de date sunt versatile și pot fi utilizate într-o gamă largă de aplicații, altele sunt specializate și concepute pentru a gestiona probleme specifice. O astfel de structură specializată, cunoscută pentru simplitatea sa, dar utilitatea sa remarcabilă, este stivui.

Deci, ce este o stivă? În esență, o stivă este o structură de date liniară care urmează LIFO Principiul (Last In First Out).. Gândește-te la asta ca la un teanc de farfurii într-o cantină; luați doar farfuria care este deasupra, iar când plasați o nouă farfurie, aceasta merge în partea de sus a stivei.

Ultimul element adăugat este primul element care trebuie eliminat

Principiul LIFO

Dar, de ce este crucială înțelegerea stivei? De-a lungul anilor, stivele și-au găsit aplicațiile într-o multitudine de domenii, de la gestionarea memoriei în limbajele de programare preferate până la funcționalitatea butonului înapoi din browserul dvs. web. Această simplitate intrinsecă, combinată cu aplicabilitatea sa vastă, face ca stiva să fie un instrument indispensabil în arsenalul unui dezvoltator.

În acest ghid, ne vom scufunda în profunzime în conceptele din spatele stivelor, implementarea acestora, cazurile de utilizare și multe altele. Vom defini ce sunt stivele, cum funcționează acestea, apoi vom arunca o privire la două dintre cele mai comune moduri de a implementa structura de date a stivei în Python.

Concepte fundamentale ale unei structuri de date stive

În esență, o stivă este înșelător de simplă, dar posedă nuanțe care îi conferă aplicații versatile în domeniul computațional. Înainte de a aborda implementările și utilizările sale practice, să ne asigurăm o înțelegere solidă a conceptelor de bază din jurul stivelor.

Principiul LIFO (Last In First Out).

LIFO este principiul călăuzitor din spatele unei stive. Implică faptul că ultimul element care intră în stivă este primul care iese. Această caracteristică diferențiază stivele de alte structuri de date liniare, cum ar fi cozile.

Notă: Un alt exemplu util pentru a vă ajuta să vă învăluiți conceptul despre modul în care funcționează stivele este să vă imaginați că oamenii intrând și ieșind dintr-un Lift - ultima persoană care intră într-un lift este prima care iese!

Operațiuni de bază

Fiecare structură de date este definită de operațiunile pe care le suportă. Pentru stive, aceste operațiuni sunt simple, dar vitale:

  • Împinge – Adaugă un element în partea de sus a stivei. Dacă stiva este plină, această operațiune poate duce la o depășire a stivei.

Operare LIFO push

  • pop – Îndepărtează și returnează elementul cel mai de sus al stivei. Dacă stiva este goală, încercarea de a face pop poate cauza o supraaglomerare a stivei.

Operațiune LIFO pop

  • Peek (sau Top) – Observă elementul superior fără a-l îndepărta. Această operație este utilă atunci când doriți să inspectați elementul superior curent fără a modifica starea stivei.

Până acum, semnificația structurii de date stivei și a conceptelor sale fundamentale ar trebui să fie evidentă. Pe măsură ce avansăm, ne vom scufunda în implementările sale, aruncând lumină asupra modului în care aceste principii fundamentale se traduc în cod practic.

Cum să implementați o stivă de la zero în Python

După ce am înțeles principiile de bază din spatele stivelor, este timpul să ne suflecăm mânecile și să ne adâncim în partea practică a lucrurilor. Implementarea unei stive, deși simplă, poate fi abordată în mai multe moduri. În această secțiune, vom explora două metode principale de implementare a unei stive – folosind matrice și liste legate.

Implementarea unei stive folosind matrice

Matrice, fiind locații de memorie învecinate, oferă un mijloc intuitiv de a reprezenta stive. Ele permit complexitatea timpului O(1) pentru accesarea elementelor prin index, asigurând operații rapide de push, pop și peek. De asemenea, matricele pot fi mai eficiente din punct de vedere al memoriei, deoarece nu există nicio suprasarcină a pointerilor ca în listele legate.

Pe de altă parte, matricele tradiționale au o dimensiune fixă, adică odată inițializate, nu pot fi redimensionate. Acest lucru poate duce la a depășirea stivei dacă nu sunt monitorizate. Acest lucru poate fi depășit de matrice dinamice (cum ar fi cele ale lui Python list), care poate redimensiona, dar această operațiune este destul de costisitoare.

Cu toate acestea din drum, să începem să implementăm clasa noastră de stivă folosind matrice în Python. În primul rând, să creăm o clasă în sine, cu constructorul care ia dimensiunea stivei ca parametru:

class Stack: def __init__(self, size): self.size = size self.stack = [None] * size self.top = -1

După cum puteți vedea, am stocat trei valori în clasa noastră. The size este dimensiunea dorită a stivei, the stack este matricea reală utilizată pentru a reprezenta structura de date a stivei și top este indicele ultimului element din stack matrice (partea de sus a stivei).

De acum înainte, vom crea și explica o metodă pentru fiecare dintre operațiunile de bază ale stivei. Fiecare dintre aceste metode va fi cuprinsă în Stack clasa pe care tocmai am creat-o.

Să începem cu push() metodă. După cum sa discutat anterior, operația de împingere adaugă un element în partea de sus a stivei. În primul rând, vom verifica dacă stiva mai are spațiu pentru elementul pe care vrem să-l adăugăm. Dacă stiva este plină, vom ridica valoarea Stack Overflow excepție. În caz contrar, vom adăuga doar elementul și vom ajusta top și stack în consecinţă:

def push(self, item): if self.top == self.size - 1: raise Exception("Stack Overflow") self.top += 1 self.stack[self.top] = item

Acum, putem defini metoda de eliminare a unui element din partea de sus a stivei – the pop() metodă. Înainte de a încerca chiar să eliminam un element, ar trebui să verificăm dacă există elemente în stivă, deoarece nu are rost să încercăm să scoatem un element dintr-o stivă goală:

def pop(self): if self.top == -1: raise Exception("Stack Underflow") item = self.stack[self.top] self.top -= 1 return item

În cele din urmă, putem defini peek() metodă care returnează doar valoarea elementului care se află în prezent în partea de sus a stivei:

def peek(self): if self.top == -1: raise Exception("Stack is empty") return self.stack[self.top]

Si asta e! Acum avem o clasă care implementează comportamentul stivelor folosind liste în Python.

Implementarea unei stive folosind liste legate

Liste legate, fiind structuri dinamice de date, poate crește și se micșorează cu ușurință, ceea ce poate fi benefic pentru implementarea stivelor. Deoarece listele conectate alocă memorie după cum este necesar, stiva poate crește și reduce dinamic, fără a fi nevoie de o redimensionare explicită. Un alt avantaj al utilizării listelor legate pentru a implementa stive este că operațiunile push și pop necesită doar modificări simple ale pointerului. Dezavantajul este că fiecare element din lista legată are un indicator suplimentar, consumând mai multă memorie în comparație cu matricele.

După cum am discutat deja în „Liste conectate Python” articol, primul lucru pe care ar trebui să-l implementăm înainte de lista reală legată este o clasă pentru un singur nod:

class Node: def __init__(self, data): self.data = data self.next = None

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!

Această implementare stochează doar două puncte de date – valoarea stocată în nod (data) și referința la următorul nod (next).

Seria noastră în trei părți despre listele legate în Python:

Acum putem sări pe clasa stivă propriu-zisă. Constructorul va fi puțin diferit de cel precedent. Va conține o singură variabilă - referința la nodul din partea de sus a stivei:

class Stack: def __init__(self): self.top = None

Așa cum era de așteptat, push() metoda adaugă un nou element (nod în acest caz) în partea de sus a stivei:

def push(self, item): node = Node(item) if self.top: node.next = self.top self.top = node

pop() metoda verifică dacă există elemente în stivă și îl elimină pe cel de sus dacă stiva nu este goală:

def pop(self): if not self.top: raise Exception("Stack Underflow") item = self.top.data self.top = self.top.next return item

În sfârșit, peek() metoda citește pur și simplu valoarea elementului din partea de sus a stivei (dacă există una):

def peek(self): if not self.top: raise Exception("Stack is empty") return self.top.data

Notă: Interfața ambelor Stack clasele este aceeași – singura diferență este implementarea internă a metodelor de clasă. Aceasta înseamnă că puteți comuta cu ușurință între diferite implementări, fără să vă faceți griji cu privire la elementele interne ale claselor.

Alegerea dintre matrice și liste legate depinde de cerințele și constrângerile specifice ale aplicației.

Cum să implementați o stivă folosind structurile încorporate ale Python

Pentru mulți dezvoltatori, construirea unei stive de la zero, deși educațională, poate să nu fie cea mai eficientă modalitate de a utiliza o stivă în aplicațiile din lumea reală. Din fericire, multe limbaje de programare populare sunt echipate cu structuri de date și clase încorporate care suportă în mod natural operațiunile de stivă. În această secțiune, vom explora ofertele Python în acest sens.

Python, fiind un limbaj versatil și dinamic, nu are o clasă de stivă dedicată. Cu toate acestea, structurile de date încorporate, în special listele și clasa deque din collections modul, poate servi fără efort ca stive.

Folosirea listelor Python ca stive

Listele Python pot emula o stivă destul de eficient datorită naturii lor dinamice și a prezenței unor metode precum append() și pop().

  • Operare push – Adăugarea unui element în partea de sus a stivei este la fel de simplă ca și utilizarea append() metodă:

    stack = []
    stack.append('A')
    stack.append('B')
    
  • Operațiunea pop – Îndepărtarea elementului superior poate fi realizată folosind pop() metoda fara niciun argument:

    top_element = stack.pop() 
  • Operațiunea Peek Accesul în partea de sus fără a se deschide se poate face folosind indexarea negativă:

    top_element = stack[-1] 

Utilizarea deque Clasa de la colecții Module

deque (Prescurtare pentru coada dublă) este un alt instrument versatil pentru implementările stivei. Este optimizat pentru anexări rapide și pop-uri de la ambele capete, făcându-l puțin mai eficient pentru operațiunile de stivă decât pentru liste.

  • Inițializarea:

    from collections import deque
    stack = deque()
    
  • Operare push - Similar listelor, append() se foloseste metoda:

    stack.append('A')
    stack.append('B')
    
  • Operațiunea pop – Like liste, pop() metoda face treaba:

    top_element = stack.pop() 
  • Operațiunea Peek – Abordarea este aceeași ca și în cazul listelor:

    top_element = stack[-1] 

Când să folosiți pe care?

În timp ce atât listele, cât și deques-urile pot fi folosite ca stive, dacă utilizați în principal structura ca stivă (cu anexări și pops de la un capăt), deque poate fi puțin mai rapid datorită optimizării sale. Cu toate acestea, pentru cele mai multe scopuri practice și cu excepția cazului în care se ocupă cu aplicații critice pentru performanță, listele Python ar trebui să fie suficiente.

Notă: Această secțiune se scufundă în ofertele încorporate ale Python pentru un comportament asemănător stivei. Nu trebuie neapărat să reinventezi roata (prin implementarea stivei de la zero) atunci când ai instrumente atât de puternice la îndemână.

Potențiale probleme legate de stiva și cum să le depășiți

Deși stivele sunt incredibil de versatile și eficiente, ca orice altă structură de date, ele nu sunt imune la potențiale capcane. Este esențial să recunoaștem aceste provocări atunci când lucrezi cu stive și să existe strategii pentru a le rezolva. În această secțiune, vom explora câteva probleme comune legate de stiva și vom explora modalități de a le depăși.

Depășirea stivei

Acest lucru se întâmplă atunci când se încearcă împingerea unui element pe o stivă care și-a atins capacitatea maximă. Este o problemă mai ales în mediile în care dimensiunea stivei este fixă, cum ar fi în anumite scenarii de programare de nivel scăzut sau apeluri recursive de funcții.

Dacă utilizați stive bazate pe matrice, luați în considerare trecerea la matrice dinamice sau implementări cu liste conectate, care se redimensionează. Un alt pas în prevenirea depășirii stivei este monitorizarea continuă a dimensiunii stivei, în special înainte de operațiunile push și furnizarea de mesaje de eroare clare sau solicitări pentru depășirea stivei.

Dacă depășirea stivei are loc din cauza apelurilor recursive excesive, luați în considerare soluții iterative sau măriți limita recursiunii dacă mediul o permite.

Stivă Underflow

Acest lucru se întâmplă atunci când există o încercare de a scoate un element dintr-o stivă goală. Pentru a preveni acest lucru, verificați întotdeauna dacă stiva este goală înainte de a executa operațiuni pop sau peek. Întoarceți un mesaj de eroare clar sau gestionați underflow-ul cu grație, fără a bloca programul.

În mediile în care este acceptabil, luați în considerare returnarea unei valori speciale atunci când ieșiți dintr-o stivă goală pentru a semnifica invaliditatea operației.

Constrângeri de memorie

În mediile constrânse de memorie, chiar și redimensionarea dinamică a stivelor (cum ar fi cele bazate pe liste legate) poate duce la epuizarea memoriei dacă devin prea mari. Prin urmare, urmăriți utilizarea memoriei generale a aplicației și creșterea stivei. Poate introduceți un capac moale pe dimensiunea stivei.

Preocupări privind siguranța firelor

În mediile cu mai multe fire de execuție, operațiunile simultane pe o stivă partajată de către diferite fire de execuție pot duce la inconsecvențe de date sau comportamente neașteptate. Soluțiile potențiale la această problemă ar putea fi:

  • Mutexuri și încuietori – Utilizați mutexuri (obiecte de excludere reciprocă) sau blocări pentru a vă asigura că doar un fir poate efectua operațiuni pe stivă la un moment dat.
  • Operațiuni atomice – Utilizați operațiunile atomice, dacă sunt susținute de mediu, pentru a asigura coerența datelor în timpul operațiunilor push și pop.
  • Stive locale de fire – În scenariile în care fiecare fir de execuție are nevoie de stiva sa, luați în considerare utilizarea stocării locale a firului de execuție pentru a oferi fiecărui thread instanța de stivă separată.

Deși stivele sunt într-adevăr puternice, conștientizarea problemelor lor potențiale și implementarea activă a soluțiilor va asigura aplicații robuste și fără erori. Recunoașterea acestor capcane este jumătate din luptă – cealaltă jumătate este adoptarea celor mai bune practici pentru a le aborda în mod eficient.

Concluzie

Stivele, în ciuda naturii lor aparent simple, stau la baza multor operațiuni fundamentale din lumea computerelor. De la analizarea expresiilor matematice complexe până la gestionarea apelurilor de funcții, utilitatea lor este largă și esențială. Pe măsură ce am parcurs profundele și dezavantajele acestei structuri de date, este clar că puterea ei constă nu doar în eficiență, ci și în versatilitate.

Cu toate acestea, ca și în cazul tuturor instrumentelor, eficacitatea sa depinde de modul în care este utilizat. Doar asigurați-vă că aveți o înțelegere aprofundată a principiilor, potențialelor capcane și a celor mai bune practici, pentru a vă asigura că puteți valorifica adevărata putere a stivelor. Indiferent dacă implementați una de la zero sau folosiți facilitățile încorporate în limbi precum Python, aplicarea atentă a acestor structuri de date este cea care vă va diferenția soluțiile.

Timestamp-ul:

Mai mult de la Stackabuse