Modelli di Calcolo & Metodologie di Analisi Modelli di Calcolo (1/4) Turing Machine (Alan Turing, 1936): S a b b a • Nastro infinito bidirezionale • Ogni cella può contenere un simbolo di un certo alfabeto finito • Passo computazionale: lettura/scrittura di una cella, spostamento della testina, variazione dello stato Modello di riferimento nello studio della calcolabilità e della complessità computazionale Modelli di Calcolo (2/4) Random Access Machine (1963): ACC Nastro di input R1 R2 R3 Programma PC R4 R5 Nastro di output • Ogni registro/cella può contenere un intero grande a piacere • Passo computazionale: operazione di I/O, operazione logico-aritmetica, lettura/scrittura in memoria Modelli di Calcolo (3/4) La Random Access Machine (RAM) permette operazioni su interi arbitrariamente grandi in un unico passo computazionale (misura di costo uniforme): ipotesi poco realistica Ipotesi più realistica: il costo delle operazioni dipende dalla dimensione degli operandi coinvolti (misura di costo logaritmico) Esempio: qual è il tempo di esecuzione? int x=1; for (int i=1; i<=n; i++) x=x*2; Criterio di costo uniforme: ~n Criterio di costo logaritmico: ~n^2 Modelli di Calcolo (4/4) Approccio alternativo: limiteremo la dimensione degli interi rappresentabili a c · log n bit (dove c è un’opportuna costante ed n è la dimensione dell’istanza del problema). Gli algoritmi che studieremo saranno quindi tutti implementabili su macchine RAM con interi di dimensione <=n^c Altri modelli computazionali: Parallel Random Access Machine (PRAM), External Memory Model Esercizio: costo di findMax? int findMax (int[] v) { int currentMax=v[0]; for (int i=1; i<v.length; i++) if (v[i]>currentMax) currentMax=v[i]; return currentMax; } • Dipende dalla lunghezza dell’array (dimensione dell’input) • Dipende dalla configurazione dei dati di input (caso peggiore: elementi ordinati in maniera crescente; caso migliore: massimo in prima posizione) Costo dei seguenti metodi Java? public static int[] metodo1 (int[] V) { int[] W=new int[V.length]; for (int i=0; i<V.length; i++) W[i]=V[i]; return W; } public static void metodo2 (int n) { for (int i=1; i<=n*n; i++) System.out.print ((i * i) + " "); System.out.print ("\n"); } public static void metodo3 (int n) { for (int i=1; i<=n; i++) for (int j=1; j<=i; j++) System.out.print ((j) + " "); System.out.print ("\n"); } Fattoriale public class Fattoriale { public static int fattoriale (int n) { if (n==1) return 1; else return n*fattoriale (n-1); } public static void main (String[] args) { System.out.println (fattoriale (5)); } } isPrime public class Primes { public static boolean isPrime (int n) { if (n<2) return false; int i=n-1; while (i>1) { if ( (n % i)==0 ) return false; i--; } return true; } Strutture dati elementari (liste, pile, code, alberi) Liste in Java • array (puo’ contenere tipi di dato primitivi, ha dimensione fissa, molto efficiente) • classe Vector (contiene riferimenti ad Object, gestione flessibile della memoria, molti metodi a disposizione, meno efficiente, accesso sincronizzato) • classe ArrayList (lista realizzata con array, fornisce anche metodi per ridimensionare l’array stesso) • classe LinkedList (lista doppiamente collegata) • “record & puntatori” Implementazione del tipo Coda in Java (1/4) public class MyQueueNode { public Object info; public MyQueueNode next=null; public class MyQueue { private MyQueueNode head; private MyQueueNode tail; } //constructor... public MyQueue () { head=null; tail=null; } public boolean isEmpty () { return head==null; } (continua…) Implementazione del tipo Coda in Java (2/4) public void enqueue (Object el) { MyQueueNode myQN=new MyQueueNode (); myQN.info=el; //if queue empty... if (isEmpty ()) { head=myQN; tail=myQN; } else { //queue not empty... tail.next=myQN; tail=tail.next; } } (continua…) Implementazione del tipo Coda in Java (3/4) public Object dequeue () { if (isEmpty ()) return null; Object el=head.info; //if only one node... if (head==tail) { head=null; tail=null; } else { //more than one node... head=head.next; } return el; } } Implementazione del tipo Coda in Java (4/4) public class ProvaMyQueue { public static void main (String[] args) { MyQueue q=new MyQueue (); q.enqueue (10); q.enqueue (15); q.enqueue (20); while (!q.isEmpty ()) { System.out.print (q.dequeue ()+" "); } System.out.print ("\n"); } } BFS in Java (1/4) public class BTreeNode { char info; BTreeNode leftChild=null; BTreeNode rightChild=null; BTreeNode parent=null; public class BTree { BTreeNode root; //constructor... public BTree (BTreeNode r) { } root=r; } (continua…) BFS in Java (2/4) public void bfsBTree () { MyQueue q=new MyQueue (); q.enqueue (root); while (!q.isEmpty ()) { BTreeNode u=(BTreeNode) q.dequeue (); if (u!=null) { //visits node... System.out.print (u.info + " "); q.enqueue (u.leftChild); q.enqueue (u.rightChild); } } System.out.print ("\n"); } } BFS in Java (3/4) public class ProvaBFS { public static void main (String[] args) { //creates nodes... BTreeNode a=new BTreeNode (); BTreeNode l=new BTreeNode (); BTreeNode b=new BTreeNode (); BTreeNode e=new BTreeNode (); BTreeNode r=new BTreeNode (); BTreeNode o=new BTreeNode (); //sets up tree... a.info='A'; l.info='L'; b.info='B'; e.info='E'; r.info='R'; o.info='O'; (continua…) BFS in Java (4/4) a.leftChild=l; a.rightChild=b; l.leftChild=e; l.rightChild=r; b.rightChild=o; BTree bintree=new BTree (a); //runs bfs... bintree.bfsBTree (); } } Implementazione del tipo Pila in Java (1/4) public class MyStackNode { public Object info; public MyStackNode next=null; } public class MyStack { private MyStackNode head; //constructor... public MyStack () { head=null; } public boolean isEmpty () { return head==null; } (continua…) Implementazione del tipo Pila in Java (2/4) public Object top () { if (isEmpty ()) return null; else return head.info; } public Object pop () { if (isEmpty ()) return null; Object el=head.info; head=head.next; return el; } (continua…) Implementazione del tipo Pila in Java (3/4) public void push (Object el) { MyStackNode mySN=new MyStackNode (); mySN.info=el; mySN.next=head; head=mySN; } } Implementazione del tipo Pila in Java (4/4) public class ProvaMyStack { public static void main (String[] args) { MyStack s=new MyStack (); s.push (10); s.push (15); s.push (20); while (!s.isEmpty ()) { System.out.print (s.top ()+" "); System.out.print (s.pop ()+" "); } System.out.print ("\n"); } } Correttezza di una stringa parentetica public static boolean isCorrect (String s) { MyStack myS=new MyStack (); String token=new String ("token"); int i=0; while (i<s.length ()) { if ( s.charAt (i) == '(‘ ) myS.push (token); if (s.charAt (i) == ')‘ ) { if (myS.isEmpty ()) return false; else myS.pop (); } i++; } if (myS.isEmpty ()) return true; else return false; } Ordinamento Implementazione di MergeSort in Java (1/4) public class MergeSort { public static void mergeSort (int[] A, int i, int f) { if (i >= f) return; int m=(i + f) / 2; //recursive calls... mergeSort (A, i, m); mergeSort (A, m+1, f); merge (A, i, m, f); } (continua…) Implementazione di MergeSort in Java (2/4) private static void merge (int[] A, int i1, int f1, int f2) { //auxiliary array... int[] X=new int [ f2 - i1 + 1]; //stores initial i1 value... int p=i1; int i=0; int i2=f1+1; //while both subsequences still have items left... while (i1 <= f1 && i2 <= f2) { if (A[i1] <= A[i2]) { X[i]=A[i1]; i++; i1++; } else { X[i]=A[i2]; i++; i2++; } } (continua…) Implementazione di MergeSort in Java (3/4) //if subseq #2 ran out of items... if (i1<=f1) { while (i1<=f1) { X[i]=A[i1]; i++; i1++; } } else {//subseq #1 ran out of items... while (i2<=f2) { X[i]=A[i2]; i++; i2++; } } //copies auxiliary vector onto input vector... for (int q=0; q<X.length; q++) { A[p]=X[q]; p++; } } } Implementazione di MergeSort in Java (4/4) public class ProvaMergeSort { public static void main (String[] args) { int v[]={25, 34, 128, -12, -128, 4000, 2, 21}; MergeSort.mergeSort (v, 0, v.length-1); for (int i=0; i<v.length; i++) System.out.print (v[i]+" "); System.out.print ("\n"); } } Alberi binari di ricerca (BST) Concetto di chiave Insieme di campi di un record che caratterizza il record stesso Es. { <nome>, <cognome>, <data di nascita>, <indirizzo>, <codice fiscale>, <stato civile>, <professione> } Possibili chiavi: {<nome>, <cognome>, <data di nascita>}, {<codice fiscale>} Interfaccia Comparable in Java Spesso le chiavi sono implementate in Java come riferimenti a Comparable, se occorre stabilire un ordinamento tra le chiavi. Implementata da moltissime classi. Impone un solo metodo: public interface Comparable<T> int compareTo (T o) Restituisce: • un intero negativo se l’oggetto di invocazione è minore di o • zero se l’oggetto di invocazione è uguale ad o • un intero positivo se l’oggetto di invocazione è maggiore di o BST in Java public class BSTNode { int key; BSTNode leftChild; BSTNode rightChild; BSTNode parent; } public class BST { private BSTNode root; //constructor... public BST () { root=null; } //returns root node... public BSTNode getRoot () { return root; } (continua…) BST in Java (operazione ‘search’) //returns node with key==k (or null otherwise)... public BSTNode search (int k) { BSTNode v=root; while (v!=null) { if (v.key==k) return v; else { if (k<v.key) v=v.leftChild; else v=v.rightChild; } } return null; } BST in Java (operazione ‘insert’) //inserts k into BST... public void insert (int k) { BSTNode v=root; BSTNode prev=null; //creates new bst node... BSTNode myNode=new BSTNode (); myNode.key=k; myNode.leftChild=null; myNode.rightChild=null; myNode.parent=null; while (v!=null) { prev=v; if (v.key<k) v=v.rightChild; else v=v.leftChild; if (root==null) { } root=myNode; return; if (prev.key<k) prev.rightChild=myNode; else prev.leftChild=myNode; } (continua…) myNode.parent=prev; } BST in Java (operazioni ‘max’ e ‘min’) //finds node with max key in subtree... public static BSTNode max (BSTNode t) { BSTNode v=t; while (v.rightChild!=null) v=v.rightChild; return v; } //finds node with min key in subtree... public static BSTNode min (BSTNode t) { BSTNode v=t; while (v.leftChild!=null) v=v.leftChild; return v; } BST in Java (operazione ‘pred’) //finds node with pred key in BST... public static BSTNode pred (BSTNode u) { if (u.leftChild!=null) return max (u.leftChild); while (u.parent != null && u.parent.leftChild == u) u=u.parent; return u.parent; } Alberi AVL Alberi di Fibonacci Costruzione ricorsiva di un albero di Fibonacci: T0 T1 T2 T3 T4 Sono gli alberi bilanciati piu’ vicini alla condizione di non bilanciamento (ogni nodo interno ha fattore di bilanciamento pari a 1) Dim. altezza AVL tree=O(log n) Sia Th un albero di Fibonacci di altezza h con nh nodi. E’ possibile dimostrare che h=Θ(log nh) (Adel’son-Vel’skii e Landis) Poiche’ quelli di Fibonacci sono gli alberi AVL con minor numero di nodi, per ogni altro albero bilanciato di altezza h con n nodi sara’ necessariamente h=O(log n) Tavole Hash Hashing doppio: implementazione Java (1/5) public class MyHashTable { MyHashItem[] theTable; public class MyHashItem { public MyHashTable (int numSlots) { int key; Object elem; //creates table... theTable=new MyHashItem[numSlots]; } //creates table items... for (int i=0; i<theTable.length; i++) theTable[i]=new MyHashItem (); //sets all elements to null... for (int i=0; i<theTable.length; i++) theTable[i].elem=null; } (continua…) Hashing doppio: implementazione Java (2/5) public int h1 (int k) { return (k % theTable.length); } public int h2 (int k) { return (k % (theTable.length - 1)) + 1; } public int c (int k, int i) { return ((h1(k) + i * h2(k)) % theTable.length); } (continua…) Hashing doppio: implementazione Java (3/5) public void insert (int k, Object e) { for (int i=0; i<theTable.length; i++) { if (theTable[c (k, i)].elem==null) { theTable[c (k, i)].key=k; theTable[c (k, i)].elem=e; return; } } System.out.println ("ERROR: table is full!"); } (continua…) Hashing doppio: implementazione Java (4/5) public Object search (int k) { for (int i=0; i<theTable.length; i++) { if (theTable[c (k, i)].elem==null) return null; if (theTable[c (k, i)].key==k) return theTable[c (k, i)].elem; } return null; } (continua…) Hashing doppio: implementazione Java (5/5) public static void main (String[] args) { //creates hash table... MyHashTable myHS=new MyHashTable (11); String s8="8"; String s10="10"; String s13="13"; myHS.insert (8, s8); myHS.insert (10, s10); myHS.insert (13, s13); for (int i=0; i<15; i++) { String o=(String) myHS.search (i); if (o!=null) System.out.println (o); } String aux="*"; for (int i=20; i<30; i++) myHS.insert (i, aux); } } Grafi & Visite di grafi Nuova versione di MyQueue per valori di tipo int (1/3) public class MyQueueNode { public int info; public MyQueueNode next=null; public class MyQueue { private MyQueueNode head; private MyQueueNode tail; } //constructor... public MyQueue () { head=null; tail=null; } public boolean isEmpty () { return head==null; } (continua…) Nuova versione di MyQueue per valori di tipo int (2/3) public void enqueue (int el) { MyQueueNode myQN=new MyQueueNode (); myQN.info=el; //if queue empty... if (isEmpty ()) { head=myQN; tail=myQN; } else { //queue not empty... tail.next=myQN; tail=tail.next; } } (continua…) Nuova versione di MyQueue per valori di tipo int (3/3) public int dequeue () { if (isEmpty ()) return -1; int el=head.info; //if only one node... if (head==tail) { head=null; tail=null; } else { //more than one node... head=head.next; } return el; } } Matrice di adiacenza & BFS (1/5) public class BFSadjMAT { private boolean[][] adjMAT; private boolean[] marks; private int numNodes; //costo: O(n^2) public BFSadjMAT (int numNodes) { this.numNodes=numNodes; adjMAT=new boolean[numNodes][numNodes]; for (int i=0; i<numNodes; i++) for (int j=0; j<numNodes; j++) adjMAT[i][j]=false; marks=new boolean[numNodes]; } (continua…) Matrice di adiacenza & BFS (2/5) //costo: O(1) public void addEdge (int endPoint0, int endPoint1) { if (endPoint0 >= 0 && endPoint0 < numNodes && endPoint1 >= 0 && endPoint1 < numNodes) adjMAT[endPoint0][endPoint1]=true; else System.out.println ("addEdge: Endpoint id out of range!"); } //costo: O(1) public void removeEdge (int endPoint0, int endPoint1) { if (endPoint0 >= 0 && endPoint0 < numNodes && endPoint1 >= 0 && endPoint1 < numNodes) adjMAT[endPoint0][endPoint1]=false; else System.out.println ("removeEdge: Endpoint id out of range!"); } (continua…) Matrice di adiacenza & BFS (3/5) //prints ids of visited nodes... //costo: O(n^2) public void BFS (int sourceEndPoint) { if (sourceEndPoint<0 || sourceEndPoint>=numNodes) { System.out.println ("BFS: invalid source!"); return; } //unmarks all vertices... for (int i=0; i<numNodes; i++) marks[i]=false; //creates queue... MyQueue F=new MyQueue (); //enqueues souce node... F.enqueue (sourceEndPoint); //marks source node... marks[sourceEndPoint]=true; System.out.print (sourceEndPoint+" "); (continua…) Matrice di adiacenza & BFS (4/5) //main loop... while (!F.isEmpty ()) { //dequeues current vertex... int u=F.dequeue (); for (int i=0; i<numNodes; i++) { if (adjMAT[u][i]==true && marks[i]==false) { F.enqueue (i); marks[i]=true; System.out.print (i + " "); } } } System.out.print ("\n"); } (continua…) Matrice di adiacenza & BFS (5/5) public static void main (String[] args) { BFSadjMAT myGraph=new BFSadjMAT (7); myGraph.addEdge (0, 1); myGraph.addEdge (1, 2); myGraph.addEdge (2, 0); myGraph.addEdge (0, 3); myGraph.addEdge (3, 2); myGraph.addEdge (2, 4); myGraph.addEdge (4, 5); myGraph.addEdge (2, 5); myGraph.addEdge (5, 3); myGraph.addEdge (3, 6); myGraph.BFS (1); System.out.print ("\n"); } } Connettivita’ & Componenti Connesse (grafi non orientati) algoritmo isConnected (grafo G) → boolean scegli arbitrariamente un vertice s in G T ← visitaGenerica (s) if (T ha n nodi) return true else return false O(m+n) (liste di adiacenza) O(n) Le componenti connesse di G possono essere determinate modificando l’algoritmo precedente, in modo da iniziare una nuova visita da un vertice non ancora esplorato (se c’e’). Tempo di esecuzione: O(m+n). Componenti fortemente connesse (grafi orientati) Per calcolare la componente fortemente connessa contenente il vertice x si puo’ procedere come segue: 1. Si calcola l’insieme D(x) dei vertici raggiungibili da x. E’ sufficiente una visita generica a partire da x. Costo computazionale: O(m+n). 2. Si calcola l’insieme A(x) dei vertici da cui e’ possibile raggiungere x. Occorre invertire la direzione di tutti gli archi del grafo, e poi eseguire una nuova visita partendo da x. Costo computazionale: O(m+n). A questo punto sara’ sufficiente individuare tutti i vertici che sono contemporaneamente in D(x) ed A(x), cioe’ che sono sia discendenti che antenati di x. Componenti fortemente connesse (grafi orientati) Applicando per n volte l’algoritmo descritto nella slide precedente, si ottengono le componenti fortemente connesse dell’intero grafo. Il costo computazionale sara’ O(mn+n^2) NB. Esiste un algoritmo migliore (che sfrutta le proprieta’ della visita DFS) in grado di calcolare le componenti fortemente connesse di un grafo in tempo O(m+n).