Zephyrnet-logotyp

Guide till stackar i Python

Datum:

Beskrivning

Medan vissa datastrukturer är mångsidiga och kan användas i ett brett spektrum av applikationer, är andra specialiserade och designade för att hantera specifika problem. En sådan specialiserad struktur, känd för sin enkelhet men ändå anmärkningsvärda användbarhet, är stapel.

Så, vad är en stack? I dess kärna är en stack en linjär datastruktur som följer LIFO (Last In First Out)-principen. Tänk på det som en bunt tallrikar i en cafeteria; du tar bara plåten som ligger ovanpå, och när du lägger en ny plåt går den till toppen av högen.

Det sista elementet som läggs till är det första elementet som ska tas bort

LIFO-principen

Men varför är förståelse för stacken avgörande? Genom åren har stackar hittat sina applikationer inom en uppsjö av områden, från minneshantering i dina favoritprogrammeringsspråk till bakåtknappsfunktionaliteten i din webbläsare. Denna inneboende enkelhet, i kombination med dess enorma användbarhet, gör stacken till ett oumbärligt verktyg i en utvecklares arsenal.

I den här guiden kommer vi att djupdyka i koncepten bakom stackar, deras implementering, användningsfall och mycket mer. Vi kommer att definiera vad stackar är, hur de fungerar, och sedan tar vi en titt på två av de vanligaste sätten att implementera stackdatastruktur i Python.

Grundläggande begrepp för en stackdatastruktur

I grunden är en stack bedrägligt enkel, men den har nyanser som ger den mångsidiga tillämpningar inom beräkningsdomänen. Innan vi går in i dess implementeringar och praktiska användningsområden, låt oss säkerställa en stensäker förståelse av kärnkoncepten kring stackar.

LIFO-principen (Last In First Out).

LIFO är den vägledande principen bakom en stack. Det innebär att det sista föremålet som kommer in i stapeln är det första som lämnar. Denna egenskap skiljer stackar från andra linjära datastrukturer, såsom köer.

Notera: Ett annat användbart exempel som hjälper dig att slingra dig runt konceptet om hur stackar fungerar är att föreställa sig människor som går in och ut ur en hiss - den sista personen som går in i en hiss är den första som kommer ut!

Grundläggande operationer

Varje datastruktur definieras av de operationer den stödjer. För stackar är dessa operationer enkla men viktiga:

  • Tryck – Lägger till ett element till toppen av stapeln. Om stacken är full kan den här åtgärden resultera i ett stackspill.

LIFO push-operation

  • Pop – Tar bort och returnerar det översta elementet i stapeln. Om stapeln är tom, kan ett försök orsaka ett underflöde.

LIFO pop operation

  • Peek (eller Top) – Observerar det översta elementet utan att ta bort det. Denna operation är användbar när du vill inspektera det aktuella toppelementet utan att ändra stackens tillstånd.

Vid det här laget borde betydelsen av stackdatastrukturen och dess grundläggande koncept vara uppenbar. När vi går framåt kommer vi att dyka ner i dess implementeringar och belysa hur dessa grundläggande principer översätts till praktisk kod.

Hur man implementerar en stack från grunden i Python

Efter att ha förstått de grundläggande principerna bakom stackarna är det dags att kavla upp ärmarna och fördjupa sig i det praktiska. Att implementera en stack, även om det är enkelt, kan närma sig på flera sätt. I det här avsnittet kommer vi att utforska två primära metoder för att implementera en stack – med hjälp av arrayer och länkade listor.

Implementera en stack med hjälp av matriser

Arrayer, vara sammanhängande minnesplatser, erbjuder ett intuitivt sätt att representera stackar. De tillåter O(1) tidskomplexitet för att komma åt element genom index, vilket säkerställer snabba push-, pop- och kikoperationer. Dessutom kan arrayer vara mer minneseffektiva eftersom det inte finns någon overhead av pekare som i länkade listor.

Å andra sidan har traditionella arrayer en fast storlek, vilket betyder att när de väl har initierats kan de inte ändras i storlek. Detta kan leda till en stack overflow om den inte övervakas. Detta kan övervinnas av dynamiska arrayer (som Pythons list), som kan ändra storlek, men den här operationen är ganska kostsam.

Med allt det ur vägen, låt oss börja implementera vår stackklass med hjälp av arrayer i Python. Först av allt, låt oss skapa en klass själv, med konstruktorn som tar storleken på stacken som en parameter:

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

Som du kan se lagrade vi tre värden i vår klass. De size är den önskade storleken på stapeln, den stack är den faktiska matrisen som används för att representera stackdatastrukturen, och top är indexet för det sista elementet i stack array (överst i stacken).

