La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Algoritmi e strutture dati

Presentazioni simili


Presentazione sul tema: "Algoritmi e strutture dati"— Transcript della presentazione:

1 Algoritmi e strutture dati
Prof. Michele Amoretti Fondamenti di Informatica a.a. 2008/2009

2 Sommario Definizione di algoritmo Attributi degli algoritmi
Costo computazionale Descrizione degli algoritmi Tecniche di progettazione di algoritmi Strutture dati Algoritmi di ordinamento

3 Definizione di algoritmo
Algoritmo: un insieme ordinato di operazioni non ambigue ed effettivamente computabili che, quando eseguito, produce un risultato e si arresta in un tempo finito. In termini informali, un algoritmo è una sequenza ordinata di operazioni che risolve un problema specifico. Se questa idea aveva una certa importanza per il calcolo matematico, l'avvento dell'informatica l'ha arricchita di nuovi significati..

4 Definizione di algoritmo
.. tanto che, con molta enfasi, è stata data anche la seguente definizione di informatica: L’informatica è lo studio degli algoritmi, che comprende: 1. le loro proprietà formali e matematiche; 2. le loro implementazioni hardware; 3. le loro implementazioni linguistiche; 4. le loro applicazioni. Gibbs, N. E., e Tucker, A. B. “A Model Curriculum for a Liberal Arts Degree in Computer Science”, Comm. of the ACM, vol. 29, n. 3 (marzo 1986).

5 Definizione di algoritmo
L’opposto di algoritmo: euristica. Si definisce infatti procedimento euristico un metodo di approccio alla soluzione dei problemi che non segue un chiaro percorso, ma si affida all'intuito e allo stato temporaneo delle circostanze, al fine di generare nuova conoscenza.

6 Definizione di algoritmo
Tutte le operazioni utilizzate per realizzare algoritmi rientrano in una delle seguenti tre categorie: Operazioni sequenziali Un’istruzione sequenziale esegue una singola attività ben definita. Terminata l’attività, l’algoritmo passa all’operazione successiva. Solitamente le operazioni sequenziali sono espresse come semplici frasi dichiarative. Operazioni condizionali Si tratta delle istruzioni di un algoritmo che “pongono una domanda”. L’operazione successiva è selezionata sulla base della risposta fornita alla domanda. Operazioni iterative Si tratta delle istruzioni “di ciclo” di un algoritmo. Indicano di non proseguire con l’istruzione successiva, ma di tornare indietro e ripetere l’esecuzione di un precedente blocco di istruzioni.

