Útmutató a Stackekhez Pythonban

Útmutató a Stackekhez Pythonban

Bevezetés

Míg egyes adatstruktúrák sokoldalúak, és sokféle alkalmazásban használhatók, mások speciálisak és speciális problémák kezelésére készültek. Az egyik ilyen speciális szerkezet, amely egyszerűségéről és mégis figyelemre méltó hasznosságáról ismert, a verem.

Szóval, mi az a verem? A verem lényegében egy lineáris adatstruktúra, amely követi a LIFO (Last In First Out) elv. Tekintsd úgy, mint egy halom tányért egy kávézóban; csak azt a tányért veszed, ami a tetején van, és új tányér elhelyezésekor az a köteg tetejére kerül.

Az utoljára hozzáadott elem az első elem, amelyet el kell távolítani

LIFO elv

De miért fontos a verem megértése? Az évek során a stackek rengeteg területen találták meg alkalmazásukat, a kedvenc programozási nyelvek memóriakezelésétől a webböngésző vissza gombjaiig. Ez a lényegi egyszerűség a széleskörű alkalmazhatóságával párosulva a verem nélkülözhetetlen eszközzé teszi a fejlesztők arzenáljában.

Ebben az útmutatóban részletesen bemutatjuk a veremek mögött rejlő fogalmakat, azok megvalósítását, használati eseteit és még sok mást. Meghatározzuk, hogy melyek a veremek, hogyan működnek, majd megvizsgáljuk a verem-adatstruktúra Pythonban való megvalósításának két leggyakoribb módját.

A verem-adatstruktúra alapfogalmai

A verem lényegét tekintve megtévesztően egyszerű, mégis olyan árnyalatokkal rendelkezik, amelyek sokoldalú alkalmazásokat biztosítanak számára a számítási területen. Mielőtt belemerülnénk a megvalósításba és a gyakorlati felhasználásba, biztosítsuk a veremeket körülvevő alapvető fogalmak sziklaszilárd megértését.

A LIFO (Last In First Out) elv

LIFO ez a vezérelv a verem mögött. Ez azt jelenti, hogy az utolsó elem, amelyik utoljára lép be a verembe, az elsőként távozik. Ez a jellemző megkülönbözteti a veremeket más lineáris adatstruktúráktól, például a várólistáktól.

Jegyzet: Egy másik hasznos példa, amely segíthet a halom működésének fogalmán, hogy képzeljük el, amint az emberek be- és kiszállnak egy lift - aki utoljára lép be a liftbe, az elsőként száll ki!

Alapműveletek

Minden adatszerkezetet az általa támogatott műveletek határoznak meg. A veremeknél ezek a műveletek egyszerűek, de létfontosságúak:

  • Nyomja – Elemet ad a verem tetejére. Ha a verem megtelt, ez a művelet verem túlcsordulást eredményezhet.

LIFO push művelet

  • Pop – Eltávolítja és visszaküldi a köteg legfelső elemét. Ha a verem üres, a felpattanási kísérlet verem alulcsordulást okozhat.

LIFO pop művelet

  • Bepillantás (vagy felül) – Megfigyeli a legfelső elemet anélkül, hogy eltávolítaná. Ez a művelet akkor hasznos, ha az aktuális felső elemet a verem állapotának megváltoztatása nélkül szeretné megvizsgálni.

Mára már nyilvánvalóvá kell válnia a verem adatszerkezetének és alapfogalmainak jelentősége. Ahogy haladunk előre, belemerülünk a megvalósításába, és rávilágítunk arra, hogy ezek az alapvető elvek hogyan ültethetők át a gyakorlati kódba.

Hogyan valósíthatunk meg egy veremet a semmiből a Pythonban

Miután felfogtuk a halmok mögött rejlő alapelveket, itt az ideje, hogy feltűrjük az ingujjunkat, és beleássuk magunkat a dolgok gyakorlati oldalába. A verem megvalósítása, bár egyszerű, többféleképpen is megközelíthető. Ebben a részben a verem megvalósításának két elsődleges módszerét vizsgáljuk meg – tömbök és csatolt listák használatával.

Verem megvalósítása tömbök használatával

