Esempio: rappresentare gli insiemi Problema: rappresentare gli insiemi Vedremo che la scelta della rappresentazione per implementarli non è così ovvia come era stato per i numeri razionali In realtà ci sono parecchie rappresentazioni possibili, che differiscono tra loro sostanzialmente Esempio: rappresentare gli insiemi Informalmente un insieme è una collezione di oggetti distinti Utilizziamo la metodologia della data abstraction, specificando le operazioni che vengono usate sugli insiemi union-set : unione di due insiemi intersection-set : intersezione di due insiemi element-of-set? : test se un elemento appartiene ad un insieme adjoin-set : aggiunge un elemento ad un insieme Dal punto di vista della data abstraction siamo liberi di scegliere qualunque rappresentazione implementi queste operazioni inmaniera consistente 1
Insiemi come liste non ordinate Una possibile rappresentazione è come una lista di elementi in cui nessun elemento compare più di una volta L insieme vuoto è rappresentato dalla lista vuota S = {1,7,5,3,11,9} 1 42 4 3 l'ordine non importa come una lista (1 7 5 3 11 9) Insiemi come liste non ordinate element-of-set? In questa rappresentazione element-ofset? è simile a memq. Usa però equal? invece di eq? perché posso avere insiemi di insiemi (define (element-of-set? x set) (cond ((null? set) #f) ((equal? x (car set)) #t) (else (element-of-set? x (cdr set))))) 2
Insiemi come liste non ordinate adjoin-set Possiamo usare la procedura element-ofset? per scrivere adjoin-set (define (adjoin-set x set) (if (element-of-set? x set) set (cons x set))) Insiemi come liste non ordinate intersection-set Possiamo utilizzare uno schema ricorsivo (define (intersection-set set1 set2) (cond ((or (null? set1) (null? set2)) '()) ((element-of-set? (car set1) set2) (cons (car set1) (intersection-set (cdr set1) set2))) (else (intersection-set (cdr set1) set2)))) 3
Insiemi come liste non ordinate union-set Vengono in mente due implementazioni Una di tipo spaghetti-programming (define (union-set set1 set2) (cond ((null? set1) set2) ((null? set2) set1) ((element-of-set? (car set1) set2) (union-set (cdr set1) set2)) (else (cons (car set1) (union-set (cdr set1) set2))))) L altra più semplice usando accumulate (define (union-set set1 set2) (accumulate adjoin-set set2 set1)) Insiemi come liste non ordinate Considerazioni sull efficienza Vediamo la complessità in temini di numero di passi che impiegano le nostre operazioni Usiamo spesso element-of-set? Può dover scandire tutto l insieme, nel caso peggiore che l oggetto sia l ultimo elemento. Il suo ordine di crescita è Θ(n) adjoin-set Usa element-of-set? e anch essa ha ordine di crescita Θ(n) 4
Insiemi come liste non ordinate intersection-set Esegue un element-of-set? su set2 per ogni elemento di set1 Il numero di passi cresce quindi come il prodotto delle dimensioni degli insiemi coinvolti, ed è quindi Θ(n 2 ) per due insiemi di lunghezza n Lo stesso vale per union-set Insiemi come liste ordinate Un modo per migliorare l efficienza è di cambiare rappresentazione Gli elementi sono rappresentati in liste ordinate in ordine crescente Abbiamo bisogno di un predicato che ci dica, dati due oggetti, qual è il più grande Ex. Per i simboli => ordinamento lessicografico Per semplicità nei nostri esempi useremo i numeri (quindi useremo <) S = {1,7,5,3,11,9} come una lista ordinata (1 3 5 7 9 11)! 5
Insiemi come liste ordinate Un primo vantaggio lo vediamo subito in elementof-set? Non dobbiamo più scandire sempre tutta la lista (define (element-of-set? x set) (cond ((null? set) #f) ((= x (car set)) #t) ((< x (car set)) #f) (else (element-of-set? x (cdr set))))) Nel caso medio ci aspettiamo di esaminare solo la metà degli elementi. Il numero medio di passi sarà n/2 (non è in granchè, Θ(n/2)) Insiemi come liste ordinate Dove otteniamo un sensibile miglioramento è con le operazioni di intersezione e unione (define (intersection-set set1 set2) (if (or (null? set1) (null? set2)) '() (let ((x1 (car set1)) (x2 (car set2))) ;;confrontiamo i primi elementi di set1 e set2 (cond ((= x1 x2) ;;se sono uguali, poniamo l elemento nell intersezione (cons x1 (intersection-set (cdr set1) (cdr set2)))) ((< x1 x2) (intersection-set (cdr set1) set2)) ((< x2 x1) (intersection-set set1 (cdr set2))))))) se sono diversi, sfruttiamo l ordinamento. Se x1 < x2, allora siccome x2 è il più piccolo elemento in set2 questo significa che x1 non può comparire in set2. Quindi x1 non può stare nell intersezione 6
Insiemi come liste ordinate Per calcolare il numero di passi di intersection, osserviamo che non facciamo più uso di element-set? A ciascun passo riduciamo il problema a calcolare l intersezione di insiemi più piccoli, rimuovendo il primo elemento di set1 o di set2 o di entrambi Il numero dipassi sarà al più la somma delle dimensioni di set1 e set2 (e non il prodotto come nelle liste non ordinate) Θ(n + m) = Θ(dim(set1)+dim(set2)) Insiemi come liste ordinate (define (adjoin-set x set) (cond ((null? set) (list x)) ((= x (car set)) set) ((< x (car set))(cons x set)) (else (cons (car set) (adjoin-set x (cdr set)))))) (define (union-set set1 set2) (cond ((null? set1) set2) ((null? set2) set1) (else (let ((x1 (car set1)) (x2 (car set2))) (cond ((= x1 x2) (cons x1 (union-set (cdr set1)(cdr set2)))) ((< x1 x2) (cons x1 (union-set (cdr set1) set2))) ((< x2 x1) (cons x2 (union-set set1 (cdr set2))))))))) 7
Insiemi come alberi binari Possiamo fare ancora meglio, rappresentando gli insiemi come alberi binari In particolare, ciascun nodo dell albero ha Una etichetta o elemento del nodo Un puntatore sinistro ad elementi più piccoli Un puntatore destro a elementi più grandi Vediamo alcune rappresentazioni dell insieme {1, 3, 5, 7, 9, 11} 7 3 9 1 3 5 7 9 5 3 9 1 5 11 11 1 7 11 Insiemi come alberi binari Quelle viste sono tutte e tre rappresentazioni corrette secondo i requisiti che abbiamo dato Un vantaggio di questa rappresentazione sta nella ricerca di un elemento Se l albero è bilanciato, ad ogni passo riduciamo il problema della metà Questa è una caratteristica che contraddistingue la crescita Θ(log n) 8
Insiemi come alberi binari Possiamo rappresentare questi alberi usando le liste. Ciascun nodo è una lista di tre elementi: L elemento del nodo Il sottoalbero sinistro Il sottoalbero destro Quando un sottoalbero è la lista vuota significa ovviamente che non c è nessun sottoalbero connesso Insiemi come alberi binari Selettori e costruttore (define (entry tree) (car tree)) (define (left-branch tree) (cadr tree)) (define (right-branch tree) (caddr tree)) (define (make-tree entry left right) (list entry left right)) 7 (make-tree 7 (make-tree 3 (make-tree 1 nil nil) (make-tree 5 nil nil)) (make-tree 9 nil (make-tree 11 nil nil))) (7 (3 (1 () ()) (5 () ())) (9 () (11 () ()))) 3 9 1 5 11 9
Insiemi come alberi binari Possiamo definire element-of-set? (define (element-of-set? x set) (cond ((null? set) #f) ((= x (entry set)) #t) ((< x (entry set)) (element-of-set? x (left-branch set))) ((> x (entry set)) (element-of-set? x (right-branch set))))) Insiemi come alberi binari Anche aggiungere un elemento è implementato in maniera simile e richiede Θ(log n) passi (define (adjoin-set x set) (cond ((null? set) (make-tree x '() '())) ((= x (entry set)) set) ((< x (entry set)) (make-tree (entry set) (adjoin-set x (left-branch set)) (right-branch set))) ((> x (entry set)) (make-tree (entry set) (left-branch set) (adjoin-set x (right-branch set)))))) 10
Insiemi come alberi binari Le prestazioni dichiarate in Θ(log n), valgono solo nel caso di alberi bilanciati: cioè i sottoalberi destro e sinistro hanno all incirca lo stesso numero di elementi Ma ogni volta che facciamo un adjoin-set provochiamo uno sbilanciamento Se aggiungiamo elementi in ordine casuale, l albero rimane bilanciato, ma questo non è garantito 1 Se inseriamo elementi da 1 a 4 in ordine: Un modo di risolvere il problema è quello di ribilanciare periodicamente l albero Ci sono comunque molti modi di risolvere il problema, molti dei quali richiedono la proggettazione di nuove strutture dati (Btree, red-black-tree) 2 3 4 Insiemi come predicati Quando è possibile scrivere un predicato di appartenenza ad un insieme, possiamo rappresentare un insieme tramite quella procedura (define S1 (lambda (el) (and (integer? el) (odd? el) (< el 13) (> el 0)))) Es. S1 è l insieme di tutti gli interi compresi tra 0 e 13 11
Insiemi come predicati (2) (define (element-of-set? x Set) (Set x)) (define (union S1 S2) (lambda (el) (or (S1 el) (S2 el)))) (define (intersection S1 S2) (lambda (el) (and (S1 el) (S2 el)))) (define (adjoin x Set) ;; {x} Set (if (element-of-set? x Set) Set (union Set (lambda (el) (equal? x el)))) (define empty-set (lambda (el) #f)) In questa rappresentazione è un problema definire l uguaglianza tra insiemi. Non c è un modo generale di verificare che due procedure ritornano sempre gli stessi valori 12