Esistono diversi modi per implementare code e stack con elenchi e matrici collegati e non sono sicuro di quali si stiano cercando. Prima di analizzare una qualsiasi di queste strutture, tuttavia, esaminiamo alcune importanti considerazioni sul runtime per le strutture di dati di cui sopra.
In un elenco a collegamento singolo con solo un puntatore testa, il costo per anteporre un valore è O (1) - semplicemente creiamo il nuovo elemento, leghiamo il puntatore al punto precedente, quindi aggiorniamo il puntatore della testa. Il costo per eliminare il primo elemento è anche O (1), operazione che viene eseguita aggiornando il puntatore head per puntare all'elemento dopo il capo corrente, quindi liberando la memoria per la vecchia testata (se viene eseguita una gestione esplicita della memoria). Tuttavia, i fattori costanti in questi termini O (1) possono essere elevati a causa della spesa delle allocazioni dinamiche. L'overhead di memoria dell'elenco collegato è solitamente O (n) memoria extra totale dovuta alla memorizzazione di un puntatore aggiuntivo in ciascun elemento.
In una matrice dinamica, è possibile accedere a qualsiasi elemento in O (1). Possiamo anche aggiungere un elemento in amortized O(1), il che significa che il tempo totale per n inserimenti è O (n), sebbene i limiti di tempo effettivi su ogni inserimento possano essere molto peggiori.In genere, gli array dinamici vengono implementati inserendo la maggior parte degli inserimenti in O (1) aggiungendo spazio preallocato, ma eseguendo un numero limitato di inserimenti nel tempo Θ (n) raddoppiando la capacità dell'array e copiando gli elementi. Esistono tecniche per cercare di ridurlo allocando uno spazio extra e copiando pigramente gli elementi (vedi this data structure, per esempio). In genere, l'utilizzo della memoria di un array dinamico è abbastanza buono - quando l'array è completamente pieno, ad esempio, c'è solo O (1) overhead - anche se subito dopo che l'array ha raddoppiato le dimensioni potrebbe esserci O (n) inutilizzato elementi allocati nell'array. Poiché le allocazioni sono infrequenti e gli accessi sono veloci, gli array dinamici sono in genere più veloci degli elenchi collegati.
Ora, pensiamo a come implementare uno stack e una coda utilizzando un elenco collegato o un array dinamico. Ci sono molti modi per farlo, quindi mi si assume che si sta utilizzando le seguenti implementazioni:
- Stack:
- coda:
- lista collegata: Come lista semplicemente legata con un puntatore testa e coda.
- Array: come circular buffer supportato da un array.
Consideriamo uno alla volta.
Stack supportato da un elenco collegato separatamente. Poiché un elenco collegato singolarmente supporta O (1) time ante e delete-first, il costo da spingere o inserire in uno stack con backlist collegato è anche O (1) worst case. Tuttavia, ogni nuovo elemento aggiunto richiede una nuova allocazione e le allocazioni possono essere costose rispetto ad altre operazioni.
Stack supportato da un array dinamico. La spinta allo stack può essere implementata aggiungendo un nuovo elemento all'array dinamico, che impiega il tempo O (1) e il tempo O (n) peggiori. Popping dallo stack può essere implementato rimuovendo solo l'ultimo elemento, che viene eseguito nel caso peggiore O (1) (o O (1) ammortizzato se si desidera provare a recuperare spazio inutilizzato). In altre parole, l'implementazione più comune è il push o pop di O (1) best-case, il push O (n) nel caso peggiore e il pop O (1) pop e il push O (1) e O (1) pop ammortizzati.
Coda supportata da un elenco collegato singolarmente. L'accodamento nella lista concatenata può essere implementato accodando alla parte posteriore dell'elenco linkato separatamente, che impiega il tempo O (1) nel caso peggiore. La dequeuing può essere implementata rimuovendo il primo elemento, che prende anche il tempo peggiore O (1). Ciò richiede anche una nuova allocazione per accodamento, che può essere lento.
Coda supportata da un buffer circolare crescente. L'accodamento nel buffer circolare funziona inserendo qualcosa nella successiva posizione libera nel buffer circolare. Questo funziona aumentando la matrice se necessario, quindi inserendo il nuovo elemento. Usando un'analisi simile per l'array dinamico, ciò richiede il tempo ottimale O (1), il caso peggiore O (n) e il tempo ammortizzato O (1). La rimozione delle code dal buffer funziona rimuovendo il primo elemento del buffer circolare, che nel caso peggiore richiede tempo O (1).
Per riassumere, tutte le strutture supportano la pressione e il popping di elementi in tempo O (n). Le versioni delle liste collegate hanno un comportamento peggiore, ma potrebbero avere un runtime complessivo peggiore a causa del numero di allocazioni eseguite. Le versioni dell'array sono più lente nel caso peggiore, ma offrono prestazioni complessive migliori se il tempo per operazione non è troppo importante.
Un'altra opzione che si desidera esaminare per l'implementazione di stack è la VList, una struttura dati recente che è un ibrido di un elenco collegato e un array dinamico. Rende meno allocazioni di una lista collegata e ha meno puntatori in essa, anche se l'utilizzo dello spazio è un po 'più alto nel peggiore dei casi. Potresti anche voler esaminare le chunklist, che sono un altro ibrido di matrici e liste concatenate, che possono essere utilizzate sia per gli stack che per le code.
Spero che questo aiuti!
Avete codificato e profilato implementazioni concorrenti? – nmichaels
No, mi piace tenerlo [DRY] (http://en.wikipedia.org/wiki/Don't_repeat_yourself) – IAmYourFaja
Quindi ... quale parte di questa è la vera domanda dei compiti? È a sostegno di qualche progetto di compiti a casa? – nmichaels