Разумевање имплементације стека у Питхон-у

U svetu programiranja, strukture podataka imaju ključnu ulogu. One omogućavaju efikasno organizovanje informacija, čineći ih lakšim za korišćenje. Stek je jedna od najjednostavnijih i najvažnijih struktura.

Hajde da detaljnije istražimo stek i vidimo kako se implementira u programskom jeziku Python.

Šta je stek?

Stek se može uporediti sa gomilom knjiga, stolica ili bilo kojih drugih predmeta koji su naslagani jedan na drugi u stvarnom životu. Karakteriše ga princip „poslednji ušao, prvi izašao“ (LIFO – Last In, First Out). Ovo je zaista jedna od najosnovnijih struktura podataka. Razmotrimo jedan primer iz stvarnog života:

Zamislite da imamo gomilu knjiga, postavljenu jednu na drugu.

Ako želimo da uzmemo treću knjigu odozgo, moraćemo prvo ukloniti prve dve knjige. U ovom slučaju, knjiga koja je poslednja stavljena na gomilu, prva će biti uzeta sa gomile. Stek kao struktura podataka u programiranju prati isti princip – LIFO.

Operacije sa stekom

U osnovi, postoje dve glavne operacije koje se mogu izvršavati nad stekom:

1. push(podatak)

Ova operacija dodaje, tj. „gura“ novi podatak na vrh steka.

2. pop()

Ova operacija uklanja, tj. „izbacuje“ podatak koji se nalazi na vrhu steka.

Na slici ispod možete videti ilustracije push i pop operacija.

Napisaćemo i nekoliko pomoćnih funkcija koje će nam pružiti dodatne informacije o stanju steka.

Evo o kojim pomoćnim funkcijama je reč:

peek()

Ova funkcija vraća podatak koji se nalazi na samom vrhu steka, bez njegovog uklanjanja.

isEmpty()

Ova funkcija proverava da li je stek prazan i vraća odgovarajući rezultat (true ili false).

Sada kada smo razjasnili konceptualne aspekte steka, pređimo na njegovu implementaciju, bez daljeg odlaganja.

Pretpostavljam da imate instaliran Python na vašem računaru. Ako to nije slučaj, možete koristiti i neki online kompajler.

Implementacija steka

Implementacija steka je, u poređenju sa drugim strukturama podataka, relativno jednostavna. U Pythonu postoji nekoliko načina za implementaciju steka.

Razmotrićemo ih sve, jedan po jedan.

#1. Lista

Stek ćemo implementirati koristeći listu unutar klase. Hajde da vidimo korak po korak implementaciju steka:

Korak 1: Kreirajte klasu pod nazivom „Stack“.

class Stack:
    pass

Korak 2: Potrebno je da podatke skladištimo u listi. Dodajmo praznu listu unutar klase „Stack“, pod imenom „elements“.

class Stack:
    def __init__(self):
        self.elements = []

Korak 3: Da bismo mogli da dodajemo elemente na stek, potreban nam je metod. Napravimo metod „push“ koji prima podatak kao argument i dodaje ga na listu „elements“.

class Stack:
    def __init__(self):
        self.elements = []

    def push(self, data):
        self.elements.append(data)
        return data

Korak 4: Slično, hajde da napišemo „pop“ metod, koji uklanja element sa vrha steka. Možemo koristiti ugrađeni „pop“ metod za tip podatka lista.

class Stack:
    ## ...
    def pop(self):
        return self.elements.pop()

Završili smo osnovnu implementaciju steka sa neophodnim operacijama. Sada ćemo dodati i pomoćne funkcije kako bismo dobili više informacija o steku.

Korak 5: Možemo dobiti element sa vrha steka korišćenjem negativnog indeksa. Element koda [-1] vraća poslednji element liste, što je, u našem slučaju, element na vrhu steka.

class Stack:
    ## ...

    def peek(self):
        return self.elements[-1]

Korak 6: Ako je dužina liste „elements“ jednaka 0, to znači da je stek prazan. Napravimo metod koji vraća informaciju o tome da li je stek prazan ili ne.

class Stack:
    ## ...
    def is_empty(self):
        return len(self.elements) == 0

Završili smo implementaciju steka koristeći tip podatka „lista“.

Čekajte malo! Upravo smo implementirali stek, ali kako ga koristiti? Kako da ga testiramo?

Da vidimo kako se to radi. Prvo moramo kreirati objekat klase „Stack“ da bismo ga mogli koristiti. Nije ništa komplikovano, uradimo to odmah.

class Stack: 
    ## ...

if __name__ == '__main__':
    stack = Stack()