Från och med nu kommer vi att skapa och förklara en metod för var och en av de grundläggande stackoperationerna. Var och en av dessa metoder kommer att finnas i Stack klass vi just har skapat.

Låt oss börja med push() metod. Som tidigare diskuterats lägger push-operationen till ett element till toppen av stapeln. Först och främst ska vi kontrollera om stacken har något utrymme kvar för elementet vi vill lägga till. Om stacken är full höjer vi Stack Overflow undantag. Annars lägger vi bara till elementet och justerar top och stack följaktligen:

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

Nu kan vi definiera metoden för att ta bort ett element från toppen av stacken - pop() metod. Innan vi ens försöker ta bort ett element, måste vi kontrollera om det finns några element i stacken eftersom det inte är någon mening med att försöka ta bort ett element från en tom stack:

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

Slutligen kan vi definiera peek() metod som bara returnerar värdet på elementet som för närvarande är överst i stacken:

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

Och det är allt! Vi har nu en klass som implementerar beteendet hos stackar med hjälp av listor i Python.

Implementera en stack med hjälp av länkade listor

Länkade listor, vara dynamiska datastrukturer, kan lätt växa och krympa, vilket kan vara fördelaktigt för att implementera stackar. Eftersom länkade listor allokerar minne efter behov, kan stacken dynamiskt växa och minska utan behov av explicit storleksändring. En annan fördel med att använda länkade listor för att implementera stackar är att push- och popoperationer bara kräver enkla pekareändringar. Nackdelen med det är att varje element i den länkade listan har en extra pekare, vilket förbrukar mer minne jämfört med arrayer.

Som vi redan diskuterat i "Python länkade listor" artikel, det första vi behöver implementera innan den faktiska länkade listan är en klass för en enda nod:

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

Kolla in vår praktiska, praktiska guide för att lära dig Git, med bästa praxis, branschaccepterade standarder och medföljande fuskblad. Sluta googla Git-kommandon och faktiskt lära Det!

Denna implementering lagrar endast två datapunkter – värdet som lagras i noden (data) och referensen till nästa nod (next).

Vår 3-delade serie om länkade listor i Python:

Nu kan vi hoppa in på själva stackklassen. Konstruktören kommer att vara lite annorlunda än den tidigare. Den kommer bara att innehålla en variabel – referensen till noden på toppen av stacken:

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

Som förväntat push() metod lägger till ett nytt element (nod i detta fall) till toppen av stacken:

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

Smakämnen pop() metoden kontrollerar om det finns några element i stacken och tar bort den översta om stacken inte är tom:

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

Slutligen, peek() metoden läser helt enkelt värdet på elementet från toppen av stacken (om det finns en):

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

Notera: Gränssnittet för båda Stack klasserna är desamma – den enda skillnaden är den interna implementeringen av klassmetoderna. Det betyder att du enkelt kan växla mellan olika implementeringar utan att behöva oroa dig för klassernas inre delar.

Valet mellan arrayer och länkade listor beror på applikationens specifika krav och begränsningar.

Hur man implementerar en stack med Pythons inbyggda strukturer

För många utvecklare är det kanske inte det mest effektiva sättet att använda en stack i verkliga applikationer att bygga en stack från grunden, även om det är pedagogiskt. Lyckligtvis är många populära programmeringsspråk utrustade med inbyggda datastrukturer och klasser som naturligtvis stöder stackoperationer. I det här avsnittet kommer vi att utforska Pythons erbjudanden i detta avseende.

Python, som är ett mångsidigt och dynamiskt språk, har inte en dedikerad stackklass. Men dess inbyggda datastrukturer, särskilt listor och deque-klassen från collections modul, kan utan ansträngning fungera som staplar.

Använda Python-listor som stackar

Python-listor kan emulera en stack ganska effektivt på grund av deras dynamiska natur och närvaron av metoder som append() och pop().

  • Push Operation – Att lägga till ett element till toppen av stapeln är lika enkelt som att använda append() metod:

    stack = []
    stack.append('A')
    stack.append('B')
    
  • Pop Operation – Att ta bort det översta elementet kan uppnås med hjälp av pop() metod utan argument:

    top_element = stack.pop() 
  • Peek Operation Att komma åt toppen utan att poppa kan göras med negativ indexering:

    top_element = stack[-1] 

Använda om vad Klass från samlingar Modulerna

Smakämnen deque (kort för dubbelkö) klass är ett annat mångsidigt verktyg för stackimplementeringar. Den är optimerad för snabba appends och pops från båda ändar, vilket gör den något mer effektiv för stackoperationer än listor.

  • Initieringen:

    from collections import deque
    stack = deque()
    
  • Push Operation – I likhet med listor, append() metod används:

    stack.append('A')
    stack.append('B')
    
  • Pop Operation – Gilla listor, pop() metoden gör jobbet:

    top_element = stack.pop() 
  • Peek Operation – Tillvägagångssättet är detsamma som med listor:

    top_element = stack[-1] 

