Pythoni virnade juhend

Pythoni virnade juhend

Sissejuhatus

Kuigi mõned andmestruktuurid on mitmekülgsed ja neid saab kasutada paljudes rakendustes, on teised spetsialiseerunud ja loodud konkreetsete probleemide lahendamiseks. Üks selline spetsialiseeritud struktuur, mis on tuntud oma lihtsuse, kuid siiski märkimisväärse kasulikkuse poolest, on Kestab.

Niisiis, mis on virn? Oma tuumaks on virn lineaarne andmestruktuur, mis järgib LIFO (Last In First Out) põhimõte. Mõelge sellele kui taldrikuvirnale kohvikus; võtad ainult selle taldriku, mis on peal ja uut taldrikut pannes läheb see virna ülaossa.

Viimati lisatud element on esimene element, mis eemaldatakse

LIFO põhimõte

Kuid miks on virna mõistmine ülioluline? Aastate jooksul on virnad leidnud oma rakendusi paljudes valdkondades, alates mäluhaldusest teie lemmikprogrammeerimiskeeltes kuni tagasinupu funktsioonideni teie veebibrauseris. See olemuslik lihtsus koos selle laialdase rakendatavusega muudab virna asendamatuks tööriistaks arendaja arsenalis.

Selles juhendis käsitleme põhjalikult virnade taga olevaid kontseptsioone, nende rakendamist, kasutusjuhtumeid ja palju muud. Määratleme, mis on virnad, kuidas need töötavad, ja seejärel vaatleme kahte kõige levinumat viisi virna andmestruktuuri rakendamiseks Pythonis.

Virna andmestruktuuri põhikontseptsioonid

Oma olemuselt on virn petlikult lihtne, kuid sellel on nüansse, mis annavad sellele arvutusvaldkonnas mitmekülgsed rakendused. Enne selle rakendustesse ja praktilistesse kasutusviisidesse sukeldumist veendugem, et mõistame virnasid ümbritsevaid põhikontseptsioone.

LIFO (Last In First Out) põhimõte

LIFO on virna juhtpõhimõte. See tähendab, et viimane üksus, mis virna siseneb, väljub esimesena. See omadus eristab virnad teistest lineaarsetest andmestruktuuridest, näiteks järjekordadest.

Märge: Veel üks kasulik näide, mis aitab teil virnade toimimise kontseptsiooni ümber keerutada, on ette kujutada inimesi, kes sisenevad ja väljuvad. lift - viimane, kes lifti siseneb, väljub esimesena!

Põhitoimingud

Iga andmestruktuur on määratletud operatsioonide poolt, mida see toetab. Virnade puhul on need toimingud lihtsad, kuid olulised:

  • Lükkama – lisab elemendi virna ülaossa. Kui virn on täis, võib see toiming põhjustada virna ülevoolu.

LIFO tõukeoperatsioon

  • Pop – Eemaldab ja tagastab virna kõige ülemise elemendi. Kui virn on tühi, võib hüppamise katse põhjustada virna allavoolu.

LIFO pop-operatsioon

  • Piilu (või ülevalt) – Jälgib ülemist elementi seda eemaldamata. See toiming on kasulik, kui soovite kontrollida praegust ülemist elementi ilma virna olekut muutmata.

Nüüdseks peaks pinu andmestruktuuri ja selle põhikontseptsioonide tähtsus olema ilmne. Edasi liikudes sukeldume selle rakendustesse, heidates valgust sellele, kuidas need aluspõhimõtted praktiliseks koodiks muutuvad.

Kuidas Pythonis virna nullist juurutada

Olles mõistnud virnade taga olevaid aluspõhimõtteid, on aeg käärida käised ja süveneda asjade praktilisse külge. Kuigi virna juurutamine on lihtne, saab seda läheneda mitmel viisil. Selles jaotises uurime kahte peamist virna rakendamise meetodit – massiivide ja lingitud loendite kasutamist.

Viru rakendamine massiivide abil