Tömbök, lét összefüggő memóriahelyek, intuitív módszert kínálnak a halmok ábrázolására. Lehetővé teszik az O(1) időbonyolítást az elemek index általi eléréséhez, biztosítva a gyors push, pop és peek műveleteket. Ezenkívül a tömbök memóriahatékonyabbak lehetnek, mivel nincs több mutató, mint a hivatkozott listákban.

Másrészt a hagyományos tömbök fix méretűek, vagyis az inicializálás után nem méretezhetők át. Ez oda vezethet, hogy a verem túlcsordulás ha nem figyelik. Ez leküzdhető dinamikus tömbökkel (mint például a Python list), amely átméretezhető, de ez a művelet meglehetősen költséges.

Ha mindez nincs az útban, kezdjük el a veremosztályunk megvalósítását tömbök segítségével Pythonban. Először is hozzunk létre egy osztályt azzal a konstruktorral, amely a verem méretét veszi paraméterként:

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

Amint látja, három értéket tároltunk az osztályunkban. A size a verem kívánt mérete, a stack az a tényleges tömb, amelyet a verem adatszerkezetének ábrázolására használnak, és a top az utolsó elem indexe a stack tömb (a verem teteje).

Mostantól minden alapvető veremművelethez létrehozunk és elmagyarázunk egy metódust. Ezen módszerek mindegyikét tartalmazza a Stack osztályt, amit most hoztunk létre.

Kezdjük a push() módszer. Amint azt korábban tárgyaltuk, a push művelet hozzáad egy elemet a verem tetejéhez. Először is ellenőrizzük, hogy a veremben van-e szabad hely a hozzáadni kívánt elem számára. Ha megtelt a verem, emeljük a Stack Overflow kivétel. Ellenkező esetben csak hozzáadjuk az elemet, és beállítjuk a top és a stack Eszerint:

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

Most meghatározhatjuk a módszert egy elem eltávolítására a verem tetejéről - a pop() módszer. Mielőtt még megpróbálnánk eltávolítani egy elemet, ellenőriznünk kell, hogy vannak-e elemek a veremben, mert nincs értelme megpróbálni egy elemet üres veremből kiemelni:

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

Végül meghatározhatjuk a peek() metódus, amely éppen annak az elemnek az értékét adja vissza, amely jelenleg a verem tetején van:

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

És ez az! Most van egy osztályunk, amely a veremek viselkedését a Python listáival valósítja meg.

Verem megvalósítása linkelt listák használatával

Kapcsolt listák, lény dinamikus adatstruktúrák, könnyen nőhet és zsugorodhat, ami előnyös lehet a veremek megvalósításához. Mivel a csatolt listák szükség szerint foglalják le a memóriát, a verem dinamikusan nőhet és csökkenhet anélkül, hogy kifejezett átméretezésre lenne szükség. A csatolt listák használatának másik előnye a veremek megvalósításához, hogy a push és pop műveletek csak egyszerű mutatómódosítást igényelnek. Ennek az a hátránya, hogy a hivatkozott lista minden eleméhez tartozik egy további mutató, ami több memóriát fogyaszt a tömbökhöz képest.

Amint arról már beszéltünk a „Python linkelt listák” cikkben, az első dolog, amit a tényleges linkelt lista előtt megvalósítanunk kell, egy osztály egyetlen csomóponthoz:

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

Tekintse meg gyakorlatias, gyakorlati útmutatónkat a Git tanulásához, amely tartalmazza a bevált gyakorlatokat, az iparág által elfogadott szabványokat és a mellékelt csalólapot. Hagyd abba a guglizást a Git parancsokkal, és valójában tanulni meg!

Ez a megvalósítás csak két adatpontot tárol – a csomópontban tárolt értéket (data) és a hivatkozás a következő csomópontra (next).

3 részes sorozatunk a Python linkelt listáiról:

Most ráugorhatunk magára a veremosztályra. A kivitelező kicsit más lesz, mint az előző. Csak egy változót fog tartalmazni – a verem tetején lévő csomópontra való hivatkozást:

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

