L'insieme di Mandelbrot e' un insieme frattale definito come l'insieme dei numeri complessi c per i quali la successione definita da:
z(0) = 0 z(n+1) = z(n)^2 + c
e' limitata, cioe' |z( n )|< 2
per ogni n >=0
.
Infatti al variare di c
, la sequenza puo' tendere
all’infinito o rimanere confinata in un disco di raggio 2 del piano complesso centrato
nell’origine.
L’algoritmo piu' semplice per visualizzare (una approssimazione de) l’insieme di Mandelbrot ´e l’Escape Time Algorithm. In questo algoritmo, dati A
(l’area del piano complesso
da visualizzare) ed r
(una precisione fissata) si effettuano i seguenti passi:
|z( r )|< = 2
si considera c appartenente all'insieme e si assegna a c il colore NERO| z(j) |>=2
Utilizzare le strutture (struct) per rappresentare i numeri complessi come visto a lezione e scrivere un programma C che calcola l'insieme di Mandelbrot per il rettangolo di estremi (-2,1) (1,-1) e stampando sullo standard output i colori dei pixel suppenendo di dividere il rettangolo di 300×200. Per la stampa a colori in C consultare le FAQ.
Consideriamo il problema di leggere da standard input una sequenza di valori reali terminata da 0.0 creando la lista corrispondente di tipo
typedef struct lista_d { double val; struct lista_d * next; } lista_d_t ;
double leggi_nuovo_valore(void);
e utilizza le funzioni
lista_d_t * inserisci_testa ( lista_d_t * l, double v); lista_d_t * inserisci_coda ( lista_d_t * l, double v); lista_d_t * inserisci_ord ( lista_d_t * l, double v);
per inserire nella lista. Ogni funzione prende come parametri (i) la lista l e (ii) il valore v da inserire (letto precedentemente con leggi_nuovo_valore(void)
, e restituisce la lista modificata (ovvero puntatore al primo elemento della lista, che potrebbe cambiato dopo l'inserzione).
La scelta della funzione per inserzione viene richiesta all'utente una volta per tutte prima di inserire i valori.
Inoltre, si utilizza una funzione stampa_lista()
per stampare la lista inserita prima di terminare.
Implementare le seguenti funzioni in modo iterativo e ricorsivo:
/** calcola e restituisce il massimo della lista l */ double max ( lista_d_t * l); /** calcola e restituisce la somma di tutti gli elementi della lista l*/ double somma ( lista_d_t * l); /** libera la memoria occupata dalla lista */ void free_list( lista_d_t * l); /** cancella, se presente, l'elemento che contiene la prima occorrenza del valore v dalla lista l (liberando la memoria) e restituisce la nuova lista */ lista_d_t * cancella_uno ( lista_d_t * l, double v); /** cancella tutti gli elementi di valore del valore v dalla lista l (liberando la memoria) e restituisce la nuova lista */ lista_d_t * cancella_tutti ( lista_d_t * l, double v);
e sviluppare un main() che ne testa il funzionamento.
Definire una funzione ricorsiva che verifica che per ogni elemento di una lista valga la proprietà
\[
l(i + 1) = 2*l(i)
\]
dove $l(i)$ indica l'i-esimo elemento della lista a partire dalla testa. La funzione restituisce true
se la proprietà è verificata e false
altrimenti. I valori booleani sono definiti con una opportuna MACRO.
Realizzare le funzioni dell'esercizio 1 utilizzando liste con puntatore al precedente e al successivo
typedef struct elem_d { double val; struct elem_d * prec; struct elem_d * next; } elem_d_t ;
in questo caso la lista complessiva puo' essere definita ad esempio come una struttura con due puntatori, uno alla testa ed uno alla coda
typedef struct lista_d { struct elem_d * head; struct elem_d * tail; } lista_d_t ;
come si modificano gli algoritmi sviluppati precedentemente ?
Vogliamo realizzare degli array di double di grandi dimensioni che contengono solo una piccola percentuale di elementi diversi da 0 come liste concatenate definite come
typedef struct sparse_d { double val; unsigned index; struct elem_d * next; } sparse_d_t ;
nella lista sono presenti solo gli elementi diversi da zero e per ogni elemento e' indicato l'indice a cui corrisponde. La Lista e' mantenuta ordinata rispetto al campo indice.
Realizzare le funzioni put
e get
che permettono di leggere il valore dell'elemento di un certo indice i
(get
) e di modificarne il valore (put
), un possibile prototipo per queste funzioni e':
double get (sparse_d_T * a, unsigned indice); sparse_d_t * put (sparse_d_T * a, double x, unsigned indice);
Verificare la correttezza degli accessi ai puntatori dello heap compiuti dalle funzioni su liste sviluppate negli esercizi precedenti utilizzando valgrind
.
Questo strumento permette fra l'altro di capire se tutte le variabili sono inizializzate prima del loro uso, se accediamo a memoria gia' deallocata o mai allocata e situazioni similari
Per fare questo procedere come segue:
-g
per includere le informazioni di debugging. Ad esempio se il mio file si chiama main.c
posso compilare congcc -Wall -pedantic -g -o prova main.c
valgrind ./prova
in questo modo, a schermo verranno riportare le infrazioni rilevate. Ad esempio, invalid read o invalid write sono accessi in lettura o scrittura a memoria non allocata o gia' deallocata.
valgrind
contiene moltissime opzioni, invitiamo gli studenti interessati ad esplorarle partendo dalsito.