Massiivid, olemine külgnevad mälukohad, pakuvad intuitiivset vahendit virnade esitamiseks. Need võimaldavad O(1)-ajalist keerukust elementidele indeksi kaudu juurde pääsemiseks, tagades kiired tõuke-, pop- ja piilumistoimingud. Samuti võivad massiivid olla mälutõhusamad, kuna seal pole viiteid nagu lingitud loendites.

Teisest küljest on traditsioonilistel massiividel fikseeritud suurus, mis tähendab, et pärast lähtestamist ei saa nende suurust muuta. See võib viia a virna ülevool kui seda ei jälgita. Sellest saab üle dünaamiliste massiividega (nagu Pythoni list), mille suurust saab muuta, kuid see toiming on üsna kulukas.

Kui see kõik on möödas, alustame oma virnaklassi rakendamist Pythonis massiivide abil. Kõigepealt loome ise klassi, mille konstruktor võtab parameetrina virna suuruse:

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

Nagu näete, salvestasime oma klassi kolm väärtust. The size on virna soovitud suurus stack on tegelik massiiv, mida kasutatakse virna andmestruktuuri esindamiseks, ja top on viimase elemendi indeks stack massiiv (virna ülaosa).

Nüüdsest loome ja selgitame iga virna põhitoimingu jaoks ühe meetodi. Kõik need meetodid sisalduvad selles Stack klass, mille me just lõime.

Alustame koos push() meetod. Nagu eelnevalt mainitud, lisab tõukeoperatsioon virna ülaossa elemendi. Kõigepealt kontrollime, kas virnas on lisatava elemendi jaoks ruumi jäänud. Kui virn on täis, tõstame Stack Overflow erand. Vastasel juhul lisame lihtsalt elemendi ja kohandame top ja stack vastavalt:

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

Nüüd saame määratleda meetodi elemendi eemaldamiseks virna ülaosast – the pop() meetod. Enne kui proovime isegi elementi eemaldada, peaksime kontrollima, kas virnas on elemente, sest pole mõtet proovida elementi tühjast virnast välja tõsta:

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

Lõpuks saame määratleda peek() meetod, mis lihtsalt tagastab praegu virna ülaosas oleva elemendi väärtuse:

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

Ja see ongi kõik! Meil on nüüd klass, mis rakendab Pythonis loendeid kasutades virnade käitumist.

Virna rakendamine lingitud loendite abil

Lingitud nimekirjad, olemine dünaamilised andmestruktuurid, võib kergesti kasvada ja kahaneda, mis võib olla kasulik virnade rakendamisel. Kuna lingitud loendid eraldavad mälu vastavalt vajadusele, saab virn dünaamiliselt kasvada ja väheneda, ilma et oleks vaja selget suurust muuta. Veel üks lingitud loendite kasutamise eelis virnade juurutamiseks on see, et push- ja pop-toimingud nõuavad vaid lihtsaid osuti muutmist. Selle negatiivne külg on see, et igal lingitud loendi elemendil on täiendav osuti, mis kulutab massiividega võrreldes rohkem mälu.

Nagu me juba arutasime "Pythoniga seotud loendid" artiklis on esimene asi, mida peame enne tegelikku lingitud loendit rakendama, ühe sõlme klass:

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

Tutvuge meie praktilise ja praktilise Giti õppimise juhendiga, mis sisaldab parimaid tavasid, tööstusharus aktsepteeritud standardeid ja kaasas olevat petulehte. Lõpetage Giti käskude guugeldamine ja tegelikult õppima seda!

See teostus salvestab ainult kaks andmepunkti – sõlme salvestatud väärtuse (data) ja viide järgmisele sõlmele (next).

Meie kolmeosaline sari Pythoni lingitud loendite kohta:

Nüüd saame hüpata tegeliku virnaklassi enda juurde. Konstruktor on eelmisest veidi erinev. See sisaldab ainult ühte muutujat – viidet virna ülaosas olevale sõlmele:

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

Nagu oodatud, on push() meetod lisab uue elemendi (antud juhul sõlme) virna ülaossa:

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

. pop() meetod kontrollib, kas virnas on elemente, ja eemaldab kõige ülemise, kui virn pole tühi:

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

