Progetto d’esame PAS A042 Informatica: Fondamenti e Programmazione Sessione di luglio 2014 Una struttura dati si dice dinamica se permette l’esecuzione di operazioni che ne modficano la struttura o anche semplicemente la dimensione. Strutture dati dinamiche possono essere realizzate (o simulate) anche lavorando con linguaggi di programmazione che non supportano allocazione e disallocazione di memoria dinamica. In questo progetto ci concentreremo su strutture dinamiche organizzate come alberi binari. Questi possono essere realizzati utilizzando la cosiddetta soluzione dei vettori paralleli, cioè vettori con lo stesso numero di posizioni e tali per cui, per ogni indice i, le informazioni memorizzate nelle posizioni i-esime dei vettori si riferiscono tutte alla stessa entità (e dunque allo stesso nodo dell’albero). La seguente figura illustra un esempio di albero binario e la sua implementazione mediante tre array paralleli: un array che contiene le informazioni (in questo esempio, semplicemente delle lettere) e due array che indicano le posizioni dei nodi figli. Le posizioni vuote indicano assenza di informazione. A C E D B inf 1 A 2 B 3 C left 6 9 2 right 3 7 4 5 D 6 E 7 G 8 9 10 11 F 5 G F Figure 1: Un esempio di albero binario rappresentato mediante array paralleli. L’unica altra informazione necessaria è rappresentata da una semplice variabile di tipo intero, che chiameremo root, che contiene l’indice della posione corrispondente alla radice dell’albero. Nel caso dell’esempio root = 1 perché la radice è memorizzata nella posizione 1 degli array paralleli. Si noti che le posizioni occupate da informazione significativa non sono necessariamente contigue; alcune posizioni possono essere vuote (come la 4 e la 1 8 nell’albero di figura 1) a causa di sequenze di operazioni di inserzione e cancellazione di elementi dalla struttura dati. In questo progetto si chiede di definire e implementare una struttura dati “albero binario dinamico” utilizzando la tecnica degli array paralleli. Chiaramente, ogni struttura dati (e quindi anche un albero) è pensata e realizzata per memorizzare informazione rilevante per una data applicazione. Tuttavia, dal punto di vista della manipolazione algoritmica della struttura, tale informazione gioca un ruolo marginale. Nel nostro caso supporremo quindi che ogni nodo memorizzi semplicemente un singolo dato numerico. Dal punto di vista squisitamente algoritmico ciò che conta sono invece le informazioni che conferiscono una certa organizzazione ai dati (in questo caso, gerarchica o, appunto, “ad albero”) e che consentono l’accesso ai medesimi o la loro modifica. Proprio allo scopo di facilitare accesso e modifica, prevediamo che ogni nodo dell’albero contenga tre ulteriori campi : i due puntatori ai nodi figli, come nel caso di figura 1, e un puntatore al nodo genitore, assente nell’esempio di figura. Possiamo dunque definire il singolo nodo dell’albero mediante la seguente struttura logica: struct nodo; float inf; ↑nodo left,right; ↑nodo parent; end dove la notazione ↑nodo vuol indicare il tipo di dato “puntatore a nodo”. Ovviamente, nell’implementazione concreta mediante array paralleli, una variabile di questo tipo è semplicemente un intero. La struttura dati deve “supportare” almeno le operazioni primitive descritte di seguito. search Dato un numero intero i, restituisce l’indice del nodo corrispondente (cioè la posizione k degli array paralleli tale che inf[k] = i); se i non è presente nell’albero, allora restituisce 0. insert Dato un numero intero i, inserisce un nuovo nodo nell’albero che memorizza i nel campo informazione. Se i è già presente, l’operazione corrisponde a no-op. delete Dato l’indice k di un nodo, lo rimuove dall’albero. Dopo la rimozione del nodo, la struttura dati risultante deve essere ancora un albero connesso. 2 save Salva l’albero in file binario dal nome scelto dall’utente, oppure predefinito se l’utente non sceglie alcun nome. load Carica l’albero da un file binario dal nome scelto dall’utente, oppure predefinito se l’utente non sceglie alcun nome. In caso di fallimento dell’operazione, il precedente contenuto dell’albero deve rimanere inalterato, mentre in caso di successo il precedente contenuto dell’albero è perso. Utilizzando le operazioni primitive sopra descritte, si scrivano poi due procedure che risolvono i seguenti problemi: stampa ordinata Dato un albero T (cioè dato il puntatore alla radice dell’albero), stampa tutti le informazioni numeriche in esso contenute in ordine crescente; minimo antenato comune dati due nodi x e y stampa l’informazione numerica contenuta nel nodo che è il minimo antenato comune di x e y, cioè quel nodo z tale che: (1) z è antenato sia di x che di y, e (2) non esiste un discendente di z che sia ancora antenato di x e y. Nella risoluzione del secondo problema, si tenga conto del fatto che un nodo è considerato antenato di se stesso, Questo vuol dire che se (ad esempio) x è antenato di y, allora z = x, cioè x è il minimo antenato comune cercato. Il punteggio ottenuto nell’esame di Informatica: Programmazione dipenderà anche da quanto efficacemente saranno applicati i principi di Ingegneria del Software visti nel corrispondente corso. 3