La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

ALMA MATER STUDIORUM: Universita' di Bologna

Presentazioni simili


Presentazione sul tema: "ALMA MATER STUDIORUM: Universita' di Bologna"— Transcript della presentazione:

1 ALMA MATER STUDIORUM: Universita' di Bologna
PAS Renzo Davoli ALMA MATER STUDIORUM: Universita' di Bologna Bologna, 5 giugno 2014

2 Il percorso di oggi... Algoritmi di ricerca Lineare, binaria, hashing,
Cenni su altri algoritmi di ricerca Esercizi Informatica e diritto di autore

3 Algoritmi di ricerca (searching algorithms)
Scopo di questi algoritmi e' di trovare un elemento in una collezione Puo' essere usato per verificare se un elemento esiste o meno alla collezione (valore di ritorno booleano) L'elemento puo' essere una chiave di accesso ad altre informazioni. Dal punto di vista algoritmico non c'e' differenza: una volta trovato l'elemento o si restituisce True o l'informazione associata.

4 Ricerca lineare: Funziona con strutture dati ad accesso sequenziale
Non richiede che I dati siano ordinati Vengono scanditi gli elementi ad uno ad uno, ci si ferma quando viene trovato l'elemento cercato o se sono stati scanditi tutti gli elementi

5 In python import random def linearsearch(x,seq):
for i in range(len(seq)): if seq[i] == x: return True return False if __name__=="__main__": test=list(range(0,256,2)) random.shuffle(test) while True: n=int(input("numero da cercare: ")) print("C'e'" if linearsearch(n,test) else "non c'e'")

6 Complessita' Computazionale della Ricerca Lineare
Se N e' la lunghezza della sequenza, quanti confronti vengono fatti? Nel caso ottimo: 1 Nel caso pessimo: N Nel caso medio: N/2

7 Verifichiamo statisticamente i valori della complessita'
import random def contaconfronti(): contaconfronti.contatore+=1 contaconfronti.contatore=0 def linearsearch(x,seq): for i in range(len(seq)): contaconfronti() if seq[i] == x: return True return False if __name__=="__main__": test=list(range(0,256,2)) random.shuffle(test) ntest=10000 for i in range(ntest): n=random.randint(0,128)*2 linearsearch(n, test) print("media confronti (elementi esistenti)=",contaconfronti.contatore/ntest) n=random.randint(0,256) print("media confronti=",contaconfronti.contatore/ntest)

8 Ecco I risultati: media confronti (elementi esistenti)= media confronti= media confronti (elementi esistenti)= media confronti= media confronti (elementi esistenti)= media confronti= La lista contiene I numeri pari da 0 a 254. (128 elementi) In media impiega 64 passi a cercare un elemento esistente. Scegliendo un numero a caso fra 0 e 255 la probabilita' che un elemento cercato sia esistente e' 50% In media impiega 64 passi per quelli esistenti e 128 per quelli assenti, la media e' 96.

9 Tempo di elaborazione import random import timeit
def linearsearch(x,seq): for i in range(len(seq)): if seq[i] == x: return True return False def search(test,maxrange): n=random.randint(0,maxrange-1) linearsearch(n,test) if __name__=="__main__": for logmax in range(1,10): rmax=2**logmax test=list(range(0,rmax,2)) random.shuffle(test) time=timeit.timeit( stmt="__main__.search(__main__.test,__main__.rmax)", setup="import __main__",number=10000) print(rmax,time)

10 Ecco I risultati: Viene calcolato il tempo di esecuzione per sequenze da 2 a 512 elementi

11 Grafico Con l'aiuto di un foglio elettronico ecco il grafico x-y del tempo necessario a trovare un elemento al variare del numero di elementi:

12 Ricerca binaria: E' detta anche dicotomica
Funziona con strutture dati ad accesso diretto (occorre poter accedere direttamente a ogni elemento dato l'indice nella struttura dati) Richiede che I dati siano ordinati Si confronta l'elemento cercato con quello centrale: Se e' quello cercato: eureka Se quello cercato e' minore si ripete la ricerca nella sottosequenza degli elementi successivi a quello centrale Altrimenti la ricerca viene ripetuta usando la sottosequenza degli elementi precedenti.

13 In Python import random def binarysearch(x,seq): if seq:
half=len(seq)//2 if x == seq[half]: return True else: if x > seq[half]: return binarysearch(x,seq[half+1:]) return binarysearch(x,seq[:half]) return False if __name__=="__main__": test=list(range(0,256,2)) while True: n=int(input("numero da cercare: ")) print("C'e'" if binarysearch(n,test) else "non c'e'")

14 Complessita' Computazionale della Ricerca Dicotomica
Se N e' la lunghezza della sequenza quante chiamate ricorsive vengono fatte? Nel caso ottimo: 1 Nel caso pessimo: log2(N) Nel caso medio (elementi esistenti): log2(N)-1

15 Una versione piu' efficiente (Non copia le sequenze)
import random def binarysearch(x,seq): def rseq(lo,hi): if lo<=hi: n=(lo+hi)//2 if x==seq[n]: return True else: if x > seq[n]: return rseq(n+1,hi) return rseq(lo,n-1) return False return rseq(0,len(seq)-1) if __name__=="__main__": test=list(range(0,256,2)) while True: n=int(input("numero da cercare: ")) print("C'e'" if binarysearch(n,test) else "non c'e'")

16 Proviamo a stimare il tempo di esecuzione
import random import timeit def binarysearch(x,seq): # e' quella descritta prima... def search(test,maxrange): n=random.randint(0,maxrange-1) binarysearch(n,test) if __name__=="__main__": for logmax in range(1,10): rmax=2**logmax test=list(range(1,rmax,2)) random.shuffle(test) time=timeit.timeit( stmt="__main__.search(__main__.test,__main__.rmax)", setup="import __main__",number=10000) print(rmax,time)

17 Grafico: Come prima con un foglio elettronico e' stato possibile disegnare il grafico: ascisse=numero di elementi della sequenza, ordinata=tempo di esecuzione

18 Limiti della ricerca binaria
Occorre una sequenza ordinata e la possibilita' di accesso diretto E' costoso mantenere queste proprieta' se la sequenza viene modificata Per esempio occorrerebbe ordinare nuovamente la sequenza ad ogni inserimento/cancellazione

19 DIVIDI ET IMPERA La migliore performance della ricerca binaria si deve alla capacita' dell'algoritmo di eliminare ad ogni iterazione la meta' degli elementi residui dallo spazio di ricerca. L'approccio che consiste nel dividere un problema in due o piu' sottoproblemi di dimensione inferiore si chiama Dividi et Impera (e' un motto attribuito a Filippo il macedone: fomentando le rivalita' all'interno di un popolo lo si governa meglio, separando un problema lo si risolve meglio).

20 Radix searching e i Faldoni...
La tecnica di separare gli elementi da cercare in classi e' comune nella vita di tutti I giorni. Il “cassetto dei calzini” consente di non dover cercare in tutto l'armadio quando si ha necessita' di un paio di calzini. Negli uffici spesso ci sono faldoni contrassegnati dalle lettere dell'alfabeto (le pratiche dei clienti con cognome che inizia con la 'M' stanno nel faldone 'M') Non sara' ottimale come la ricerca binaria, ma si puo' velocizzare la ricerca. (diventerebbe ottimale se tutti I faldoni contenessero circa lo stesso numero di elementi. Chi ha esperienza di ufficio sa che I faldoni con la B e con la M sono piu' pieni di quelli con la Q o la H)

21 Radix tree, l'albero delle radici comuni
Gli elementi vengono messi in un albero. Le radici comuni formano un nodo dell'albero La complessita' di ricerca e' proporzionale alla profondita' dell'albero (che puo' essere sbilanciato) CC BY-SA 2.5 Claudio Rocchini