När ska man använda vilken?

Även om både listor och deques kan användas som stackar, om du i första hand använder strukturen som en stack (med appends och pops från ena änden), deque kan vara något snabbare på grund av dess optimering. Men för de flesta praktiska ändamål och såvida det inte handlar om prestandakritiska applikationer bör Pythons listor räcka.

Notera: Det här avsnittet dyker in i Pythons inbyggda erbjudanden för stackliknande beteende. Du behöver inte nödvändigtvis uppfinna hjulet på nytt (genom att implementera stack från grunden) när du har så kraftfulla verktyg till hands.

Potentiella stackrelaterade problem och hur man övervinner dem

Även om stackar är otroligt mångsidiga och effektiva, som alla andra datastrukturer, är de inte immuna mot potentiella fallgropar. Det är viktigt att känna igen dessa utmaningar när du arbetar med stackar och att ha strategier på plats för att hantera dem. I det här avsnittet kommer vi att dyka in i några vanliga stack-relaterade problem och utforska sätt att övervinna dem.

stack Overflow

Detta inträffar när ett försök görs att trycka ett element på en stack som har nått sin maximala kapacitet. Det är särskilt ett problem i miljöer där stackstorleken är fixerad, som i vissa programmeringsscenarier på låg nivå eller rekursiva funktionsanrop.

Om du använder arraybaserade stackar kan du överväga att byta till dynamiska arrayer eller implementeringar med länkade listor, som ändrar storleken på sig själva. Ett annat steg för att förhindra stackspill är att kontinuerligt övervaka stackens storlek, särskilt före push-operationer, och ge tydliga felmeddelanden eller uppmaningar om stackspill.

Om stackoverflow inträffar på grund av överdrivna rekursiva anrop, överväg iterativa lösningar eller höj rekursionsgränsen om miljön tillåter.

Stack Underflow

Detta händer när det görs ett försök att poppa ett element från en tom stack. För att förhindra att detta händer, kontrollera alltid om stacken är tom innan du utför pop- eller kikoperationer. Skicka ett tydligt felmeddelande eller hantera underflödet graciöst utan att krascha programmet.

I miljöer där det är acceptabelt, överväg att returnera ett speciellt värde när du hoppar från en tom stack för att indikera operationens ogiltighet.

Minnesbegränsningar

I minnesbegränsade miljöer kan till och med dynamisk storleksändring av stackar (som de som baseras på länkade listor) leda till minnesutmattning om de blir för stora. Håll därför ett öga på den övergripande minnesanvändningen för applikationen och stackens tillväxt. Kanske införa en mjuk keps på stapelns storlek.

Tråda säkerhetsproblem

I flertrådiga miljöer kan samtidiga operationer på en delad stack av olika trådar leda till datainkonsekvenser eller oväntade beteenden. Potentiella lösningar på detta problem kan vara:

  • Mutexes och lås – Använd mutexes (ömsesidiga uteslutningsobjekt) eller lås för att säkerställa att endast en tråd kan utföra operationer på stacken vid en given tidpunkt.
  • Atomverksamhet – Utnyttja atomära operationer, om de stöds av miljön, för att säkerställa datakonsistens under push- och pop-operationer.
  • Trådlokala staplar – I scenarier där varje tråd behöver sin stack, överväg att använda trådlokal lagring för att ge varje tråd sin separata stackinstans.

Även om stackar verkligen är kraftfulla, kommer att vara medveten om deras potentiella problem och att aktivt implementera lösningar säkerställa robusta och felfria applikationer. Att inse dessa fallgropar är halva kampen – den andra hälften är att anta bästa praxis för att ta itu med dem effektivt.

Slutsats

Stackar, trots sin till synes enkla natur, stöder många grundläggande operationer i datorvärlden. Från att analysera komplexa matematiska uttryck till att hantera funktionsanrop, deras användbarhet är bred och väsentlig. När vi har gått igenom detaljerna i denna datastruktur är det tydligt att dess styrka inte bara ligger i dess effektivitet utan också i dess mångsidighet.

Men som med alla verktyg beror dess effektivitet på hur det används. Se bara till att du har en grundlig förståelse för dess principer, potentiella fallgropar och bästa praxis för att säkerställa att du kan dra nytta av den verkliga kraften i stackarna. Oavsett om du implementerar en från grunden eller använder inbyggda faciliteter i språk som Python, är det den medvetna tillämpningen av dessa datastrukturer som kommer att skilja dina lösningar åt.

plats_img

Senaste intelligens

plats_img