Lõpuks peek() meetod loeb lihtsalt elemendi väärtuse virna ülaosast (kui see on olemas):

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

Märge: Mõlema liides Stack klassid on samad – erinevus on ainult klassimeetodite sisemises teostuses. See tähendab, et saate hõlpsalt erinevate rakenduste vahel vahetada, ilma et peaksite muretsema klasside sisemiste omaduste pärast.

Massiivide ja lingitud loendite valik sõltub rakenduse spetsiifilistest nõuetest ja piirangutest.

Kuidas rakendada virna Pythoni sisseehitatud struktuuride abil

Paljude arendajate jaoks ei pruugi virna loomine nullist, kuigi see on hariv, olla kõige tõhusam viis virna kasutamiseks reaalsetes rakendustes. Õnneks on paljud populaarsed programmeerimiskeeled varustatud sisseehitatud andmestruktuuride ja klassidega, mis loomulikult toetavad virna toiminguid. Selles jaotises uurime Pythoni pakkumisi selles osas.

Pythonil, mis on mitmekülgne ja dünaamiline keel, pole spetsiaalset virnaklassi. Kuid selle sisseehitatud andmestruktuurid, eriti loendid ja deque klassi collections moodul, saab hõlpsasti virnadena toimida.

Pythoni loendite kasutamine virnadena

Pythoni loendid võivad oma dünaamilise olemuse ja selliste meetodite olemasolu tõttu üsna tõhusalt virna jäljendada append() ja pop().

  • Tõukeoperatsioon – Elemendi lisamine virna ülaossa on sama lihtne kui kasutada append() meetod:

    stack = []
    stack.append('A')
    stack.append('B')
    
  • Pop-operatsioon – Kõige ülemise elemendi saab eemaldada kasutades pop() meetod ilma argumentideta:

    top_element = stack.pop() 
  • Peek Operatsioon Ülaosale pääsemiseks ilma hüppamiseta saab kasutada negatiivset indekseerimist:

    top_element = stack[-1] 

Kasutamine millest Klass alates Kollektsioonid moodulid

. deque (lühend sõnadest double-ended queue) klass on veel üks mitmekülgne virnarakenduste tööriist. See on optimeeritud kiireks lisamiseks ja hüppamiseks mõlemast otsast, muutes selle virnatoimingute jaoks pisut tõhusamaks kui loendid.

  • Vormindamine:

    from collections import deque
    stack = deque()
    
  • Tõukeoperatsioon - Sarnaselt loenditele, append() kasutatakse meetodit:

    stack.append('A')
    stack.append('B')
    
  • Pop-operatsioon - nagu loendid, pop() meetod teeb tööd:

    top_element = stack.pop() 
  • Peek Operatsioon – Lähenemisviis on sama, mis loendite puhul:

    top_element = stack[-1] 

Millal millist kasutada?

Kuigi nii loendeid kui ka dequee saab kasutada virnadena, siis kui kasutate struktuuri peamiselt virna (koos ühest otsast lisatud lisade ja hüpikatega), deque võib selle optimeerimise tõttu olla veidi kiirem. Enamikul praktilistel eesmärkidel ja välja arvatud juhul, kui tegemist on jõudluskriitiliste rakendustega, peaks piisama Pythoni loenditest.

Märge: Selles jaotises käsitletakse Pythoni sisseehitatud pakkumisi virnataolise käitumise jaoks. Kui teil on nii võimsad tööriistad teie käeulatuses, ei pea te ilmtingimata jalgratast uuesti leiutama (rakendada virna nullist).

Võimalikud virnaga seotud probleemid ja kuidas neist üle saada

Kuigi virnad on uskumatult mitmekülgsed ja tõhusad, nagu iga teinegi andmestruktuur, ei ole need kaitstud võimalike lõksude eest. Virnadega töötamisel on oluline neid väljakutseid teadvustada ja nende lahendamiseks strateegiaid välja töötada. Selles jaotises käsitleme mõningaid tavalisi virnaga seotud probleeme ja uurime võimalusi nende ületamiseks.