Napravili smo objekat steka i sada smo spremni da ga koristimo. Pratimo sledeće korake da testiramo operacije steka:

  • Proverite da li je stek prazan ili ne, pomoću metode „is_empty“. Rezultat bi trebalo da bude „True“.
  • Dodajte brojeve 1, 2, 3, 4 i 5 na stek pomoću metode „push“.
  • Metoda „is_empty“ bi sada trebalo da vrati „False“. Proverite.
  • Ispišite element sa vrha steka koristeći metodu „peek“.
  • Izbacite element sa vrha steka pomoću metode „pop“.
  • Ponovo proverite element sa vrha steka pomoću metode „peek“. Rezultat bi trebalo da bude 4.
  • Sada izbacite sve elemente sa steka.
  • Metoda „is_empty“ bi trebalo da vrati „True“. Proverite.

Ako su svi navedeni koraci uspešno izvršeni, to znači da smo uspešno implementirali stek. Pokušajte da sami napišete kod za ove korake.

Da li ste napisali kod? Ako niste, nema problema, pogledajte kod ispod:

class Stack:
    def __init__(self):
        self.elements = []

    def push(self, data):
        self.elements.append(data)
        return data

    def pop(self):
        return self.elements.pop()

    def peek(self):
        return self.elements[-1]

    def is_empty(self):
        return len(self.elements) == 0

if __name__ == '__main__':
    stack = Stack()

    ## checking is_empty method -> true
    print(stack.is_empty())

    ## pushing the elements
    stack.push(1)
    stack.push(2)
    stack.push(3)
    stack.push(4)
    stack.push(5)

    ## again checking is_empty method -> false
    print(stack.is_empty())

    ## printing the topmost element of the stack -> 5
    print(stack.peek())

    ## popping the topmost element -> 5
    stack.pop()

    ## checking the topmost element using peek method -> 4
    print(stack.peek())

    ## popping all the elements
    stack.pop()
    stack.pop()
    stack.pop()
    stack.pop()

    ## checking the is_empty method for the last time -> true
    print(stack.is_empty())

Uspeli smo! Implementirali smo stek od nule, koristeći tip podatka „lista“. Ukoliko pokrenete gornji kod, dobićete izlaz kao što je navedeno u nastavku:

True
False
5
4
True

Možemo direktno koristiti listu kao stek. Međutim, ova implementacija je dobra za razumevanje kako se stek implementira i u drugim programskim jezicima.

Takođe, možete pogledati i ove članke koji se odnose na listu.

Hajde da sada vidimo ugrađeni „deque“ iz modula „collections“, koji takođe može poslužiti kao stek.

#2. deque iz „collections“

„Deque“ je implementiran kao dvostrani red. Budući da podržava dodavanje i uklanjanje elemenata sa oba kraja, može se koristiti kao stek. Možemo ga podesiti da poštuje LIFO princip steka.

Implementiran je korišćenjem druge strukture podataka koja se zove dvostruko povezana lista. Zato su performanse umetanja i brisanja elemenata konzistentne. Pristup elementima sa sredine dvostruko povezane liste traje O(n) vremena. Međutim, možemo ga koristiti kao stek, jer nema potrebe za pristupom srednjim elementima steka.

Pre same implementacije steka, pogledajmo metode koje se koriste za implementaciju steka pomoću reda:

  • append(podatak) – koristi se za dodavanje podatka na vrh steka
  • pop() – koristi se za uklanjanje elementa sa vrha steka

Ne postoje direktne zamene za metode „peek“ i „is_empty“. Umesto „peek“, možemo ispisati ceo stek. Za proveru da li je stek prazan, možemo koristiti „len“ metod.

Implementirajmo stek koristeći „deque“ iz modula „collections“.

from collections import deque

## creating deque object
stack = deque()

## checking whether stack is empty or not -> true
print(len(stack) == 0)

## pushing the elements
stack.append(1)
stack.append(2)
stack.append(3)
stack.append(4)
stack.append(5)

## again checking whether stack is empty or not -> false
print(len(stack) == 0)

## printing the stack
print(stack)

## popping the topmost element -> 5
stack.pop()

## printing the stack
print(stack)

## popping all the elements
stack.pop()
stack.pop()
stack.pop()
stack.pop()

## checking the whether stack is empty or not for the last time -> true
print(len(stack) == 0)

To je to! Naučili smo kako da implementiramo stek koristeći „deque“ iz modula „collections“. Ukoliko pokrenete gornji program, dobićete izlaz kao što je navedeno ispod:

True
False
deque([1, 2, 3, 4, 5])
deque([1, 2, 3, 4])
True