7 Definizione di algoritmo
Un algoritmo deve essere: - non ambiguo (i risultati non devono variare in funzione della macchina o persona che esegue l'algoritmo) - corretto (deve risolvere un dato problema) - realizzabile (deve essere eseguibile con le risorse a disposizione) - finito (deve essere composto da un numero finito di passi elementari; le operazioni sono eseguite un numero finito di volte) - efficiente (deve avere un costo accettabile, se non ottimo, in termini di risorse consumate: tempo di CPU richiesto per completare, quantità di memoria utilizzata, quantità di bit trasferiti)

8 Attributi degli algoritmi
Correttezza Facilità di comprensione Eleganza Efficienza Talvolta eleganza e facilità di comprensione vanno in direzioni contrarie: più elegante è la soluzione, più difficile risulta da capire. Es. somma dei primi N interi (algoritmo di Gauss) Conviene scambiare la facilità di comprensione per l’eleganza? Naturalmente l’ideale è un algoritmo elegante e facile da comprendere al tempo stesso.

9 Attributi degli algoritmi
Efficienza è il termine usato per descrivere l’uso attento delle risorse da parte di un algoritmo. Come si misurano il tempo e lo spazio consumati da un algoritmo, in modo da determinare se è efficiente? L’efficienza rispetto allo spazio si può giudicare in base alla quantità di informazioni che l’algoritmo deve registrare nella memoria del computer per svolgere il proprio compito, oltre ai dati iniziali sui quali opera. L’efficienza rispetto al tempo di un algoritmo è un’indicazione della quantità di “lavoro” richiesto dall’algoritmo stesso. È una misura dell’efficienza implicita del metodo, indipendente dalla velocità della macchina su cui è eseguito, dai valori dei dati di ingresso elaborati ma non dalla loro quantità.

10 Attributi degli algoritmi
La quantità di lavoro di un algoritmo coincide con il numero di istruzioni eseguite? Non tutte le istruzioni si equivalgono, perciò forse non vanno “contate” tutte allo stesso modo. Alcune istruzioni sono fondamentali per il modo in cui l’algoritmo opera, mentre altre svolgono attività periferiche in supporto dell’opera principale. Per misurare l’efficienza rispetto al tempo, identifichiamo le unità di lavoro fondamentali dell’algoritmo e contiamo quante volte sono eseguite.

11 Tipologia di algoritmo
Attributi degli algoritmi Sia N la dimensione dei dati da elaborare. Tempo di esecuzione Tipologia di algoritmo logN Risolvono grossi problemi riducendone la dimensione di un fattore costante. N Eseguono poche operazioni su ciascun elemento dell’input. NlogN Suddividono il problema in sottoproblemi più piccolo che vengono risolti indipendentemente. N2 Elaborano gli elementi dell’input a coppie (probabilmente all’interno di due cicli nidificati). 2N Risolvono alcuni problemi di interesse pratico per i quali non si conoscono ancora algoritmi non esponenziali. N3 Elaborano terne di valori (probabilmente all’interno di tre cicli nidificati).

12 Complessità degli algoritmi
La notazione O(·)

13 Complessità degli algoritmi
La notazione Ω(·)

14 Complessità degli algoritmi
La notazione Θ(·) Gli algoritmi si classificano spesso in base all’ordine di grandezza Θ() della loro efficienza rispetto al tempo.

15 Descrizione di algoritmi
Il linguaggio di descrizione di un algoritmo deve essere adeguato alle caratteristiche del suo esecutore. esecutore umano: - linguaggio naturale (eventualmente strutturato) - linguaggio grafico (es. diagrammi di flusso) calcolatore (esecutore automatico): - linguaggio di programmazione (programma = specifica di una computazione)

16 Descrizione di algoritmi
La macchina di Turing è un modello astratto di generico sistema risolutore di problemi (non necessariamente un calcolatore elettronico). Le istruzioni della MT sono quindi un modello di algoritmo, infatti: - non vi è mai confusione su cosa la MT deve fare ad ogni passo, né sulla successiva istruzione da eseguire (non ambiguità) - la MT esegue le istruzioni e restituisce un risultato sul nastro di uscita (correttezza) - le istruzioni comportano l’uso delle risorse a disposizione, i k nastri, e di nient’altro (realizzabilità) Le altre caratteristiche (finitezza ed efficienza) non dipendono da come è fatta la MT, ma da come le istruzioni sono concepite, e dai dati in ingresso.

17 Pseudocodice Utilizzare un insieme di istruzioni per MT per descrivere un algoritmo non è certamente agevole. Molti studiosi di informatica utilizzano una notazione denominata pseudocodice per progettare e rappresentare gli algoritmi. Si tratta di costrutti in simil-linguaggio naturale studiati per assomigliare alle istruzioni di un linguaggio di programmazione, ma che in realtà non si eseguono su un computer. Lo pseudocodice rappresenta un compromesso tra i due estremi del linguaggio naturale e di quello formale; è semplice, altamente leggibile e praticamente privo di regole grammaticali.

18 Pseudocodice Esempio moltiplicazione per somme
Problema: dati due numeri interi a e b maggiori o uguali a 0, determinarne il prodotto p. p  0 se b=0 vai all’istruzione 6 p  p+a b  b-1 vai all’istruzione 2 fine

19 Pseudocodice ATTENZIONE!
Il fatto che il numero di istruzioni presenti nella descrizione di un algoritmo sia finito non implica necessariamente che l’algoritmo termini in un tempo finito! r  0 r  r+1 vai all’istruzione 2 fine Manca la condizione di uscita dal ciclo!

20 Diagrammi di flusso Un diagramma di flusso (flow chart) è la rappresentazione grafica dei passi che costituiscono un algoritmo. E’ uno strumento efficace per la descrizione degli algoritmi. I diagrammi di flusso usano forme geometriche diverse per rappresentare: trasferimento di informazioni (lettura dati, scrittura risultati, visualizzazione dati intermedi) esecuzione di calcoli assunzione di decisioni esecuzione di iterazioni (ripetizione di sequenze di operazioni)

21 Diagrammi di flusso v  E B Simboli convenzionali:
Elaborazione – I blocchi rettangolari possono rappresentare istruzioni di assegnamento o più in generale istruzioni che comportano una qualche modifica dello stato globale della computazione. Decisione – I blocchi a forma di rombo vengono utilizzati per rappresentare istruzioni di salto condizionato. Inizio/fine – I blocchi ovali vengono utilizzati per rappresentare l’inizio e la fine dell’algoritmo. v  E B i1,i2,..,in o1,o2,..,on

22 Diagrammi di flusso Strutture per il flusso di controllo: While-Do I O

23 Diagrammi di flusso I O Strutture per il flusso di controllo:
Repeat-Until I O

24 Diagrammi di flusso I O Then Else
Strutture per il flusso di controllo: If-Then-Else I O Then Else

25 Diagrammi di flusso Esempio calcolo di N! = N(N-1)(N-2)..1

26 Strutture dati Un problema è generalmente caratterizzato da una o più “variabili”. I dati in ingresso, i dati in uscita, i dati generati e consumati durante l’elaborazione, sono spesso indicati come variabili. Quando si assegnano dei valori precisi ai dati in ingresso, si definisca una “istanza” del problema. Ciascuna variabile è caratterizzata da un tipo. Il tipo è un nome che indica l'insieme di valori che una variabile, o il risultato di un'espressione (che tipicamente coinvolge più variabili), possono assumere e le operazioni che su tali valori si possono effettuare. Dire per esempio che la variabile X è di tipo "numero intero" significa affermare che X può assumere come valori solo numeri interi (appartenenti ad un certo intervallo) e che su tali valori sono ammesse solo certe operazioni (ad esempio le operazioni aritmetiche elementari).

27 Strutture dati I tipi si dividono in tipi base (o primitivi) e tipi complessi (o derivati). I tipi base non possono essere decomposti, come i numeri interi, i numeri in virgola mobile, i booleani (questi ultimi possono assumere solo due valori: vero o falso), i caratteri (es. caratteri ASCII); ogni linguaggio tipizzato ne prevede un certo insieme. I tipi complessi si ottengono dai tipi atomici mediante opportuni operatori forniti dal linguaggio.

28 Strutture dati Una struttura dati è un'entità usata per organizzare un insieme di dati all'interno della memoria del computer, ed eventualmente per allocarli in una memoria di massa. La scelta delle strutture dati da utilizzare è strettamente legata a quella degli algoritmi. A tal proposito, solitamente si utilizza il concetto unificato di “Algoritmi e Strutture Dati”. La scelta della struttura dati influsce inevitabilmente sull'efficienza degli algoritmi utilizzati. La struttura dati è un metodo di organizzazione dei dati, quindi prescinde dai dati effettivamente contenuti. Una struttura dati può contenere dati di tipo base, di tipo complesso, e anche altre strutture dati.

29 Strutture dati ARRAY Un array è una struttura dati omogenea, che contiene un numero finito di elementi dello stesso tipo, ad esempio interi. Questi elementi sono individuati attraverso un indice numerico intero, che tipicamente va da 0 al numero massimo di elementi meno uno. int v[10] v[0]  5 La dimensione dell’array deve essere dichiarata al momento della sua creazione. L'accesso ad un elemento di un array ha un costo computazionale costante, mentre l'aggiunta o la rimozione di elementi in posizione casuale possono essere piuttosto onerose.

30 Strutture dati ARRAY BIDIMENSIONALE (MATRICE) ARRAY MULTIDIMENSIONALE
int v[10][5] v[0][2]  5 ARRAY MULTIDIMENSIONALE int v[10][5]..[7] v[0][2]..[0]  53

31 Strutture dati RECORD (o STRUTTURA)
Un record definisce un tipo complesso. Gli elementi che lo compongono sono detti anche campi, e sono identificati da un nome. Un record può essere eterogeneo, cioè può contenere una combinazione di elementi di diverso tipo. struct { char nome[8]; char coloreDelPelo[8]; char coloreDegliOcchi[8];  definizione del record Gatto int età; } Gatto; Gatto unGatto; unGatto.nome  “Felix”;  definizione della variabile unGatto di tipo Gatto unGatto.età  2;

32 Strutture dati CLASSE Una classe consiste di un record a cui sono associate anche delle operazioni. Una classe quindi è un altro modo di definire un tipo complesso. class Dog { private char name[8]; private char colour[8]; private char race[16];  definizione della classe Dog private int age; int getName() { return name; } void setName(nome) { this.name = name; } } Dog aDog = new Dog();  definizione di una istanza della classe Dog aDog.setName(“Fido”); (si dice che aDog è un oggetto di tipo Dog)

33 Strutture dati LISTA CONCATENATA
- gli elementi della lista sono disposti sequenzialmente come nell’array, ciascuno all’interno di un nodo che contiene anche un riferimento al nodo successivo (ed eventualmente a quello precedente) - la lista può crescere e decrescere di dimensione - cambiando i riferimenti nei nodi, la lista può essere riorganizzata

34 Strutture dati STACK (PILA)
Una pila è una struttura dati di tipo LIFO (Last In First Out). Operazioni: push() – inserisce un dato in cima alla pila pop() – estrae un dato dalla cima della pila Viene tipicamente realizzata con array o liste.

35 Strutture dati QUEUE (CODA)
Una pila è una struttura dati di tipo FIFO (First In First Out). Operazioni: insert() – inserisce un dato in fondo alla coda remove() – estrae un dato dalla testa Viene tipicamente realizzata con array o liste.

36 Strutture dati GRAFO Ci sono due modi generali per rappresentare
un grafo con una struttura dati utilizzabile da un programma: la lista delle adiacenze la matrice delle adiacenze Ua lista di adiacenze è una lista che mantiene un elenco di nodi, ed ad ogni nodo è collegata una lista di puntatori ai nodi collegati. Una matrice di adiacenze è una matrice N per N, dove N è il numero dei nodi, che mantiene un valore 1 nella cella (a,b) se il nodo a è collegato al nodo b (0 altrimenti). La matrice è simmetrica solo se il grafo non è orientato.

37 Strutture dati GRAFO Indicati con V la cardinalità dell'insieme dei vertici del grafo e con E la cardinalità dell'insieme degli archi del grafo, le due rappresentazioni, quella mediante liste e quella mediante matrice delle adiacenze, richiedono rispettivamente Θ(V+E) e Θ(V2) spazio di memoria. Ognuna delle due rappresentazioni ha alcuni vantaggi: una lista di adiacenze risulta più adatta a rappresentare grafi sparsi o con pochi archi, perciò è facile trovare tutti i nodi adiacenti ad un nodo dato e verificare l'esistenza di connessioni tra nodi; una matrice di adiacenze è invece più indicata per descrivere grafi densi e con molti archi, inoltre rende più facile invertire i grafi e individuarne sottografi.

38 Strutture dati GRAFO Grafo dei collegamenti della
pagina principale di Wikipedia.

39 Strutture dati ALBERO E’ un grafo connesso che non contiene cicli.
Per ogni coppia di nodi esiste un solo cammino che li connette. Se il numero dei nodi è V, il numero di archi è E=V-1. Attraversamento: preorder, postorder, inorder, levelorder. Molto usati sono gli alberi binari: V nodi interni, V+1 nodi esterni V nodi  altezza ~ log2V

40 Strutture dati ALBERO Es. Preorder: 1,2,3,5,8,9,6,10,4,7
Postorder: 2,8,9,5,10,6,3,7,4,1 Inorder: 2,1,8,5,9,3,10,6,7,4 Levelorder: 1,2,3,4,5,6,7,8,9,10

41 Strutture dati HEAP E’ un albero binario i cui nodi
soddisfano la condizione di integrità: La chiave che identifica ciascun nodo deve essere ≥ della chiave dei nodi figli (se esistono). Operazioni: insert() – aggiunge un nuovo nodo replace() – sostituisce la radice corrente remove() – elimina la radice corrente extract() – elimina un nodo change() – sostituisce un nodo NOTA: questo è il max-heap; se nella condizione di integrità usiamo ≤, abbiamo il min-heap

42 Tecniche di progettazione di algoritmi
Per definire un algoritmo è necessario: condurre un'attenta analisi del problema individuare i possibili ingressi precisare le uscite definire completamente e dettagliatamente la sequenza dei passi che portano alla soluzione (è conveniente suddividere il problema in piccoli sottoproblemi)

43 Tecniche di progettazione di algoritmi
Dal particolare all’universale: INDUZIONE Dall’universale al particolare: DEDUZIONE Principio di induzione U={nєN: vale P(n)} dove P(n) è un predicato su N Se 1) si dimostra che vale P(0), cioè 0єU 2) P(n) => P(n+1), cioè nєU => n+1єU allora P(n) vale per ogni n, cioè U coincide con N 1) è detto passo base dell’induzione 2) è detto passo induttivo Es. per tutti gli nєN vale P(n) = n = n(n+1)/2