22 Hashing (traduzione da vocabolario: pasticcio, guazzabuglio)
Si creano N contenitori (N viene scelto a seconda della quantita' di risorse a disposizione) Si definisce una funzione hash che in qualche modo associa a ogni chiave uno dei contenitori Se la funzione “sparpaglia” uniformemente gli M elementi da cercare nei contenitori alla fine in ogni contenitore ci sono M/N elementi Teoricamente se la funzione di Hash sparpagliasse perfettamente gli elementi nel caso N=M l'elemento verrebbe trovato in complessita' 1 (al primo tentativo).

23 In Python Ricerca in Aptiva 2014
def hashfun(x): s=str(x) count=5381 for c in s: count=count*33+ord(c) return count NELEM=128 hashtable=[[] for i in range(NELEM)] def hash_insert(elem): h=hashfun(elem)%NELEM hashtable[h].append(elem) def hashtable_create(keys): for elem in keys: hash_insert(elem) def hash_search(elem): for scan in hashtable[h]: if scan == elem: return True return False aptiva14list=["Franca", "Marta", "Denis", "Anna", "Runa", "Arianna", "Domenico", "Alice", "Carmosina", "Francesca", "Melissa", "Agostino", "Carmela", "Silvia", "Gaetana", "Lara", "Chiara", "Michelangelo", "Adriana", "Davide", "Mariangela", "Aniello", "Renzo"] hashtable_create(aptiva14list) print(hashtable) while True: s=input("Scrivi un nome: ") if not s: break print(s,"" if hash_search(s) else "non","fa parte di Aptiva 2014") In Python Ricerca in Aptiva 2014

24 Proviamo il programma Il programma mostra la tabella e poi consente di provare la ricerca [[], [], [], [], [], ['Lara'], [], [], [], ['Aniello'], [], [], [], [], [], [], ['Franca'], [], ['Davide'], ['Melissa'], [], [], [], [], ['Denis'], [], ['Carmela'], [], [], [], [], [], [], [], [], ['Anna', 'Alice'], [], [], [], [], [], [], [], [], [], ['Silvia', 'Michelangelo'], [], [], [], [], [], [], [], ['Adriana'], [], [], [], [], [], [], [], [], [], ['Arianna'], [], [], [], [], [], [], [], [], [], ['Agostino'], [], [], [], ['Chiara'], [], [], [], [], [], ['Renzo'], [], [], ['Mariangela'], [], [], [], [], ['Runa'], [], [], [], [], [], [], ['Carmosina'], [], [], [], [], [], [], [], [], ['Francesca'], [], [], [], [], [], [], [], ['Domenico'], [], [], ['Gaetana'], [], [], [], ['Marta'], [], [], [], [], []] Scrivi un nome: Agostino Agostino fa parte di Aptiva 2014 Scrivi un nome: Lara Lara fa parte di Aptiva 2014 Scrivi un nome: Filippo Filippo non fa parte di Aptiva 2014 Scrivi un nome:

25 Quanto e' efficiente l'hashing
Per provare l'efficienza della ricerca tramite tabelle di hash proviamo a inserire tutte le parole di un vocabolario della lingua inglese Ovviamente l'efficienza dipendera' dalla “bonta'” della funzione scelta e dalla distribuzione dei dati da memorizzare.

26 Il programma di prova def hashfun(x): s=str(x) count=5381 for c in s:
count=count*33+ord(c) return count def hash_insert(elem): h=hashfun(elem)%NELEM hashtable[h].append(elem) def hash_evaluate(N): lentable=[len(hashtable[i]) for i in range(NELEM)] print("N= {:5} MIN= {:5} MAX= {:5} MEAN_VALUE= {:5}".format(N, min(lentable),max(lentable),sum(lentable)/len(lentable))) for N in range(16): NELEM=2**N hashtable=[[] for i in range(NELEM)] with open("/usr/share/dict/american-english",'r') as fd: for word in fd: word=word.strip() hash_insert(word) hash_evaluate(NELEM)

27 Ecco l'output N= 1 MIN= 99171 MAX= 99171 MEAN_VALUE= 99171.0

28 In forma grafica e' piu' chiaro

29 Ricerca di una stringa in un testo (string searching)
Un algoritmo di ricerca che usiamo spesso nei word processor (o anche nei browser) e' la ricerca di un testo nel documento o nella pagina. Come funziona questa ricerca? Che complessita' ha?

30 Algoritmo di Forza Bruta
E' l'algoritmo piu' semplice (e meno efficiente). Si confronta ogni sottostringa del testo di lunghezza uguale alla stringa da cercare con la stringa da cercare stessa Text: Sempre caro mi fu quest'ermo colle Ko: caro Ko: caro Ko: caro Ko: caro Ko: caro .... Ok: caro

31 In Python def bfsearch(s,t): for i in range(len(t)-len(s)+1):
# if t[i:i+len(s)] == s: for j in range(len(s)): if t[i+j] != s[j]: break else: return i return None if __name__=="__main__": t=input("scrivi il testo: ") s=input("scrivi la stringa da cercare: ") n=bfsearch(s,t) if n != None: print("la stringa e' presente alla posizione: ",n) print("la stringa non e' presente")

32 Risultato scrivi il testo: sempre caro mi fu quest'ermo colle scrivi la stringa da cercare: caro la stringa e' presente alla posizione: 7 scrivi la stringa da cercare: montagna la stringa non e' presente La complessita' e' piu che proporzionale rispetto alla lunghezza del testo Se si controllasse sempre tutta la stringa da cercare sarebbe NxM dove N e' la lunghezza del testo, M la lunghezza della stringa da cercare Con l'ottimizzazione del break nel confronto fra stringhe, dipende dal numero di parole con prefissi comuni

33 SI puo' fare di piu'! Esistono algoritmi di ricerca per stringhe piu' veloci Ne voglio analizzare uno in particolare (Rabin-Karp) perche' fornisce un esempio di strategia risolutiva Divide et Impera. Si costruisce una funzione che abbia una bassa complessita' computazionale per controllare una ipotesi di soluzione. La funzione ha output binario: Vero: significa che “puo' essere una soluzione” Falso: significa che sicuramente non e' una soluzione Questo metodo consente di evitare nella maggior parte dei casi la verifica completa dell'ipotesi di soluzione.

34 Come si applica alla ricerca di stringhe in un testo
Si costruisce una funzione hash. Si calcola il valore hash del testo da cercare. Si calcola il valore di hash della sottostringa da controllare. Se il valore di hash coincide se effettivamente e' stata trovata una corrispondenza.

35 La “furbizia” della funzione hash per Rabin-Karp
La funzione di hash viene scelta in modo che sia facile da “ricalcolare”. Per cercare “caro” nel primo verso dell'infinito occorre confrontare l'hash di “caro” con l'hash di “Semp”, poi con quello di “empr” poi con “mpre” e cosi' via. Dobbiamo scegliere una funzione per la quale sia possibile aggiungere un elemento in coda e toglierne uno in testa senza ricalcolare tutta la funzione. Le funzioni Hash per Rabin Karp possono essere complesse, noi qui ci limiteremo alla funzione “somma della codifica dei caratteri”. L'hash di “Semp” sara': ord('S')+ord('e')+ord('m')+ord('p'). Chiameremo questa funzione sumhash sumhash(“empr”)==sumhash(“Semp”)- sumhash(“S”)+sumhash(“r”) Se l'hash coincide con l'hash di “caro” puo' darsi che siamo in presenza di una soluzione. Se l'hash e' diverso sicuramente non e' una soluzione.

36 In Python def sumhash(s): return sum([ord(c) for c in s])
# count=0 # for c in s: # count+=ord(c) # return count def bfsearch(s,t): hs=sumhash(s) ht=sumhash(t[:len(s)]) if hs==ht and s==t[:len(s)]: return 0 for i in range(len(t)-len(s)): ht+=ord(t[i+len(s)])-ord(t[i]) if hs==ht and s==t[i+1:i+1+len(s)]: return i+1 return None if __name__=="__main__": t=input("scrivi il testo: ") s=input("scrivi la stringa da cercare: ") n=bfsearch(s,t) if n != None: print("la stringa e' presente alla posizione: ",n) else: print("la stringa non e' presente") In Python

37 Complessita' di Rabin Karp
Con una funzione hash che minimizzi I falsi positivi la ricerca di una stringa in un testo ha una complessita' poco piu' che lineare.

38 Esistono altri algoritmi di ricerca!
Questo insieme di algoritmi e' stato scelto per la valenza didattica. Sono semplici e forniscono un'idea delle strategie da usare e utilizzano conoscenze matematiche alla portata degli studenti del biennio delle scuole superiori. La trattazione della complessita' e' stata fatta in modo intuitivo senza inutili formalizzazioni

39 We are still creating art and beauty on a computer:
the art and beauty of revolutionary ideas translated into (libre) code... renzo, rd235, iz4dje


Scaricare ppt "ALMA MATER STUDIORUM: Universita' di Bologna"

Presentazioni simili


Annunci Google