Do sada smo videli dva načina za implementaciju steka. Postoje li i drugi načini? Da! Hajde da vidimo još jedan, poslednji način za implementaciju steka u ovom vodiču.

#3. LifoQueue

Sam naziv „LifoQueue“ govori da ova klasa sledi LIFO princip. Zbog toga je možemo bez problema koristiti kao stek. Ona dolazi iz modula „queue“. „LifoQueue“ pruža nekoliko korisnih metoda koje su od pomoći prilikom implementacije steka. Da vidimo koje su to metode:

  • put(podatak) – dodaje, tj. „gura“ podatak u red
  • get() – uklanja, tj. „izbacuje“ element sa vrha reda
  • empty() – vraća informaciju da li je red prazan ili ne
  • qsize() – vraća dužinu reda

Implementirajmo stek koristeći „LifoQueue“ iz modula „queue“.

from queue import LifoQueue

## creating LifoQueue object
stack = LifoQueue()

## checking whether stack is empty or not -> true
print(stack.empty())

## pushing the elements
stack.put(1)
stack.put(2)
stack.put(3)
stack.put(4)
stack.put(5)

## again checking whether stack is empty or not -> false
print(stack.empty())

## popping all the elements
print(stack.get())
print(stack.get())
print(stack.get())

## checking the stack size
print("Size", stack.qsize())

print(stack.get())
print(stack.get())

## checking the whether stack is empty or not for the last time -> true
print(stack.empty())

Ukoliko pokrenete gornji program bez izmena, dobićete sledeći izlaz:

True
False
5
4
3
Size 2
2
1
True

Primena steka

Sada kada posedujete dovoljno znanja o steku, možete ga primeniti i u rešavanju programerskih problema. Razmotrimo jedan problem i rešimo ga pomoću steka.

Zadat je izraz. Potrebno je napisati program koji proverava da li su zagrade, uglaste zagrade i vitičaste zagrade pravilno izbalansirane ili ne.

Razmotrimo neke primere:

Ulaz: „[{}]([])“

Izlaz: Izbalansirano

Ulaz: „{[}]([])“

Izlaz: Nije izbalansirano

Za rešavanje ovog problema možemo koristiti stek. Pogledajmo korake koje treba da pratimo:

  • Kreirajte stek za čuvanje znakova.
  • Ako je dužina izraza neparna, izraz nije izbalansiran.
  • Prođite kroz dati izraz.
    • Ako je trenutni znak početna zagrada (ili { ili [, dodajte ga na stek.
    • U suprotnom, ako je trenutni znak zatvorena zagrada ) ili } ili ], izbacite element sa steka.
    • Ako se izbačeni znak poklapa sa početnom zagradom, nastavite dalje. U suprotnom, zagrade nisu izbalansirane.
  • Nakon iteracije, ako je stek prazan, jednačina je izbalansirana. U suprotnom, jednačina nije izbalansirana.

Možemo koristiti tip podatka „set“ za proveru da li se zagrade poklapaju.

## stack
class Stack:
    def __init__(self):
        self.elements = []

    def push(self, data):
        self.elements.append(data)
        return data

    def pop(self):
        return self.elements.pop()

    def peek(self):
        return self.elements[-1]

    def is_empty(self):
        return len(self.elements) == 0

def balance_check(expression):
    ## checking the length of the expression
    if len(expression) % 2 != 0:
        ## not balanced if the length is odd
        return False

    ## for checking
    opening_brackets = set('([{')
    pairs = set([ ('(',')'), ('[',']'), ('{','}') ])

    ## stack initialization
    stack = Stack()

    ## iterating through the given expression
    for bracket in expression:

        ## checking whether the current bracket is opened or not
        if bracket in opening_brackets:
            ## adding to the stack
            stack.push(bracket)
        else:
            ## popping out the last bracket from the stack
            popped_bracket = stack.pop()

            ## checking whether popped and current bracket pair
            if (popped_bracket, bracket) not in pairs:
                return False

    return stack.is_empty()

if __name__ == '__main__':
    if balance_check('[{}]([])'):
        print("Balanced")
    else:
        print("Not Balanced")

    if balance_check('{[}]([])'):
        print("Balanced")
    else:
        print("Not Balanced")

Stek se može koristiti za rešavanje mnogih drugih problema. Gore navedeni problem je samo jedan od primera. Pokušajte da primenite koncept steka kad god mislite da je to najbolje rešenje.

Zaključak

Ura! Stigli ste do kraja ovog vodiča. Nadam se da ste uživali u ovom vodiču koliko sam i ja uživao dok sam ga pravio. To je sve za ovaj tutorijal.

Srećno kodiranje 🙂 👨‍💻