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).