Algorithmie PC 4 : Méthodes de recherche corrigé 1 Fonction d’adressage Un fonction d’adressage, ou fonction de hashage est une fonction permettant d’associer un entier de [0,N] à un ensemble de données (des chaı̂nes de caractères, par exemple). De façon générale, comme toute donnée en informatique est dénombrable (i.e. toute donnée est représentée par une suite de 0 et de 1), une fonction d’adressage est une fonction de l’ensemble des entiers naturels dans un sousensemble fini d’icelui (qui sera la plupart du temps les entiers de [0, N ], avec N entier). 1.1 Une fonction d’adressage particulière Une bonne fonction d’adressage doit avoir un caractère “aléatoire” pour que les données se répartissent bien entre tous les entiers de [0, N ]. Une fonction très utilisée est la fonction modulo. En prenant deux entiers k et k’, k modulo k’ est égal au reste de la division entière de k par k’. 1.1.1 Implémentez un algorithme permettant de calculer la fonction modulo. Quelle est sa complexité ? L’algorithme 1 est une possibilité. Il y a k’/k itérations, la complexité est donc en O(k 0 /k). 1.1.2 On suppose que chaque lettre de l’alphabet est codée de façon binaire sur 5 bits par sa position dans l’alphabet (00001 pour A, 00101 pour E, etc.). Si l’on veut des entiers entre 0 et 100 (en notation décimale), quelle est l’adresse via la fonction modulo de la chaı̂ne de caractère CLE ? 1 Algorithme 1 Fonction modulo Données un entier k un entier k’ Début i=1 tant que k*i < k’ faire i=i+1 i=i-1 rendre k’-k*i Fin Comme C=00011, L=01100 et E=00101, le code de CLE est égal à 000110110000101. Ce nombre est égal à 211 +210 +28 +27 +22 +20 = 2048+1024+256+128+4+1 = 3461. Comme tout est codé sur 5 bits, on a également que le code de CLE est égal à 3 ∗ 322 + 12 ∗ 321 + 5 ∗ 320 . Si l’on veut des entiers entre 0 et 100, on doit prendre comme diviseur 101. L’adresse de CLE est alors ici égale à 3461 modulo 101 = 27. 1.1.3 Que se serait-il passé si l’on avait voulu des entiers entre 0 et 31 ? Le code utilisé affecte un entier codé sur 5 bis. Ainsi, CLE = 3 ∗ 322 + 12 ∗ 1 32 +5∗320 . Le résultat du modulo 32 ne concerne alors que le dernier caractère de la clé, le rendant non aléatoire. Un moyen simple d’enlever cette difficulté est de ne considérer que des nombres premiers pour l’opération modulo. 1.1.4 En utilisant la méthode de Horner (cf. première petite classe) proposez un algorithme permettant de calculer l’adresse d’une chaı̂ne de caractère en utilisant le code précédant (où le code de CLE est égal à 3 ∗ 322 + 12 ∗ 321 + 5 ∗ 320 ) et la fonction d’adressage égale au modulo. On Prappelle que la méthode de Horner consiste à écrire un polynôme P (x) = a0 + 1≤i≤n ai xi de la façon suivante : P (x) = a0 + x(a1 + ...x(an−1 + an x)...). Pour notre codage, soit une chaı̂ne de caractère c = c1 c2 . . . cm , et φ la fonction qui a chaque caractère rend sa position dans l’alphabet. Le code associé à la chaı̂ne de caractère est alors PC (32) où X Pc (x) = φ(ci−1 )xi 0≤i≤m−1 2 En s’inspirant fortement de la PC1, on a alors l’algorithme 2 qui rend bien l’adresse de toute chaı̂ne de caractère. On peut remarquer que ceci permet d’échapper au dépassements d’entiers qui résulterait du codage d’une très longue chaı̂ne de caractère grâce aux propriétés de la fonction modulo (a*b modulo k’ = (a modulo k’)*(b modulo k’) modulo k’. Algorithme 2 Calcul d’adresse Données une chaine c = c1 c2 . . . cm de m caractères un entier k’ Début res=0 pour i=1 a m faire res=(32*res + φ(ci )) modulo k’ rendre res Fin 1.2 Fonction d’adressage et dictionnaire Les fonctions d’adressages sont utilisées dans les structures de données appelées dictionnaires. Un dictionnaire peut être vu comme un tableau auquel on accède non pas par des indices, mais par des chaı̂nes de caractères. 1.2.1 En supposant qu’il existe une fonction d’adressage rendant une adresse unique quelque soit la chaı̂ne de caractère, proposez une implémentation d’un dictionnaire. Une telle fonction existe-t-elle ? La méthode est-elle réalisable ? Il suffit de considérer un tableau dont l’indice correspond à l’adresse de la chaı̂ne de caractère. Une telle fonction d’adressage existe, il suffit de prendre comme adresse le code associé à chaque chaı̂ne de caractère de la section précédente. Une telle méthode est bien évidemment irréalisable en pratique puisque l’on ne possède pas de mémoire infinie. Mettre en place un dictionnaire nécessite donc de gérer les chaı̂nes de caractères ayant même adresse. 1.2.2 Comment gérer les chaı̂nes ayant même adresse ? Le plus simple est qu’à chaque adresse, on utilise une liste contenant les différentes chaı̂nes de caractères. Ainsi, lorsque deux chaı̂nes possèdent la même adresse, on peut grâce à la liste savoir si la chaı̂ne est déjà présente. Si la fonction 3 d’adressage est bien “aléatoire”, les listes associées à chaque adresse seront de longueurs équivalentes. 1.2.3 Quel est l’intérêt d’utiliser ce genre de structure ? Tout d’abord, utiliser des structures indexées par des chaı̂nes de caractères se révèle utile dans de nombreuses situations (gérer un carnet d’adresse par exemple), mais surtout, permet de diviser par le nombre d’adresses différentes le coût de la recherche d’une chaı̂ne particulière par rapport au coût que l’on aurait si l’on avait utilisé une unique liste pour stocker les dites chaı̂nes. 2 Algorithme de Rabin-Karp L’algorithme de Rabin-Karp pour la recherche d’un motif de longueur m dans une chaı̂ne de caractères c1 revient à considérer une fonction d’adressage et de vérifier, pour chaque paquet de m lettres de c1 si l’adresse correspond à celle du motif. On considérera ici que nombre associé à chaque caractère est sa position dans l’alphabet, codé en base d. Dans la partie précédente, d était égal à 32. De même on considère que la fonction d’adressage est la fonction modulo appliquée à un grand nombre premier q. 2.1 On considère que l’on a une fonction φ rendant pour chaque caractère sa position dans l’alphabet. Quelle est le code du mot de longueur m présent à la position i de la chaı̂ne c1 ? En utilisant l’écriture sous forme de polynôme vue dans la partie précédente, ce mot s’écrit : x = φ(c1 [i])dm−1 + φ(c1 [i + 1])dm−2 + . . . + φ(c1 [i + m − 1])d0 2.2 En connaissant le code du mot de longueur m présent à la position i de la chaı̂ne c1 , quelle est le code du mot de longueur m présent à la position i+1 de la chaı̂ne c1 ? (x − φ(c1 [i])dm−1 )d + φ(c1 [i + m]) 2.3 En connaissant l’adresse du mot de longueur m présent à la position i (c’est à dire l’adresse du code du mot) de la chaı̂ne c1 , quelle est 4 l’adresse du mot de longueur m présent à la position i+1 de la chaı̂ne c1 ? On utilise la même propriété du modulo que dans la partie précédente pour résoudre cette question. On note y = dm−1 modulo q, et x le code du mot de longueur m présent à la position i de la chaı̂ne c1 . L’adresse de x est alors x modulo q. de là, l’adresse du mot de longueur m présent à la position i+1 est alors égale à : ((((x modulo q) + (d ∗ q − φ(c1 [i]) ∗ y)) modulo q) ∗ d + φ(c1 [i + m])) modulo q Une quantité d*q est ajoutée pour garantir le fait que tout reste bien positif dans le calcul des modulos. 2.4 En déduire un algorithme permettant de trouver dans une chaı̂ne de longueur n la première position i dont le mot de longueur m commençant à cette position à la même adresse qu’un motif de longueur m. Quel est sa complexité ? La complexité de cette algorithme est clairement O(m + n) (une première itération de longueur m pour calculer l’adresse du motif et du mot commençant en position 0, puis une itération de longueur n pour trouver l’appariement). 2.4.1 Que reste-t-il à faire pour trouver un motif dans une chaı̂ne de caractère ? Il faut encore vérifier que les deux mots sont identiques, puisque deux chaı̂nes différentes peuvent avoir même adresse. Si l’on considère un entier q très grand et premier, on va pouvoir limiter au maximum ce risque, rendant l’algorithme complet presque sûrement linéaire. 5 Algorithme 3 Algorithme de Rabin-Karp Données une chaine c1 de n caractères une chaine c2 de m caractères une fonction φ, un entier d et q Début y=1 pour i=1 a m-1 faire (calcul de dm−1 modulo q) y = d*y modulo q h1 = 0 h2 = 0 pour i=0 a m-1 faire (calcul de l’adresse de c2 et du mot de longueur m en position 0 de c1 ) h2 = (h2 ∗ d + φ(c2 [i])) modulo q h1 = (h1 ∗ d + φ(c1 [i])) modulo q i=0 tant que h1 6= h2 faire h2 = (h2 + d ∗ q − φ(c1 [i]) ∗ y) modulo q h2 = (h2 ∗ d + phi(c1 [i + m])) modulo q Si i > n − m alors rendre -1 rendre i Fin 6