Cos’è uno Stack
Uno stack (pila) è una struttura dati basata sul principio LIFO (Last In, First Out): l’ultimo elemento inserito è il primo a essere rimosso.
Analogia: Una lattina di palline da tennis - puoi solo aggiungere o rimuovere dalla cima.
// Esempio visivo di uno stack// Push: aggiungere elementi// [3] ← top// [2]// [1]
// Pop: rimuovere dalla cima// [2] ← top (dopo pop)// [1]Esempi pratici di utilizzo
- Browser History: il pulsante “indietro” usa uno stack - ogni pagina visitata viene aggiunta allo stack, premendo “indietro” si esegue un pop
- Call Stack: JavaScript usa uno stack per gestire le chiamate di funzioni
- Undo/Redo: editor di testo usano stack per annullare/ripetere operazioni
Terminologia Stack
- Push: aggiungere un elemento alla cima dello stack
- Pop: rimuovere l’elemento dalla cima dello stack
- Top: riferimento all’elemento in cima allo stack
- Peek: visualizzare l’elemento in cima senza rimuoverlo
Implementazioni Stack
Array
Con un array, push e pop sono operazioni native di JavaScript:
// Stack con arrayconst stack = [];stack.push(1); // [1]stack.push(2); // [1, 2]stack.push(3); // [1, 2, 3]stack.pop(); // ritorna 3, array diventa [1, 2]Complessità con array:
- Push (fine): O(1) ✅
- Pop (fine): O(1) ✅
- Unshift (inizio): O(n) ❌ - richiede re-indicizzazione
- Shift (inizio): O(n) ❌ - richiede re-indicizzazione
Importante: Se usi un array per uno stack, usa sempre la fine (push/pop), mai l’inizio (unshift/shift).
Linked List
Implementiamo lo stack con una linked list modificata:
// Stack con linked list// top → [3] → [2] → [1] → null// ↑// Primo elemento (LIFO)| Operazione | Complessità | Note |
|---|---|---|
| Push (inizio) | O(1) | Inserimento in testa |
| Pop (inizio) | O(1) | Rimozione dalla testa |
| Push (fine) | O(1) | Solo se si mantiene il puntatore tail |
| Pop (fine) | O(n) | Serve scorrere tutta la lista |
Implementazione Stack
Classe Node per Stack
Il nodo è identico a quello delle linked lists:
// Classe per creare un singolo nodoclass Node { constructor(value) { // Assegna il valore passato al costruttore this.value = value; // Inizialmente il nodo non punta a nulla this.next = null; }}
// Esempio di utilizzoconst newNode = new Node(11);// Crea un oggetto: { value: 11, next: null }Costruttore dello Stack
Il costruttore crea uno stack con il primo elemento. Usiamo top invece di head, e non abbiamo bisogno di bottom (equivalente di tail).
class Stack { constructor(value) { // Crea il primo nodo con il valore passato const newNode = new Node(value);
// Top punta al primo nodo (cima dello stack) this.top = newNode;
// Lo stack inizia con lunghezza 1 this.length = 1; }}
// Esempio di utilizzoconst myStack = new Stack(7);// Crea uno stack con un nodo di valore 7// top punta a questo nodo, length = 1Differenze rispetto a Linked List:
- Usiamo
topinvece dihead - Non abbiamo
bottom(non servetail) - Il resto è identico
Risultato in memoria:
{ top: { value: 7, next: null }, length: 1}Operazioni Stack
Push - Aggiungere alla cima
Aggiunge un nodo alla cima dello stack. Complessità: O(1)
Logica:
- Crea un nuovo nodo
- Il nuovo nodo punta al nodo attualmente in cima (
newNode.next = top) topviene aggiornato per puntare al nuovo nodo
Edge case: Se lo stack è vuoto, top punta semplicemente al nuovo nodo.
Mostra implementazione
push(value) { // Crea il nuovo nodo const newNode = new Node(value);
// Se lo stack è vuoto, top punta al nuovo nodo if (this.length === 0) { this.top = newNode; } else { // Il nuovo nodo punta al nodo attualmente in cima newNode.next = this.top; // Top viene aggiornato per puntare al nuovo nodo this.top = newNode; }
// Incrementa la lunghezza this.length++; return this; // Ritorna l'intero stack}Nota: Push su uno stack con linked list è identico a unshift su una linked list normale.
Pop - Rimuovere dalla cima
Rimuove il nodo dalla cima dello stack e lo ritorna. Complessità: O(1)
Logica:
- Salva il riferimento al nodo in cima (
temp = top) - Sposta
topal nodo successivo (top = top.next) - Rimuove il collegamento del nodo rimosso (
temp.next = null) - Ritorna il nodo rimosso
Edge case: Se lo stack è vuoto, ritorna undefined.
Mostra implementazione
pop() { // Se lo stack è vuoto, ritorna undefined if (this.length === 0) return undefined;
// Salva il riferimento al nodo in cima let temp = this.top; // Sposta top al nodo successivo this.top = this.top.next; // Rimuove il collegamento del nodo rimosso temp.next = null;
// Decrementa la lunghezza this.length--;
// Ritorna il nodo rimosso return temp;}Nota: Pop su uno stack con linked list è identico a shift su una linked list normale.
Peek - Visualizzare la cima
Ritorna il valore della cima dello stack senza rimuoverlo. Complessità: O(1)
Mostra implementazione
peek() { // Se lo stack è vuoto, ritorna undefined if (this.length === 0) return undefined;
// Ritorna il valore del nodo in cima return this.top.value;}IsEmpty - Controllare se vuoto
Verifica se lo stack è vuoto. Complessità: O(1)
Mostra implementazione
isEmpty() { // Ritorna true se la lunghezza è 0 return this.length === 0;}Implementazione completa Stack
Ecco il codice completo delle classi Node e Stack:
// Classe per creare un singolo nodoclass Node { constructor(value) { this.value = value; this.next = null; }}
// Classe per lo stackclass Stack { constructor(value) { const newNode = new Node(value); this.top = newNode; this.length = 1; }
push(value) { const newNode = new Node(value); if (this.length === 0) { this.top = newNode; } else { newNode.next = this.top; this.top = newNode; } this.length++; return this; }
pop() { if (this.length === 0) return undefined; let temp = this.top; this.top = this.top.next; temp.next = null; this.length--; return temp; }
peek() { if (this.length === 0) return undefined; return this.top.value; }
isEmpty() { return this.length === 0; }}Queues
Cos’è una Queue
Una queue (coda) è una struttura dati basata sul principio FIFO (First In, First Out): il primo elemento inserito è il primo a essere rimosso.
Analogia: Una coda di persone - la prima persona che entra in fila è la prima a essere servita.
// Esempio visivo di una queue// Enqueue (aggiungi): ←// Dequeue (rimuovi): →
// first → [1] → [2] → [3] ← last// ↑ ↑// Rimuovi AggiungiConcetto FIFO
FIFO - First In, First Out: il primo elemento aggiunto è il primo a essere rimosso.
Esempi pratici di utilizzo
- Coda di stampa: i documenti vengono stampati nell’ordine in cui sono stati inviati
- Gestione task: i task vengono processati nell’ordine in cui sono stati aggiunti
- BFS (Breadth-First Search): gli algoritmi di ricerca in ampiezza usano queue
- Message Queue: sistemi di messaggistica processano messaggi in ordine FIFO
Terminologia Queue
- Enqueue: aggiungere un elemento alla fine della queue
- Dequeue: rimuovere l’elemento dall’inizio della queue
- First: riferimento al primo elemento della queue (quello che verrà rimosso per primo)
- Last: riferimento all’ultimo elemento della queue (dove vengono aggiunti nuovi elementi)
Implementazioni Queue
Array
Con un array ci sono due approcci, entrambi con un’operazione O(n):
// Approccio 1: Enqueue con push, Dequeue con shiftconst queue = [];queue.push(1); // Enqueue: O(1) ✅queue.push(2);queue.shift(); // Dequeue: O(n) ❌ - richiede re-indicizzazione
// Approccio 2: Enqueue con unshift, Dequeue con popconst queue2 = [];queue2.unshift(1); // Enqueue: O(n) ❌ - richiede re-indicizzazionequeue2.unshift(2);queue2.pop(); // Dequeue: O(1) ✅Problema: Con un array, non possiamo avere entrambe le operazioni O(1). Una delle due sarà sempre O(n).
Linked List
Con una linked list modificata, possiamo avere entrambe le operazioni O(1):
// Queue con linked list// first → [1] → [2] → [3] ← last// ↑ ↑// Dequeue Enqueue// O(1) O(1)| Operazione | Complessità | Note |
|---|---|---|
Enqueue (fine con last) | O(1) | Aggiunta in fondo con puntatore last |
Dequeue (inizio con first) | O(1) | Rimozione dall’inizio con puntatore first |
| Enqueue dall’inizio o Dequeue dalla fine | O(n) | Sconsigliato — richiede scorrimento di tutta la lista |
Importante: Con una linked list per queue:
- Enqueue avviene dalla fine usando
last(equivalente ditail) - Dequeue avviene dall’inizio usando
first(equivalente dihead)
Implementazione Queue
Classe Node per Queue
Il nodo è identico a quello delle linked lists e degli stack:
// Classe per creare un singolo nodoclass Node { constructor(value) { // Assegna il valore passato al costruttore this.value = value; // Inizialmente il nodo non punta a nulla this.next = null; }}
// Esempio di utilizzoconst newNode = new Node(4);// Crea un oggetto: { value: 4, next: null }Costruttore della Queue
Il costruttore crea una queue con il primo elemento. Usiamo first e last invece di head e tail.
class Queue { constructor(value) { // Crea il primo nodo con il valore passato const newNode = new Node(value);
// First punta al primo nodo (inizio della queue) this.first = newNode;
// Last punta allo stesso nodo di first (quando c'è un solo nodo) this.last = newNode;
// La queue inizia con lunghezza 1 this.length = 1; }}
// Esempio di utilizzoconst myQueue = new Queue(4);// Crea una queue con un nodo di valore 4// first e last puntano entrambi a questo nodo// length = 1Differenze rispetto a Linked List:
- Usiamo
firstinvece dihead - Usiamo
lastinvece ditail - Il resto è identico
Risultato in memoria:
{ first: { value: 4, next: null }, last: { value: 4, next: null }, // punta allo stesso oggetto di first length: 1}Operazioni Queue
Enqueue - Aggiungere alla fine
Aggiunge un nodo alla fine della queue. Complessità: O(1)
Logica:
- Crea un nuovo nodo
- L’ultimo nodo punta al nuovo nodo (
last.next = newNode) lastviene aggiornato per puntare al nuovo nodo
Edge case: Se la queue è vuota, first e last puntano entrambi al nuovo nodo.
Mostra implementazione
enqueue(value) { // Crea il nuovo nodo const newNode = new Node(value);
// Se la queue è vuota, first e last puntano al nuovo nodo if (this.length === 0) { this.first = newNode; this.last = newNode; } else { // L'ultimo nodo punta al nuovo nodo this.last.next = newNode; // Last viene aggiornato per puntare al nuovo nodo this.last = newNode; }
// Incrementa la lunghezza this.length++; return this; // Ritorna l'intera queue}Nota: Enqueue su una queue è identico a push su una linked list normale (aggiungere alla fine).
Dequeue - Rimuovere dall’inizio
Rimuove il primo nodo dalla queue e lo ritorna. Complessità: O(1)
Logica:
- Salva il riferimento al primo nodo (
temp = first) - Sposta
firstal nodo successivo (first = first.next) - Rimuove il collegamento del nodo rimosso (
temp.next = null) - Ritorna il nodo rimosso
Edge cases:
- Queue vuota: ritorna
undefined - Un solo nodo: dopo la rimozione,
lastdeve puntare anull
Mostra implementazione
dequeue() { // Se la queue è vuota, ritorna undefined if (this.length === 0) return undefined;
// Salva il riferimento al primo nodo let temp = this.first;
// Edge case: se c'è un solo nodo if (this.length === 1) { this.first = null; this.last = null; } else { // Sposta first al nodo successivo this.first = this.first.next; // Rimuove il collegamento del nodo rimosso temp.next = null; }
// Decrementa la lunghezza this.length--;
// Ritorna il nodo rimosso return temp;}Nota: Dequeue su una queue è identico a shift su una linked list normale (rimuovere dall’inizio).
IsEmpty - Controllare se vuota
Verifica se la queue è vuota. Complessità: O(1)
Mostra implementazione
isEmpty() { // Ritorna true se la lunghezza è 0 return this.length === 0;}Peek - Visualizzare il primo elemento
Ritorna il valore del primo elemento senza rimuoverlo. Complessità: O(1)
Mostra implementazione
peek() { // Se la queue è vuota, ritorna undefined if (this.length === 0) return undefined;
// Ritorna il valore del primo nodo return this.first.value;}Implementazione completa Queue
Ecco il codice completo delle classi Node e Queue:
// Classe per creare un singolo nodoclass Node { constructor(value) { this.value = value; this.next = null; }}
// Classe per la queueclass Queue { constructor(value) { const newNode = new Node(value); this.first = newNode; this.last = newNode; this.length = 1; }
enqueue(value) { const newNode = new Node(value); if (this.length === 0) { this.first = newNode; this.last = newNode; } else { this.last.next = newNode; this.last = newNode; } this.length++; return this; }
dequeue() { if (this.length === 0) return undefined; let temp = this.first; if (this.length === 1) { this.first = null; this.last = null; } else { this.first = this.first.next; temp.next = null; } this.length--; return temp; }
peek() { if (this.length === 0) return undefined; return this.first.value; }
isEmpty() { return this.length === 0; }}Confronto Stack vs Queue
Ora che abbiamo implementato entrambe le strutture, confrontiamole:
Tabella comparativa
| Caratteristica | Stack | Queue |
|---|---|---|
| Principio | LIFO (Last In, First Out) | FIFO (First In, First Out) |
| Aggiunta | Push (top) | Enqueue (last) |
| Rimozione | Pop (top) | Dequeue (first) |
| Accesso | Solo dalla cima | Solo dall’inizio |
| Analogia | Lattina di palline | Coda di persone |
| Uso | Undo/Redo, Call Stack, DFS | Task Queue, BFS, Message Queue |
Complessità con implementazioni diverse
| Operazione | Stack (Array fine) | Stack (Linked List inizio) | Queue (Array) | Queue (Linked List) |
|---|---|---|---|---|
| Push/Enqueue | O(1) | O(1) | O(n) una delle due | O(1) |
| Pop/Dequeue | O(1) | O(1) | O(n) una delle due | O(1) |
Conclusione chiave:
- Gli stack possono essere implementati efficientemente sia con array che con linked list
- Le queue richiedono linked list per avere entrambe le operazioni O(1) con gli array una delle due sarà sempre O(n)
Quando usare Stack vs Queue
Usa uno Stack quando:
- Devi gestire operazioni LIFO (Last In, First Out)
- Hai bisogno di backtracking (tornare indietro nelle operazioni)
- Devi implementare undo/redo
- Lavori con chiamate ricorsive o call stack
- Devi fare parsing di espressioni (es. parentesi bilanciate)
Usa una Queue quando:
- Devi gestire operazioni FIFO (First In, First Out)
- Hai bisogno di processare elementi nell’ordine di arrivo
- Devi implementare BFS (Breadth-First Search)
- Lavori con task scheduling o message processing
- Vuoi gestire buffer o stream di dati
Esercizi e problemi comuni
Per praticare e approfondire stacks e queues, consulta la sezione dedicata agli esercizi e problemi comuni