ületäitumine

See juhtub siis, kui elementi üritatakse lükata virnale, mis on saavutanud oma maksimaalse mahutavuse. See on eriti probleem keskkondades, kus pinu suurus on fikseeritud, näiteks teatud madala taseme programmeerimise stsenaariumide või rekursiivsete funktsioonikutsete korral.

Kui kasutate massiivipõhiseid virnasid, kaaluge üleminekut dünaamilistele massiividele või lingitud loendi rakendustele, mis muudavad enda suurust. Teine samm virna ületäitumise ennetamiseks on pinu suuruse pidev jälgimine, eriti enne tõuketoiminguid, ning virna ületäitumise kohta selgete veateadete või viipade esitamine.

Kui pinu ületäitumine toimub liigsete rekursiivsete kõnede tõttu, kaaluge iteratiivseid lahendusi või suurendage rekursioonilimiiti, kui keskkond seda võimaldab.

Stack Underflow

See juhtub siis, kui üritatakse elementi tühjast virnast välja tõsta. Selle vältimiseks kontrollige alati enne pop- või peek-operatsioonide sooritamist, kas virn on tühi. Esitage selge veateade või käsitlege alavoolu graatsiliselt, ilma programmi kokkujooksmiseta.

Keskkondades, kus see on vastuvõetav, kaaluge tühjast virust hüppamisel eriväärtuse tagastamist, mis tähistab toimingu kehtetust.

Mälu piirangud

Piiratud mäluga keskkondades võib isegi virnade suuruse dünaamiline muutmine (nagu need, mis põhinevad lingitud loenditel) põhjustada mälu ammendumist, kui need kasvavad liiga suureks. Seetõttu hoidke silma peal rakenduse üldisel mälukasutusel ja virna kasvul. Võib-olla lisage virna suurusele pehme kork.

Niidi ohutusprobleemid

Mitme lõimega keskkondades võivad erinevate lõimede samaaegsed toimingud jagatud virnaga põhjustada andmete vastuolusid või ootamatut käitumist. Selle probleemi võimalikud lahendused võivad olla järgmised:

  • Muteksid ja lukud – Kasutage mutexe (vastastikuseid välistavaid objekte) või lukke tagamaks, et ainult üks lõim saab virnaga teatud ajahetkel toiminguid teha.
  • Aatomioperatsioonid – Kui keskkond seda toetab, kasutage aatomioperatsioone, et tagada andmete järjepidevus push- ja popoperatsioonide ajal.
  • Lõim-kohalikud virnad – Stsenaariumide korral, kus iga lõime vajab oma pinu, kaaluge lõime kohaliku salvestusruumi kasutamist, et anda igale lõimele eraldi pinu eksemplar.

Kuigi virnad on tõepoolest võimsad, tagab nende võimalike probleemide teadvustamine ja lahenduste aktiivne rakendamine töökindlad ja veatud rakendused. Nende lõksude äratundmine on pool võitu – teine ​​pool on parimate tavade kasutuselevõtt nende tõhusaks lahendamiseks.

Järeldus

Vaatamata näiliselt lihtsale olemusele on virnad paljude põhitoimingute aluseks arvutimaailmas. Alates keerukate matemaatiliste avaldiste sõelumisest kuni funktsioonikutsete haldamiseni on nende kasulikkus lai ja oluline. Kui oleme selle andmestruktuuri läbi ja lõhki rännanud, on selge, et selle tugevus ei seisne mitte ainult selle tõhususes, vaid ka mitmekülgsuses.

Kuid nagu kõigi tööriistade puhul, sõltub selle tõhusus sellest, kuidas seda kasutatakse. Lihtsalt veenduge, et tunneksite põhjalikult selle põhimõtteid, võimalikke lõkse ja parimaid tavasid, et saaksite kasutada virnade tõelist jõudu. Olenemata sellest, kas rakendate seda nullist või kasutate sisseehitatud võimalusi sellistes keeltes nagu Python, teie lahendused eristab nende andmestruktuuride teadlik rakendamine.

Ajatempel:

Veel alates Stackabus