Ahogy az várható volt, a push() metódus egy új elemet (ebben az esetben csomópontot) ad hozzá a verem tetejéhez:

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

A pop() metódus ellenőrzi, hogy vannak-e elemek a veremben, és eltávolítja a legfelsőt, ha a verem nem üres:

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

Végül a peek() A metódus egyszerűen kiolvassa az elem értékét a verem tetejéről (ha van ilyen):

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

Jegyzet: Mindkettő interfésze Stack osztályok ugyanaz – az egyetlen különbség az osztály metódusainak belső megvalósítása. Ez azt jelenti, hogy könnyedén válthat a különböző megvalósítások között, anélkül, hogy aggódnia kellene az osztályok belső elemei miatt.

A tömbök és a csatolt listák közötti választás az alkalmazás speciális követelményeitől és korlátaitól függ.

Verem megvalósítása a Python beépített struktúráival

Sok fejlesztő számára a verem felépítése a semmiből, bár oktatási célú, nem biztos, hogy a leghatékonyabb módja a verem használatának valós alkalmazásokban. Szerencsére sok népszerű programozási nyelv beépített adatstruktúrákkal és osztályokkal rendelkezik, amelyek természetesen támogatják a veremműveleteket. Ebben a részben megvizsgáljuk a Python e tekintetben kínált ajánlatait.

A Python sokoldalú és dinamikus nyelvként nem rendelkezik külön veremosztályokkal. Azonban a beépített adatstruktúrák, különösen a listák és a deque osztály a collections modul, könnyedén stackként szolgálhat.

Python listák használata veremként

A Python listák dinamikus természetüknek és az olyan módszereknek köszönhetően, mint pl append() és a pop().

  • Push művelet – Egy elem hozzáadása a verem tetejéhez olyan egyszerű, mint a append() eljárás:

    stack = []
    stack.append('A')
    stack.append('B')
    
  • Pop művelet – A legfelső elem eltávolítása a pop() módszer minden érv nélkül:

    top_element = stack.pop() 
  • Peek művelet A tetejére ugrás nélküli hozzáférést negatív indexeléssel lehet elérni:

    top_element = stack[-1] 

<p></p> miről Osztály tól gyűjtemény Modulok

A deque (a double-ended queue rövidítése) osztály egy másik sokoldalú eszköz a verem-megvalósításokhoz. Mindkét végéről gyors hozzáfűzésekre és popupokra optimalizálták, így kissé hatékonyabb a veremműveletekhez, mint a listákhoz.

  • Inicializálás:

    from collections import deque
    stack = deque()
    
  • Push művelet - Hasonlóan a listákhoz, append() módszert használják:

    stack.append('A')
    stack.append('B')
    
  • Pop művelet - Mint a listák, pop() módszer elvégzi a feladatot:

    top_element = stack.pop() 
  • Peek művelet – A megközelítés ugyanaz, mint a listáknál:

    top_element = stack[-1] 

Mikor melyiket használjuk?

Míg a listák és a deque-ek egyaránt használhatók veremként, ha a struktúrát elsősorban veremként használja (egyik végéről hozzáfűzések és felugró elemek), a deque optimalizálása miatt valamivel gyorsabb lehet. A legtöbb gyakorlati célra azonban, hacsak nem teljesítménykritikus alkalmazásokról van szó, a Python listái elegendőek.

Jegyzet: Ez a rész a Python beépített ajánlataival foglalkozik a veremszerű viselkedés érdekében. Nem feltétlenül kell újra feltalálnia a kereket (a stack a semmiből való megvalósításával), ha ilyen hatékony eszközök vannak a keze ügyében.

Potenciális halmozással kapcsolatos problémák és megoldásuk

Bár a veremek hihetetlenül sokoldalúak és hatékonyak, mint bármely más adatstruktúra, nem mentesek a potenciális buktatóktól. Alapvető fontosságú, hogy felismerjük ezeket a kihívásokat, amikor veremekkel dolgozik, és stratégiákat dolgozunk ki a megoldásukra. Ebben a részben belemerülünk néhány gyakori veremmel kapcsolatos problémába, és megvizsgáljuk a megoldásuk módjait.

kötegtúlcsordulást

