Transparents repris d'un cours donné par Jean-Charles Régin à l'Université de Nice.
Diverses situations où un algorithme est nécessaireSuite finie et non ambiguë d’opérations ou d'instructions permettant de résoudre un problème.
Exemples :
[chemin] Pour résoudre un labyrinthe, toujours longer le mur de droite.
[tri] Chercher les minimas successifs (sans remise) dans un ensemble fini d'entiers revient à les trier
[cryptage] Remplacer chaque lettre d'un message par la suivante dans l'alphabet
...
Remarque : on peut (en général) les décrire en langage naturel.
"Science des algorithmes"
Remonte à l'Antiquité
Euclide : calcul du pgcd de deux nombres,
Archimède : calcul d’une approximation de £\pi£
S'étudient à l'aide des mathématiques : on peut par exemple montrer que l'algorithme de tri du slide précédent est correct par récurrence
...Mais se trouvent pleinement exploités en informatique :
On peut ranger les algorithmes dans diverses catégories en fonction (de l'équivalent mathématique) du
£\Rightarrow£ Théorie de la complexité
En pratique, on retient en général qu'un algorithme doit avoir un temps d'exécution et un nombre d'opérations au maximum en £n^3£ pour être utile.
Exemple : le produit matriciel usuel est en £O(n^3)£
Attention : un algorithme juste peut être mal implémenté
£n = 2p + \delta£ avec £\delta \in \{0,1\}£, donc £x^n = (x^p)^2 x^{\delta}£
Récurrence : il faut au plus 3 multiplications pour passer de £\leq \frac{n}{2}£ à £n£.
Conclusion : au plus £3 \log_2(n)£ multiplications au total.
Choix uniforme dans £[1,n]£ (initialement ordonné) :
1 | 2 | ... | n |
Permutation avec le dernier élément :
1 | n | ... | 2 |
Choix uniforme dans £[1,n-1]£ : ...etc.
Conclusion : £O(n)£ opérations
Pour passer de l'énoncé d'un algorithme à son implémentation dans un ordinateur, il est nécessaire de préciser comment seront stockées et manipulées les données impliquées
Ces choix sont cruciaux : l'efficacité d'un algorithme dépend en grande partie des structures de données utilisées pour l'implémenter
Exemple :
Une pile est l'analogue informatique d'une pile d'assiette : on empile des choses, n'accède qu'au dernier empilé, et doit l'enlever pour accéder à la suite.
Une pile peut être implémentée par un tableau dynamique ou une liste chaînée...
Ensemble d'éléments accédés par un numéro d'indice
Notation : £T[i]£ désigne l'élément d'indice £i£ du tableau £T£
Caractéristiques :[concret] Les éléments d'un tableau sont contigus en mémoire.
Ajout (ou suppression) d'un élément en £O(1)£ en fin de tableau
Soit £T£ un tableau standard. On lui associe un entier £C£ désignant sa capacité maximale. Initialement on peut prendre £C£ = taille du tableau (notée £\ell£).
Contrepartie : le tableau n'a pas d'adresse mémoire fixe
Illustration
1 | 3 | 5 |
1 | 3 | 5 | 8 |
Soient £\ell_i£ et £C_i£ les taille et capacité initiales du tableau.
L'ajout de £m£ éléments nécessite £R = \left \lceil \log_2 \frac{\ell_i - C_i + m}{C_i} \right \rceil£ réallocations + recopies.
Comptons les opérations élémentaires (copie d'un élément) :
£\sum_{j=0}^{R-1} (C_i 2^j + 1)£ £\simeq C_i 2^R£
£\sum_{j=0}^{R-1} (2^j C_i + 1)£ £\simeq \ell_i - C_i + m£
£\sum_{j=0}^{R-1} (2^j C_i + 1)£ £= O(m)£
En moyenne, l'ajout d'un élément se fait donc en £O(1)£
Une pile (stack en anglais) est une structure de données fondée sur le principe "dernier arrivé, premier sorti" (ou LIFO pour Last In, First Out)
£\Rightarrow£ les derniers éléments ajoutés à la pile seront les premiers à être récupérés.
Une des structures de données les plus fondamentales en informatique : très simple et puissante.
Sommet(P) :
renvoie le dernier élément ajouté et non encore retiré (top)
Empiler(P,elt) :
place l'élément au sommet de la pile P (push)
Dépiler(P) :
retire de la pile le sommet (pop)
EstVide(P) :
Renvoie vrai si la pile est vide et faux sinon (empty)
Algorithme : on empile les parenthèses ouvrantes, et à chaque parenthèse fermante rencontrée on dépile un élément.
32+a*((4-6/(b+3)*51)-1)
( | ( | ( |
( | ( |
rien |
La pile est vide : l'expression est bien parenthésée.
"Inverse" d'une pile : c'est l'analogue d'une file d'attente £\Rightarrow£ first-in-first-out
Exemples :
Question : Sdd pour le parcours d'un graphe en profondeur ?
Question : ...Une pile
Sdd représentant une collection ordonnée et de taille arbitraire d'éléments
L'accès aux éléments d'une liste se fait de manière séquentielle : chaque maillon permet l'accès au suivant.
La recherche d'un élément donné est donc coûteuse (comparée au cas du tableau). Cependant, l'insertion et la suppression peuvent s'effectuer en temps constant
Un maillon de la châine contient une donnée et un accès vers un autre maillon (éventuellement vide)
Le "dernier" élément a pour suivant le "premier"
Chaque maillon pointe à la fois sur le suivant et le précédent
Décrire les opérations à effectuer pour insérer elt après p
Structure hiérarchique définie récursivement :
£\Rightarrow£ un arbre est composé d'un sommet et de sous-arbres.
Analogie généalogique...
Également frères/soeurs, ancêtres, descendants, cousin(e)s...
Arbre binaire : cas particulier où chaque noeud a au plus deux fils ;
Arbre binaire : adapté pour la recherche par comparaisons
Intérêt : peu de comparaisons si faible hauteur
Rechercher 12 depuis la racine
Chaque noeud "trie" ses sous-arbres par valeurs de clés
Chaque clé est associée à une adresse mémoire : celle de la donnée recherchée
Arbre ordonné avec des contraintes particulières (pour que la recherche soit toujours efficace)
Arbre satisfaisant les propriétés suivantes (D. Knuth).
£\Rightarrow£ Structure couramment utilisé dans les bases de données
£\Rightarrow£ pour indexer les données ; i.e., y accéder rapidement.
£n£ = nombre maximal de clés contenues dans l'arbre
££\lceil \log_m(n+1) \rceil \leq h \leq \left\lfloor \log_{\lceil m/2 \rceil} \left( \frac{n+1}{2} \right) \right\rfloor££Recherche, insertion / suppression : £O(h)£ en moyenne et dans le pire cas
Remarque : l'insertion et la suppression ne sont pas triviales
[Wikipedia]
[Wikipedia] a B-tree:
In addition, a B-tree minimizes waste by making sure the interior nodes are at least half full. A B-tree can handle an arbitrary number of insertions and deletions.
La recherche, insertion et suppression d'éléments dans une BdD sont souvent optimisées à l'aide de structures arborescentes telles que décrites précédemment
Structure de données utilisée et entretenue par le système de gestion de base de données (SGBD) pour lui permettre de retrouver rapidement des enregistrements.
+ Efficacité nettement augmentée si index bien choisi
- Il faut mettre à jour les index à chaque insertion/suppression
Différents types d'index possible. Le plus courant étant le B-tree (ou variantes)
create index
statement.
Analogie : index en fin d'ouvrage scientifique
Difficulté : mettre à jour l'index avec une faible complexité
Solution : B(+)-tree + liste doublement chaînée
Databases use doubly linked lists to connect the so-called index leaf nodes.
£\Rightarrow£ The index order is maintained on two different levels: the index entries within each leaf node, and the leaf nodes among each other using a doubly linked list
Balanced : toutes les feuilles ont la même profondeur
Each branch node entry corresponds to the biggest value in the respective leaf node.
Index fragment to illustrate a search for the key "57". The tree traversal starts at the root node on the left-hand side. Each entry is processed in ascending order until a value is greater than or equal to the search term (57)
The tree traversal is a very efficient operation, because of:
Le parcours de l'arbre pour atteindre une feuille a une longueur bornée, donc n'impacte pas la performance
En revanche, il peut y avoir beaucoup d'entrées possédant la même clé, forçant l'algorithme de recherche à parcourir plusieurs entrées d'une même feuille, voire plusieurs feuilles (liste chaînée). Il faut ensuite récupérer les entrées dans la table. Tout cela prend du temps.
£\Rightarrow£ Il faut faire attention à la façon dont on écrit les requêtes.
£\rightarrow£ Automatiquement créés sur les clés primaires
Sinon...
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ name ] ON table [ USING method ]
( { column | ( expression ) } [ COLLATE collation ] [ opclass ]
[ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ WITH ( storage_parameter = value [, ... ] ) ]
[ TABLESPACE tablespace ]
[ WHERE predicate ]
+ Accélère les requêtes si bien choisis
- Ralentit les INSERT/DELETE £\Rightarrow£ ne pas en abuser
Les champs marqués "UNIQUE" sont également indexés :
-- dans psql sur 192.168.31.236 :
create table test (
id serial primary key,
name varchar(30) unique,
date timestamp);
NOTICE: CREATE TABLE créera des séquences implicites
« test_id_seq » pour la colonne serial « test.id »
NOTICE: CREATE TABLE / PRIMARY KEY créera un index
implicite « test_pkey » pour la table « test »
NOTICE: CREATE TABLE / UNIQUE créera un index implicite
« test_name_key » pour la table « test »
The where clause defines the search condition of an SQL statement, and it thus falls into the core functional domain of an index: finding data quickly.
Although the where clause has a huge impact on performance, it is often phrased carelessly so that the database has to scan a large part of the index.
The result: a poorly written where clause is the first ingredient of a slow query.
£\Rightarrow£ On se focalise dans ce cours sur le WHERE seulement
Given a query:
SELECT last_name FROM employees where salary >= 50000;
We can inspect how Postgres will execute it with:
EXPLAIN SELECT last_name FROM employees where salary >= 50000;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on employees (cost=0.00..16.50 rows=173 width=118)
Filter: (salary >= 50000)
We can both execute the query and inspect the path/time it took with:
EXPLAIN ANALYZE SELECT last_name FROM employees
where salary >= 50000;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on employees (cost=0.00..16.50 rows=173 width=118)
(actual time=0.018..0.018 rows=0 loops=1)
Filter: (salary >= 50000)
Total runtime: 0.053 ms
We start with the simplest yet most common where clause: the primary key lookup. We use the EMPLOYEES table defined as follows:
CREATE TABLE employees (
employee_id NUMBER NOT NULL,
first_name VARCHAR2(1000) NOT NULL,
last_name VARCHAR2(1000) NOT NULL,
date_of_birth DATE NOT NULL,
phone_number VARCHAR2(1000) NOT NULL,
CONSTRAINT employees_pk PRIMARY KEY (employee_id)
);
SELECT first_name, last_name
FROM employees
WHERE employee_id = 123
QUERY PLAN --------------------------------- Index Scan using employees_pk (cost=0.00..8.27 rows=1 width=14) Index Cond: (employee_id = 123::numeric)
Supposons que l'entreprise fusionne avec une autre : employee_id n'est alors plus unique. On ajoute un champ 'subsidiary_id' désignant l'entreprise.
£\Rightarrow£ La clé primaire devient (employee_id, subsidiary_id)
Un index est alors construit de la même façon que sur une colonne unique, mais l'ordre importe
Exemple de l'annuaire téléphonique : clé = (nom, prénom)
£\rightarrow£ On cherche d'abord le nom, puis le prénom !
SELECT first_name, last_name, date_of_birth
FROM employees
WHERE date_of_birth >= TO_DATE('1971-01-01', 'YYYY-MM-DD')
AND date_of_birth <= TO_DATE('1971-01-09', 'YYYY-MM-DD')
AND subsidiary_id = 27
On ne peut pas utiliser de fonctions d'un index en conservant ses propriétés :
SELECT *
FROM employees
WHERE UPPER(last_name)
= 'WINAND'
QUERY PLAN ------------------------------ Seq Scan on employees [...] Filter: (upper((last_name)::text) = 'WINAND'::text)
Mais on peut créer un index sur une fonction des attributs :
CREATE INDEX emp_up_name
ON employees (UPPER(last_name));
On peut utiliser des fonctions PL/pgSQL quelconques pour définir un index (exemple : date_de_décès - date_de_naissance), mais elles doivent être déterministes.
Contre-exemple :
CREATE FUNCTION get_age(date_of_birth DATE)
RETURNS integer AS $$
BEGIN
RETURN TRUNC(MONTHS_BETWEEN(SYSDATE, date_of_birth)/12);
END $$ language plpgsql;
get_age(X)
change à chaque anniversaire de X...
Les emails circulant sur le web sont en grande majorité du spam (~90%).
Le spam ayant des caractéristiques particulières :CREATE INDEX messages_spam
ON messages (receiver)
WHERE spam_flag = ...
CREATE TABLE messages (
sender VARCHAR(64) NOT NULL,
receiver VARCHAR(64) NOT NULL,
subject VARCHAR(256),
content TEXT,
spam_flag BOOLEAN
);
Avec l'index précédent :
SELECT *
FROM messages
WHERE subject LIKE 'Hello%' AND spam_flag = TRUE
Par défaut les données n'ont pas d'ordre dans une table (elles sont stockées au fur et à mesure des insertions).
..."sauf" dans les indexes, où leurs adresses sont rangées "à leur place"
£\Rightarrow£ Les accès aux données filtrées après utilisation d'un index peuvent être très lents si les données adressées sont très éparpillées.
Trier les données pour qu'elles apparaissent en mémoire dans l'ordre de l'index
CLUSTER [VERBOSE] table_name [ USING index_name ]
Extrait : CREATE INDEX tab_idx ON tbl (a, date_column);
SELECT date_column, count(*)
FROM tbl
WHERE a = 123
GROUP BY date_column;
£\rightarrow£ ~300 lignes
SELECT date_column, count(*)
FROM tbl
WHERE a = 123
AND b = 42
GROUP BY date_column;
£\rightarrow£ ~30 lignes
/