Sei sulla pagina 1di 3

ARRAY DINAMICI E LISTE

Il modo più semplice per aggregare dei dati elementari consiste nel disporli uno di
seguito all’altro a formare una sequenza lineare, identificando ciascun dato con la
posizione occupata. In questo capitolo studieremo tale disposizione descrivendo due
diversi modi di realizzarla, l’accesso diretto e l’accesso sequenziale.

Una sequenza lineare è un insieme finito di elementi disposti consecutivamente in cui


ognuno ha associato un indice di posizione in modo univoco. Nel disporre gli elementi in
una sequenza viene ritenuto importante il loro ordine relativo; infatti generalmente
invertendo l’ordine degli elementi, generalmente la parola risulterà diversa dall’originale e
potrà avere o no un significato.

Come detto all’inizio, l’accesso agli elementi di una sequenza lineare avviene
generalmente eseguito in due modalità. In quella ad accesso diretto, dato un indice i,
accediamo direttamente all’elemento 𝑎𝑖 della sequenza senza doverla attraversare (costo
uniforme e indipendente dall’ indice di posizione i). Nel seguito chiameremo array le
sequenze lineari ad accesso diretto. L’altra modalità consiste nel raggiungere l’elemento
desiderato attraversando la sequenza a partire dal suo estremo, solitamente il primo
elemento. Tale modalità, detta ad accesso sequenziale, ha un costo O(i) proporzionale
alla posizione i dell’elemento cui si desidera accedere. D’ora in poi chiameremo liste le
sequenze lineari ad accesso sequenziale.
I due modi di realizzare l’accesso agli elementi di una sequenza lineare non devono essere
considerati equivalenti, vista la differenza di costo computazionale. Entrambe le modalità
presentano pro e contro per cui non è possibile dire in generale che una sia preferibile
all’altra: tale scelta dipende dall’applicazione che vogliamo realizzare o dal problema che
dobbiamo risolvere.

ARRAY DINAMICO

Un array dinamico è una struttura dati array che può essere ridimensionata e consente
di aggiungere e rimuovere elementi.
L'array più semplice è costruito allocando un array di dimensione fissa e dividendolo in
due parti: la prima memorizza gli elementi dell'array dinamico e la seconda è riservata, o
inutilizzata. A questo punto è possibile aggiungere o rimuovere elementi alla fine
dell'array dinamico in tempo costante utilizzando lo spazio riservato, finché questo spazio
non viene completamente riempito.
Il ridimensionamento comporta un costo molto elevato, perché implica la costruzione di
un altro array con dimensione aumentata e la successiva copia degli elementi dal
"vecchio" al "nuovo" array. Per evitare di eseguire questo procedimento ogni volta che si
aggiunge un nuovo elemento, gli array dinamici si ridimensionano raddoppiando le
dimensioni (invece che di una sola unità). In questo modo si utilizza lo spazio riservato
per espansioni future, facendo sì che l’immissione e la cancellazione di elementi avvenga
in un tempo costante.
Le funzioni utilizzate per gestire la memoria dinamica sono
principalmente malloc() e calloc() (rinominata dall’ANSI), adibite all’allocazione della
memoria, free() che, come si intuisce, serve per liberare la memoria allocata,
e realloc() la cui funzione è quella di permettere la modifica di uno spazio di memoria
precedentemente allocato. Un comando particolarmente utile risulta essere sizeof, che
restituisce la dimensione del tipo di dato da allocare.

Per far funzionare il programma dobbiamo includere la libreria malloc.h, senza la quale
queste funzioni non potrebbero fare il loro dovere.

Un array dinamico può essere creato come implementazione di una struttura (struct),
definendo come membri minimi (sempre necessari) un puntatore alla struttura con alla
sua sinistra il tipo di dati dei valori che l’array deve rappresentare e due variabili intere:
la prima che definisce il contatore al numero di valori presenti nell’array e la seconda che
definisce anch’essa un contatore, ma al numero massimo di elementi contenibili.

LISTE CONCATENATE
Una lista concatenata è una struttura dati dinamica fondamentale nella
programmazione che consiste di una sequenza di nodi (o elementi), ognuno contenente
campi di dati arbitrari ed uno o due riferimenti che puntano al nodo successivo e/o
precedente. Le liste concatenate permettono l’inserzione e la rimozione di nodi in ogni
punto della lista in tempo costante, permettendo tuttavia il solo accesso sequenziale.
Esistono diversi tipi di liste concatenate: liste concatenate semplici, liste concatenate
doppie e liste circolari.

In una lista concatenata semplice (o slist), ogni nodo è associato a un puntatore


contenente l’indirizzo dell’elemento successivo (o precedente). La variabile p (puntatore)
punta al primo di questi nodi e ci consente di rintracciare nella memoria la lista in
questione. L'ultimo nodo contiene un indirizzo a un puntatore detto NULL, che
corrispondente all'indirizzo zero e sta ad indicare la fine della lista.
Un tipo più sofisticato di lista concatenata è una lista doppiamente concatenata in cui
ogni nodo possiede due collegamenti: uno punta al nodo precedente (o ad un valore nullo
o una lista vuota se è il nodo iniziale), l'altro punta al nodo successivo (o ad un valore
nullo o una lista vuota se è il nodo finale).

Infine vediamo la lista circolarmente concatenata, in cui il nodo iniziale e finale sono
collegati. Questo può essere implementato sia per liste semplicemente o doppiamente
concatenate. Per attraversare una lista circolarmente concatenata, si può iniziare in
qualsiasi nodo e si segue la lista in entrambe le direzioni fino a ritornare al nodo di
origine. Viste in un altro modo, le liste circolarmente concatenate non hanno né inizio né
fine. Questo tipo di liste è utile per maneggiare buffer di dati e nei casi in cui si ha un
oggetto nella lista e si desidera scorrere tutti gli altri oggetti della lista. Il puntatore che
punta all'intera lista è normalmente chiamato il puntatore finale.