Ez akkor fordul elő, amikor egy elemet megpróbálnak egy olyan verembe tolni, amely elérte a maximális kapacitását. Ez különösen olyan környezetekben jelent problémát, ahol a verem mérete rögzített, például bizonyos alacsony szintű programozási forgatókönyveknél vagy rekurzív függvényhívásoknál.

Ha tömb alapú veremeket használ, fontolja meg a dinamikus tömbök vagy a linkelt listák megvalósítására való váltást, amelyek átméretezik magukat. A verem túlcsordulás megelőzésének másik lépése a verem méretének folyamatos figyelése, különösen a leküldési műveletek előtt, és egyértelmű hibaüzenetek vagy figyelmeztetések a verem túlcsordulása esetén.

Ha a verem túlcsordulása a túlzott rekurzív hívások miatt következik be, fontolja meg az iteratív megoldásokat, vagy növelje a rekurziós korlátot, ha a környezet lehetővé teszi.

Stack Underflow

Ez akkor fordul elő, amikor egy üres veremből próbálnak kidobni egy elemet. Ennek elkerülése érdekében mindig ellenőrizze, hogy a verem üres-e, mielőtt pop- vagy peek-műveleteket hajtana végre. Egyértelmű hibaüzenet küldése, vagy kecsesen kezelje az alulcsordulást a program összeomlása nélkül.

Olyan környezetben, ahol ez elfogadható, fontolja meg egy speciális érték visszaadását, amikor üres veremből ugrik ki, jelezve a művelet érvénytelenségét.

Memória korlátok

Memóriakorlátos környezetben még a veremek dinamikus átméretezése is (például a csatolt listákon alapulóak) a memória kimerüléséhez vezethet, ha túl nagyra nőnek. Ezért tartsa szemmel az alkalmazás általános memóriahasználatát és a verem növekedését. Esetleg vezessen be egy puha sapkát a köteg méretére.

Szálbiztonsági aggályok

Többszálú környezetekben a megosztott verem különböző szálak általi egyidejű műveletei adatok inkonzisztenciájához vagy váratlan viselkedéshez vezethetnek. A probléma lehetséges megoldásai a következők lehetnek:

  • Mutexek és zárak – Használjon mutexet (kölcsönös kizárási objektumokat) vagy zárakat annak biztosítására, hogy egy adott időpontban csak egy szál végezhessen műveleteket a veremen.
  • Atomműveletek – Használja ki az atomi műveleteket, ha a környezet támogatja, hogy biztosítsa az adatok konzisztenciáját a push és pop műveletek során.
  • Thread-local Stacks – Olyan esetekben, amikor minden szálnak szüksége van a saját veremére, fontolja meg a szálhelyi tároló használatát, hogy minden szálnak külön verempéldányt adjon.

Noha a veremek valóban nagy teljesítményűek, a lehetséges problémák tudatában és a megoldások aktív megvalósításában robusztus és hibamentes alkalmazások garantálhatók. Ezeknek a buktatóknak a felismerése a csata fele – a másik fele pedig a legjobb gyakorlatok átvétele a hatékony megoldásuk érdekében.

Következtetés

A veremek, látszólag egyszerű természetük ellenére, számos alapvető művelet alapját képezik a számítástechnika világában. Az összetett matematikai kifejezések elemzésétől a függvényhívások kezeléséig hasznosságuk széles és elengedhetetlen. Ahogy végigjártuk ennek az adatszerkezetnek a csínját-bínját, egyértelmű, hogy az erőssége nemcsak a hatékonyságában, hanem a sokoldalúságában is rejlik.

Azonban, mint minden eszköz esetében, a hatékonysága a használat módjától függ. Csak győződjön meg arról, hogy alaposan ismeri annak alapelveit, lehetséges buktatóit és bevált gyakorlatait, hogy ki tudja használni a veremek valódi erejét. Akár a semmiből valósítja meg az egyiket, akár a Pythonhoz hasonló nyelvek beépített szolgáltatásait használja, ezeknek az adatstruktúráknak a tudatos alkalmazása fogja kiemelni a megoldásait.

Időbélyeg:

Még több Stackabus