44 Tecniche di progettazione di algoritmi
Algoritmo ricorsivo contiene richiami a se stesso (ma deve esserci una condizione di terminazione). Es. N! = N(N-1)!

45 Tecniche di progettazione di algoritmi
Divide et Impera I. Si divide il problema (di dimensione n) in a sottoproblemi di dimensione inferiore n/b, operanti su sottoinsiemi disgiunti del set di dati. Questa fase richiede un tempo D(n). II. Si risolvono i sottoproblemi. Questa fase richiede un tempo aT(n/b). III. Si combinano le soluzioni dei sottoproblemi in modo da ottenere la soluzione del problema principale. Questa fase richiede un tempo C(n). NOTA: i sottoproblemi possono essere a loro volta risolti con la tecnica divide et impera (quindi operando in maniera ricorsiva).

46 Tecniche di progettazione di algoritmi
Ricerca esaustiva (metodo “forza bruta”) Consiste nel verificare tutte le soluzioni teoricamente possibili fino a che non si trova quella effettivamente corretta. Il suo principale fattore positivo è che porta sempre a trovare la soluzione corretta, ma è anche vero che è sempre la soluzione più lenta o dispendiosa; viene utilizzato come ultima risorsa sia in crittanalisi che in altre parti della matematica solamente in quei casi dove sia l'unica soluzione conosciuta. Es. Quando su un sistema informatico è possibile un attacco offline (ovvero quando l'attacco si può eseguire su una copia di lavoro locale del sistema da attaccare) si può barattare la velocità di esecuzione con la quantità di risorse necessarie (attacchi "distribuiti“).

47 Tecniche di progettazione di algoritmi
Backtracking (= ritorno all’indietro) E’ una tecnica per trovare soluzioni a problemi in cui devono essere soddisfatti dei vincoli. Con questa tecnica si considerano successivamente tutte le possibili soluzioni, scartando man mano le condizioni che non soddisfano i vincoli. Una tecnica classica consiste nell'esplorazione di strutture ad albero e tenere traccia di tutti i nodi e i rami visitati in precedenza, in modo da poter tornare indietro al più vicino nodo che conteneva un cammino ancora inesplorato nel caso che la ricerca nel ramo attuale non abbia successo. Es. Programmi per giocare a scacchi: generano tutte le mosse possibili per una profondità di N mosse a partire da quella attuale e poi esaminano con il backtracking le varie alternative, selezionando alla fine quella migliore.

48 Tecniche di progettazione di algoritmi
Greedy Ad ogni passo viene scelta la soluzione che è ottima o “greediest” (più golosa) localmente, senza preoccuparsi degli effetti successivi di tale scelta. La soluzione locale scelta può dipendere dalle scelte precedenti. Es. Il problema "Dai il minor numero di monete di resto utilizzando monete da 100, 10, 1 Euro" è un problema risolvibile tramite un algoritmo di tipo greedy: ad ogni passo viene controllato il resto ancora da dare e si aggiunge la moneta con valore maggiore possibile. Quindi per dare un resto di 112 Euro la macchina farà cadere in sequenza una moneta da 100, poi 10, poi 1 e poi 1 Euro per un totale di 4 monete.

49 Tecniche di progettazione di algoritmi
Programmazione dinamica Vengono risolti tutti i possibili sottoproblemi (a priori, in generale, non si sa quale sia il set di sottoproblemi migliore). Es. Problema dello zaino (knapsack problem): - Zaino di capacità M - N tipi di oggetti (ogni tipo ha un suo valore e spazio occupato) Per ogni i da 1 a M, troviamo Cost(i) = max valore ottenibile per uno zaino di capacità i Best(i) = ultimo oggetto messo nello zaino di capacità i per avere Cost(i) => Best(M) è nello zaino, e anche Best[M – size{Best(M)}], ecc. Costo dell’algoritmo: O(MN)

50 Algoritmi di ordinamento
Supponiamo di avere memorizzato un array di N elem, dove il tipo elem è un record contenente due campi: informazione e chiave. type elem = record informazione: tipo_informazione; chiave: int; end; elem a[N]; Vediamo una serie di metodi per ordinare le componenti dell’array secondo l’ordine crescente del campo chiave.

51 Algoritmi di ordinamento
BUBBLE SORT Confronta ogni elemento con il successivo e li scambia (swap) se necessario. Ripete finchè non si ha una passata senza swap. Complessità computazionale: O(N2)

52 Algoritmi di ordinamento
INSERTION SORT Parte dall’elemento in seconda posizione e lo confronta con il primo. Se necessario li scambia. Passa all’elemento in terza posizione e lo confronta con i precedenti, inserendolo se necessario in prima posizione, o tra il primo e il secondo. Prosegue con gli elementi nelle posizioni successive, inserendoli eventualmente tra l’insieme dei predecessori (≤) e quello dei predecessori (>). Complessità computazionale: O(N2)

53 Algoritmi di ordinamento
SELECTION SORT Per ogni i=0..N-1 trova il più piccolo elemento di a[i+1..N-1] e lo scambia con a[i]. Complessità computazionale: O(N2) Es.

54 Algoritmi di ordinamento
SHELL SORT Divide la lista in sottoinsiemi* e li ordina singolarmente con insertion sort. Raggruppa i sottoinsiemi in insiemi più grandi. Ordina questi ultimi con insertion sort. Ripete fino ad avere un unico insieme ordinato di cardinalità N. Complessità computazionale: O(N2) *: gli elementi dei sottoinsiemi non sono elementi contigui dell’insieme complessivo; se i sottoinsiemi sono M, gli elementi sono presi uno ogni M dall’insieme complessivo

55 Algoritmi di ordinamento
HEAP SORT for (i = 1; i ≤ N; i++) heap.insert(a[i]); For (i = N; i ≥ 1; i--) a[i] = heap.remove(); E’ più lento degli algoritmi MERGESORT e QUICKSORT, ma non usa array multipli e non è troppo ricorsivo, per cui per grandi quantità di dati è la soluzione migliore. Complessità computazionale: O(NlogN)

56 Algoritmi di ordinamento
MERGE SORT Divide l’array in due parti e le copia in due array distinti. Continua a dividere ricorsivamente. Quando restano array di 2 elementi ciascuno, li ordina. Rimette insieme gli array, ordinandoli dopo ciascuna fusione. Complessità computazionale: O(NlogN)

57 Algoritmi di ordinamento
QUICKSORT Sceglie un elemento dell’array (è il PIVOT). Divide l’array in due gruppi: elementi < PIVOT elementi > PIVOT Ripete ricorsivamente per ciascun sottogruppo. Complessità computazionale: O(NlogN)


Scaricare ppt "Algoritmi e strutture dati"

Presentazioni simili


Annunci Google