Exercices sur les algorithmes et les structures de données - 3° partie, Exercices de Algorithmique et programmation d'applications
Christophe
Christophe28 February 2014

Exercices sur les algorithmes et les structures de données - 3° partie, Exercices de Algorithmique et programmation d'applications

PDF (135.3 KB)
31 pages
243Numéro de visites
Description
Exercices de mathématique - examen sur les algorithmes et les structures de données - 3° partie. Les principaux thèmes abordés sont les suivants: Structures de donnees arborescentes.
20points
Points de téléchargement necessaire pour télécharger
ce document
Télécharger le document
Aperçu3 pages / 31
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Aperçu avant impression terminé
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Aperçu avant impression terminé
Chercher dans l'extrait du document
cours.dvi

Implémentation au moyen d’un tableau

L’opération ieme est en O(1) seulement si le tableau est de taille fixe.

Mais nous considérons des ensembles dont le nombre d’éléments est inconnu,

donc le tableau infini est nécessaire, et la complexité dans le pire des cas

devient O(n), avec n étant le nombre d’éléments de la liste. De même, les

opérations inserer et supprimer nécessitent de décaler des éléments et sont

donc en O(n) dans le pire des cas.

Implémentation avec des structures châınées

Les trois opérations ieme, inserer et supprimer sont en O(n) dans le pire

des cas.

Dans le cas de cette implémentation, considérons le scénario suivant.

Supposons que l’on souhaite supprimer le premier élément d’une liste l

ayant la valeur v et considérons l’algorithme à mettre en oeuvre avec cette

signature.

– Rechercher le k-ème élément de l ayant pour valeur v. Il faut faire une

boucle parcourant l depuis le début et comparer le résultat de la fonc-

tion ieme(l, i) à la valeur v. Renvoyer le rang i de l’élément correspon-

dant ou n + 1 si la valeur n’est pas trouvée. Notons rechercher(l, k, v)

cette fonction (voir ci-dessous).

– Insérer un élément au rang i en utilisant directement la fonction inserer.

Dans cette implémentation, un parcours depuis le début de la liste est

à nouveau nécessaire : inserer(l, rechercher(l, k, v), v).

70

RECHERCHER(l,k,v)

% Recherche de la kieme occurrence de v dans l

i ←1 j ←0 tant que i ≤ n et ieme(l, i) 6= v et j < k faire si ieme(l,i) = v

alors j ←j+1 i ←i+1

retourner i

On constate que l’opération d’insertion d’un élément selon sa valeur est

en O(2n), ce qui est inacceptable pour une opération de base.

Afin de solutionner ce problème, on pourrait envisager dans un premier

temps les possibilités suivantes :

– Renvoyer un noeud et non une valeur pour l’élément trouvé par la

fonction de recherche. Cette solution est extrèmement mauvaise car elle

affiche dans la signature de la sorte des contraintes d’implémentation.

– Ajouter autant de fonctions que de services nécessaires identifiés. Cette

solution est aussi mauvaise car le nombre de fonctions risque d’être

important si l’on prend en compte tous les cas possibles (y compris les

cas d’erreur) :

– inserer-avant-par-valeur

– inserer-apres-par-valeur

– inserer-avant-par-rang

– inserer-apres-par-rang

71

– supprimer-avant-par-valeur

– ...

– inserer-a-la-fin-si-absent

– ...

Deuxième signature

Cette signature associe à une liste l un curseur explicite qui va éviter

les parcours multiples et redondants dans le cas d’une implémentation avec

des structures châınées. Des fonctions vont être associées au curseur pour le

positionner. Elle admettent toutes une liste en argument et renvoient une

liste en retour.

– debut(l) positionne le curseur sur le premier élément de la liste si celle-ci

est non vide.

– fin(l) positionne le curseur sur le dernier élément de la liste si celle-ci

est non vide.

– avant(l) et arriere(l) font avancer ou reculer le curseur.

– aller positionne le curseur sur une position spécifique.

– position donne le rang du curseur.

Les cas de débordement seront identifiés par les fonctions suivantes.

– trop-a-gauche(l) est une fonction booléenne indiquant si le curseur a

débordé à gauche de la première position.

– trop-a-droite(l) est une fonction booléenne indiquant si le curseur a

débordé à droite de la dernière position.

72

Ainsi les fonctions d’insertion et de suppression admettent un paramètre

de moins car elles opèrent sur le rang courant du curseur. On peut introduire

les fonctions suivantes avec les conventions suivantes :

– La fonction supprimer(l) suprime l’élement de rang égal à celui du

curseur et positionne le curseur sur son voisin de gauche.

– La fonction inserer-a-droite(l, v) insère un élement de valeur v à droite

du curseur, sans modifier ce dernier.

– La fonction inserer-a-gauche(l, v) insère un élement de valeur v à

gauche du curseur, sans bouger ce dernier. Son rang se trouve alors

augmenté d’une position.

– La fonction change-valeur(l, v) change la valeur de l’élément sous le

curseur. La valeur de cet élément est donnée par la fonction valeur.

Propriétés. Soit l une liste et n son nombre d’éléments.

– 0 ≤ position(l) ≤ n + 1

– est-vide(l) si et seulement si trop-a-gauche(l) et trop-a-droite(l)

– trop-a-gauche(l) si et seulement si est-vide(l) ou position(l) = n− 1.

– trop-a-droite(l) si et seulement si est-vide(l) ou position(l) = n + 1.

La recherche suivie d’une insertion peut alors s’écrire comme suit :

l = recherche(l,v,k)

si non trop-a-droite(l)

alors inserer-droite(l,v)

73

L’insertion d’une valeur v en position i s’écrit :

inserer(aller(l,i),v)

Remarques

– La complexité dépend de l’implémentation de cette sorte. Il faut remar-

quer qu’en plus du curseur, il faut conserver en interne des références

vers les noeuds précédant et suivant (le curseur) afin d’assurer l’effica-

cité de l’insertion et de la suppression.

– On doit pouvoir aussi l’implémenter au moyen de deux files ayant pour

tête le curseur et un voisin. À ce moment-là, les opérations avant et

arriere sont en O(1) mais pas les autres.

– Il conviendrait de comparer l’implémentation en structures châınées de

cette liste avec celle utilisant un tableau infini. Ainsi que les deux sortes

listes avec la même implémentation sous forme de tableau.

Durée : 1H30

74

Chapitre 4

Structures de données

arborescentes

Un arbre est un ensemble d’élements appelés noeuds (ou sommets) orga-

nisés de façon hiérarchique à partir d’un noeud distingué appelé racine. Un

arbre est défini de façon récursive et induit par sa structure des algorithmes

récursifs.

Exemple. La hiérarchie Unix pour les fichiers, avec la notion de chemin

(PATH) et de répertoire (directory)

4.1 Les arbres binaires

On peut prendre comme exemple le déroulement d’un tournoi d’échec

(père = vainqueur des deux fils) ou le codage d’une expression arithmétique

binaire permettant de reconstituer le calcul.

75

(x− 2y)/(x(z − y))

DESSIN de l’arbre de syntaxe abstraite.

4.1.1 Définition

Un arbre binaire est soit vide (noté ∅), soit de la forme

B =< r, B1, B2 >

où B1 et B2 sont des arbres binaires disjoints et r un noeud appelé racine.

Remarques

– Il n’y a pas de symétrie gauche-droite, c’est-à-dire < r, B1, B2 >6=< r, B2, B1 > si B1 6= B2.

– On repère les noeuds d’un arbre en leur donnant des noms symboliques

tous différents.

4.1.2 Signature du type abstrait arbre binaire

Sorte ArbreBinaire

Utilise NoeudBinaire, booléen

Opérations

76

∅ :→ ArbreBinaire <, , > : NoeudBinaire× ArbreBinaire× ArbreBinaire→ ArbreBinaire racine : ArbreBinaire→ NoeudBinaire gauche : ArbreBinaire→ ArbreBinaire droit : ArbreBinaire→ ArbreBinaire

Axiomes

– racine(< r, B1, B2 >) = r

– gauche(< r, B1, B2 >) = B1

– droit(< r, B1, B2 >) = B2

Ces opérations ne sont définies que si leur argument est non vide.

4.1.3 Terminologie sur les arbres

– Soit B =< r, B1, B2 > un arbre binaire. Le symbole r est la racine de

B, B1 (resp. B@) est le sous-arbre gauche (resp. droit) de B. On dit

que C est un sous-arbre de B si C = B1 ou bien C = B2.

– L’enfant gauche (droit) d’un noeud est la racine de son sous-arbre

gauche (droit) et il y a un lien gauche (droit) entre le noeud et son

enfant gauche (droit). Si ni a pour enfant gauche (droit) nj, ni est le

parent de nj; Deux noeuds ayant même parents sont frères (siblings).

Exemple : n0 est la racine, n1 est l’enfant gauche de n0, n3 est enfant

droit de n2, n5 et n6 sont frères de parent n4.

– ni est un ascendant de nj ssi ni est le parent de nj ou un ascendant du

77

parent de nj; nj est un descendant de ni.

Exemple : n1 est un ascendant de n3, n5 est un descendant de n0.

– Les noeuds d’un arbre binaire ont au plus deux enfants.

– Un noeud interne binaire a deux enfants.

– Un noeud interne unaire a un enfant (gauche ou droit).

– Un noeud externe, ou feuuille, n’a pas d’enfants.

Exemple : n0 et n4 sont des noeuds binaires, n1 est unaire à gauche,

n2 est unaire à droite, n3, n5 et n7 sont des feuilles.

– On appelle branche de l’arbre tout chemin (suite consécutive de noeuds)

allant de la racine à une feuille; il y a autant de branches que de feuilles.

La branche gauche (droite) est le chemin issu de la racine en ne suivant

que des liens gauches (droits).

Exemple : (n0, n4, n5) est une branche, (n0, n1, n2) est la branche gauche,

(n0, n4, n6) est la branche droite.

4.1.4 Caractéristiques d’un arbre binaire

– La profondeur (ou niveau) d’un noeud x est donnée par

p(x) = 0, si n est la racine

p(x) = 1 + h(y) si y est parent de x.

Exemple : h(n0) = 0, h(n1) = h(n4) = 1, h(n2) = h(n5) = h(n6) =

2, h(n3) = h(n7) = 3.

– La profondeur (ou hauteur) d’un arbre binaire B est définie par

h(B) = max{p(x), x noeud de B}

78

– La longueur de cheminement de l’arbre B est

LC(B) = ∑

x noeud

h(x) = ∑

x feuille

h(x) + ∑

x interne

h(x)

LC(B) = LCE(B) + LCI(B)

où LCE représente la longueur de cheminement externe de B et LCI

représente la longueur de cheminement interne de B.

Exemple : LCE(B) = 8, LCI(B) = 6, LC(B) = 14.

– La profondeur moyenne externe de l’arbre B ayant f feuilles est PE(B) =

1 f LCE(B) (profondeur moyenne d’une feuille).

Exemple : PE(B) = 8/3 = 2.66

4.1.5 Un exemple de codage des arbres binaires

On peut coder un arbre binaire par un ensemble de mots, chacun d’entre

eux étant le codage du chemin allant de la racine à un noeud donné. Ces

mots sont obtenus par concaténation de 0 ou de 1 (alphabet = {0, 1}) :

– code(racine) = , le mot vide

– code(enfant gauche de x) = code(x)0

– code(enfant droit de x) = code(x)1

Exemple : {, 0, 00, 001, 1, 10, 11, 110}

4.1.6 Bornes sur la profondeur d’un arbre binaire

Proposition. Pour tout arbre binaire de taille (nombre total de noeuds)

n, de profondeur p, et ayant f feuilles, on a

blog2(n)c ≤ p ≤ n− 1

79

et

p ≥ dlog2(f)e

Preuve : Pour p profondeur fixée

– l’arbre binaire ayant le plus petit nombre de sommets est celui qui n’est

formé que de noeuds unaires et d’une feuille. On a dans ce cas p + 1

sommets. Donc

n ≥ p + 1

c’est-à-dire

p ≤ n− 1

– l’arbre binaire ayant le plus de sommets est celui où tous les noeuds de

hauteurs 0, 1, ..., p-1 sont binaires. On a alors

1 + 2 + 22 + . . . + 2p−1 + 2p = 2p+1 − 1

noeuds en tout. On aura donc

n ≤ 2p+1 − 1

c’est-à-dire

n < 2p+1 ⇒ log2(n) < p + 1

Donc

blog2(n)c ≤ p

On a montré la première double inégalité.

Pour montrer la deuxième, il suffit de voir que le nombre de feuilles f est

toujours tel que

f ≤ 2p

80

(2p est le nombre de feuilles d’un arbre binaire parfait, ce qui correspond

au cas précédent avec n = 2p+1 − 1 et donc f = (n + 1)/2). On aura alors log2(f) ≤ p c’est-à-dire p ≥ dlog2(f)e.

4.1.7 Arbres binaires complets

Un arbre binaire est complet si tous les noeuds ont soit aucun enfant

soit deux enfants.

Exemples.

Proposition 1. Un arbre binaire complet ayant n noeuds internes a (n+1)

feuilles.

Preuve. On fait une récurrence sur n.

– La propriété est vraie pour un arbre réduit à une feuille (n = 0).

– On suppose la propriété vraie pour tous les arbres binaires complets

ayant moins de n noeuds internes. Soit

B =< ∅, B1, B2 >

un arbre binaire complet ayant n noeuds internes. Les arbres B1 et B2

sont deux arbres binaires complets et on a

n = n1 = n2 + 1

où n1 (n2) est le nombre de noeuds internes de B1 (B2). Par hypothèse

de récurrence, le nombre de feuilles de B1 (B2) est n1 + 1 (n2 + 1).

81

Or, une feuille de B est une feuille de B1 ou de B2, donc le nombre de

feuilles de B est n1 + 1 + n2 + 1 = n + 1

Durée : 1H30

Proposition 1. Il existe une bijection entre l’ensemble Bn des arbres bi- naires de taille n, et l’ensemble BC2n+1 des arbres binaires complets de taille 2n + 1.

Preuve. On enlève toutes les feuilles d’un arbre binaire complet et on ob-

tient un arbre binaire. La bijection inverse consiste à remettre des feuilles

partout de sorte que chaque noeud ait deux enfants.

Faire un dessin.

Propriété. Le nombre bn d’arbres binaires de taille n (et donc bcn+1 le

nombre d’arbres bianires complets de taille 2n + 1 est égal à 1 n+1

Cn2n.

4.1.8 Parcours d’arbres binaires

L’opération de parcours sur les arbres binaires consiste à examiner systé-

matiquement, dans un certain ordre, les noeuds de l’arbre pour y effectuer

un traitement donné.

Parcours en profondeur

Le parcours en profondeur à gauche consiste à partir de la racine et à

choisir toujours l’enfant le plus à gauche d’abord. De façon imagée, l’arbre

82

peut être vu comme un mur que l’on suit avec sa main gauche en partant à

gauche depuis la racine.

Exemple. Faire un dessin en faisant figurer les noeuds vides et en considérant

l’arbre complet.

PARCOURS(x)

si x 6=NULL alors PARCOURS(gauche(x))

PARCOURS(droit(x))

On constate que chaque noeud est rencontré trois fois : une fois en des-

cendant, puis une deuxième fois en remontant depuis l’enfant gauche, et une

troisième fois en remontant depuis l’enfant droit.

Ce parcours contient trois ordres importants d’exploration des arbres se-

lon la place du traitement de chaque noeud par rapport aux appels récursifs.

– Ordre préfixe ou préordre : le traitement du noeud s’effectue avant

le premier appel récursif. Supposons que ce traitement soit un simple

affichage du noeud.

PARCOURS-PREFIXE(x)

si x 6=NULL alors PRINT(x)

PARCOURS(gauche(x))

83

PARCOURS(droit(x))

Exemple : dessin + affichage.

– Ordre infixe ou symétrique : le traitement du noeud s’effectue entre

les deux appels récursifs.

PARCOURS-INFIXE(x)

si x 6=NULL alors PARCOURS(gauche(x))

PRINT(x)

PARCOURS(droit(x))

Exemple : dessin + affichage.

– Ordre postfixe ou suffixe : le traitement du noeud s’effectue après les

deux appels récursifs.

PARCOURS-POSTFIXE(x)

si x 6=NULL alors PARCOURS(gauche(x))

PARCOURS(droit(x))

PRINT(x)

Exemple : dessin + affichage.

84

Remarques

– Dans le premier chapitre de ce cours, nous avons dit qu’une fonc-

tion récursive nécessitait la mémorisation de ses données et de ses pa-

ramètres pour pouvoir effectuer des opérations au retour de l’appel

récursif. La structure de donnée utilisée est une pile. Faire tourner la

fonction PARCOURS-INFIXE sur un exemple et donner l’évolution de

la pile.

– En conséquence, afin d’écrire une fonction itérative représentant le

même traitment qu’une fonction récursive, il faut gérer explicitement

une pile.

Exercice. Écrire ces algorithmes de façon itérative, en gérant soi-même

une pile de noeuds.

Parcours en largeur

Le parcours en largeur consiste à explorer tous les enfants d’un noeud

avant d’explorer les enfants de ses enfants.

Exemple. Dessiner un arbre binaire et montrer la nécessité d’utiliser une

file pour traiter les noeuds dans l’ordre de leur arrivée.

La fonction suivante doit être appelée avec la racine de l’arbre et une file

vide en arguments.

PARCOURS-LARGEUR(x,f)

si x 6=NULL

85

alors PRINT(x)

f ←ajouter(file, gauche(x)) f ←ajouter(file, droit(x)) x ←premier(file) f ←retirer(file) parcours-largeur(x,f)

% Il peut rester des noeuds dans la file

sinon si non vide?(f)

x ←premier(file) f ←retirer(file) parcours-largeur(x,f)

Durée : 1H30

4.1.9 Arbres binaires de recherche

Un arbre binaire de recherche permet de traiter des ensembles dynamiques

de données admettant un ordre en fournissant beaucoup d’opérations (recher-

cher, minimum, maximum, prédécesseur, successeur, insérer, supprimer).

La complexité en temps des opérations de base d’un arbre de recherche

est proportionnelle à la hauteur de l’arbre. Si l’arbre binaire est complet et

a n noeuds, cette complexité est donc en θ(log2 n). Si par contre l’arbre est

une châıne ayant une seule feuille, cette complexité est en θ(n).

86

Définition

Un arbre binaire de recherche est un arbre binaire dans lequel on stocke

des éléments, appelés des clés, à chaque noeud. Les clés admettent un type

muni d’une relation d’ordre. Les clés sont organisées dans l’arbre selon la

propriété suivante :

Soit x un noeud de B, si y est un noeud du sous-arbre gauche de

B, alors la clé de x est plus grande que la clé de y. Si y est un

noeud du sous-arbre droit de B, alors la clé de x est plus petite

que la clé de y.

Exemple. FAIRE UN DESSIN

Implémentation d’arbre binaire de recherche

Un arbre binaire peut se représenter au moyen d’un tableau ou bien de

structures châınées. Dans les deux cas, il faut définir au préalable le type

abstrait des noeuds. C’est une structure à quatre champs : la clé, l’enfant

gauche, l’enfant droit et le parent.

Sorte NoeudBinaire

Paramètre élément

Utilise NoeudBinaire et booléen

Opérations

87

NoeudBinaire : élément×NoeudBinaire3 → NoeudBinaire cle : NoeudBinaire→ élément gauche : NoeudBinaire→ NoeudBinaire droit : NoeudBinaire→ NoeudBinaire parent : NoeudBinaire→ NoeudBinaire changer-cle : NoeudBinaire× élément→ NoeudBinaire changer-gauche : NoeudBinaire×NoeudBinaire→ NoeudBinaire changer-droit : NoeudBinaire×NoeudBinaire→ NoeudBinaire changer-parent : NoeudBinaire×NoeudBinaire→ NoeudBinaire

Un tel type abstrait s’implémente au moyen d’une structure à quatre

champs : la clé, l’enfant gauche, l’enfant droit et le parent :

structure[élément, NoeudBinaire, NoeudBinaire, NoeudBinaire]

(cle, gauche, droit, parent)

Un arbre binaire s’implémente alors au moyen d’un ensemble de noeuds

châınés entre eux par les liens enfants. Ces noeuds peuvent être disposés

de façon contiguë dans un tableau (faire un dessin), ou bien de façon non

contiguë dans la mémoire. En réalité, ce choix a une incidence seulement au

moment de la construction d’un arbre binaire (allocation de noeuds), et lors

de la suppression de noeuds d’un arbre binaire (libération de noeuds). Cet

aspect (gestion de la mémoire avec allocation et libération de structures) sera

étudié ultèrieurement.

Choisissons ici d’implémenter notre arbre binaire au moyen de struc-

tures châınées (ensemble de noeuds disposés de façon non contiguë dans la

mémoire). Alors, un arbre est implémenté par une référence à sa racine que

88

l’on peut encapsuler dans une structure :

structure[NoeudBinaire](racine)

∅()

b = structure[NoeudBinaire](racine)

b.racine = NULL

retourner b

<,>(r,b1,b2)

n = NoeudBinaire(r,b1.racine,b2.racine, NULL)

b1.racine.parent ←n b2.racine.parent ←n b = structure[NoeudBinaire](racine)

b.racine = n

retourner b

RACINE(x)

retourner x.racine

GAUCHE(x)

retourner x.racine.gauche

DROIT(x)

retourner x.racine.droit

89

Parcours en profondeur d’un arbre binaire de recherche

L’organisation des clés d’un arbre binaire de recherche est telle que lorsque

l’on effectue un parcours en profondeur en ordre infixe de l’arbre, on obtient

les clés en ordre croissant.

Requêtes dans un arbre binaire de recherche

Plusieurs sortes de requêtes sont envisageables dans un arbre binaire de re-

cherche : rechercher (une clé), minimum, maximum, successeur et prédecesseur.

Ces opérations sont toutes de complexité en O(h), où h est la profondeur de

l’arbre.

– La fonction ARBRE-RECHERCHER doit être appelée avec la racine

de l’arbre et la clef recherchée. Elle retourne une référence vers un noeud

contenant la clef ou bien NULL.

ARBRE-RECHERCHER(x,k)

si x = NULL ou k = x.cle

alors retourner x

si k < x.cle

alors retourner arbre-rechercher(x.gauche,k)

sinon retourner arbre-rechercher(x.droit,k)

Les noeuds rencontrés par la fonction forment une branche allant de

la racine vers une feuille de l’arbre. Sa longueur est donc au plus la

profondeur de l’arbre.

90

Cette fonction peut s’écrire de façon itérative. Pour certains langages,

la version itérative est plus efficace que la version récursive.

ARBRE-RECHERCHER-ITERATIVE(x,k)

tant que x 6=NULL et k 6=x.cle faire si k < x.cle

alors x ←x.gauche sinon x ←x.droit

retourner x

– On peut trouver la clef minimum d’un arbre binaire de recherche en

prenant l’enfant gauche de chaque noeud. En effet, si le noeud x ne

possède pas d’enfant gauche, la clef minimum du sous-arbre enraciné

en x est x.cle, puisque toutes les clefs à droite sont plus grandes. Si par

contre, x a un sous-arbre gauche, la clef minimum de l’arbre enraciné

en x est la clef minimum de son sous-arbre gauche.

ARBRE-MINIMUM(x)

tant que x.gauche 6=NULL faire x ←x.gauche

retourner x

La fonction maximum est symétrique. Ces deux fonctions s’exécutent en

O(h) puisqu’elles suivent chacune une branche depuis la racine jusqu’à

une feuille.

91

– La structure d’un arbre binaire de recherche permet de déterminer le

successeur d’un noeud sans même effectuer de comparaisons entre les

clefs.

Il faut considérer deux cas. En effet, si le sous-arbre droit du noeud

n’est pas vide, le successeur de x est le minimum du sous-arbre droit.

Sinon, il faut remonter à partir du noeud x jusqu’à emprunter un lien

gauche. Le successeur est le premier noeud obtenu sur ce chemin par

un lien gauche (en remontant vars la racine).

Durée : 1H30

ARBRE-SUCCESSEUR(x)

si x.droit = NULL

alors retourner arbre-minimum(x.droit)

y ←x.parent tant que y 6=NULL et x = y.droit

faire x ←y y ←y.parent

retourner y

Insertion et suppression

p245 Cormen

92

Remarques

La hauteur d’un arbre binaire de recherche varie alors que les éléments

sont insérés ou supprimés. La hauteur moyenne d’un arbre crée par des in-

sertions et suppression n’est pas connue en général. Par contre, si l’arbre est

crée par des insertions, où les n! permutations sont équiprobables, on peut

montrer que sa hauteur moyenne est O(log n).

Durée : 1H30

4.1.10 Arbres rouge et noir

Les arbres rouge et noir sont un des nombreux schémas d’arbres binaires

de recherche dits “équilibré”, garantissant que les opérations s’éxécutent en

O(log n).

Propriétés des arbres rouge et noir

1. Chaque noeud est soit rouge soit noir.

2. Chaque feuille est noire.

3. Si un noeud est rouge alors ses deux fils sont noirs.

4. Les branches allant de la racine à une feuille possèdent le même nombre

de noeuds noirs.

Définition On appelle le hauteur noire d’un noeud x, le nombre de

noeuds présent dans la branche descendant de ce noeud vers une feuille.

On la notera hn(x).

93

Lemme Un arbre rouge et noir comportant n noeuds internes a une hauteur

au plus égale à 2 log(n + 1).

Preuve

– un arbre enraciné en x quelconque contient au moins

2hn(x) − 1

noeuds internes (par récurrence).

– Soit h la hauteur de l’arbre. Au moins h/2 noeuds sont noirs, donc

hn(r) ≥ n/2

Donc selon le point précédant

2h/2 − 1 ≤ n

Implémentation Afin de simplifier les algorithmes, on va représenter différemment

les feuilles d’un arbre rouge et noir. Au lieu de représenter les feuilles par

une simple référence NULL, nous allons utiliser la technique des sentinelles

et considérer que les feuilles sont représentées par un noeud externe d’une

référence donnée.

∅()

b = structure[NoeudBinaire,NoeudBinaire](racine,feuille)

b.feuille = NoeudBinaire(NULL,NULL,NULL,NULL)

b.racine = feuille

retourner b

94

<,>(r,b1,b2)

n = NoeudBinaire(r,b1.racine,b2.racine, NULL)

b1.racine.parent ←n b2.racine.parent ←n b = structure[NoeudBinaire](racine)

b.racine = n

b.feuille = b1.feuille

retourner b

RACINE(x)

retourner x.racine

GAUCHE(x)

retourner x.racine.gauche

DROIT(x)

retourner x.racine.droit

Rotations

Les opérations de rotations permettent de modifier les couleurs des noeuds

et de changer leurs châınage. Elles sont utiles pour transformer l’arbre en cas

de violation de ses propriétés aprés une insertion ou une suppression.

Rotations gauche et droite. Donner l’exemple et l’algorithme p261 du

95

Cormen.

Insertion

Exemple et code p263 du Cormen.

Durée : 1H30

Suppression

Exemple et code p267 du Cormen.

Durée : 1H30

4.1.11 Les tas

Définition

Arbre binaire presque complet. Implémentation possible dans un tableau.

– Longeur : nombre d’éléments du tableau.

– Taille : nombre d’élements du tas.

Donner parent, gauche et droit Cormen p139.

Propriété de tas.

A[pere(i)] ≥ Ai

Conservation de la structure de tas

– Procédure entasser(A,i) : i peut violer la propriété de tas : on va le faire

descendre.

– Calcul complexité : T (n) ≥ T (2n/3) + Θ(1). O(lg n).

96

Construction d’un tas

– Construire-tas(A) : un tableau quelconque est transformé en tas. On

appelle la procédure entasser en partant de la fin du tableau.

– Borne large : O(lg n) pour entasser et O(n) pour le nombre d’appels.

O(n lgn)

– Borne plus précise : en tenant compte des profondeurs différentes des

sous-tas à traiter. remarque : le nombre de noeuds dont le sous-arbre

est de profondeur p dans un tas à n sommet est égal au nombre de

noeuds de profondeur lg n− p + 1. Ce nombre est égal à

2lg n−p+1 = 2lg n

2p+1 =

n

2p+1

pour les arbres binaires complets, et donc borné par cette valeur pour

les tas. Le temps requis par ENTASSER pour un tas de profondeur p

étant O(p), on peut écrire pour le temps de CONSTRUIRE-TAS :

lg n ∑

p=0

n

2p+1 O(p) = O(n

lg n ∑

p=0

p

2p )

Or ∑

k=0 kx k = x

(1−x)2 . Donc en substituant x = 1/2, on a

p=0 p/2 p =

1/2 (1−1/2)2

= 2. Donc

O(n lg n ∑

p=0

p

2p ) = O(n

∞ ∑

p=0

p

2p ) = O(n)

Code et exemple. Cormen p143-144.

97

Tri par tas

– On se sert de CONSTRUIRE-TAS pour construire le tas sur le tableau

d’éntrée. Ensuite, on échange l’élément se trouvant à la racine avec

celui se trouvant en fin de tableau. Puis, on exclut cet élément et on

corrige éventuellement la racine avec ENTASSER(A,1).

– Complexité : O(n lgn).

Files de priorité

SorteFile de priorité

Paramètre élément

Utilise booléen

Opérations ∣

file− priorite :→ file− priorite inserer : file− priorite× élément→ file− priorite maximum : file− priorite→ élément extraire−max : file− priorite→ file− priorite

Applications. Plannification des tâches sur un ordinateur à ressources par-

tagées. Simulateur événementiel.

Implémentation. Tas.

98

Code et exemple. Cormen p 147 et 148.

– EXTRAIRE-MAX : ressemble à la procédure TRIER-TAS. On extrait

la racine. Complexité O(lg n).

– INSERER : On met l’élément supplémentaire à la fin et on le fait re-

monter. Complexité O(lg n).

Durée : 1H30

99

Table des figures

100

commentaires (0)
Aucun commentaire n'a été pas fait
Écrire ton premier commentaire
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Docsity n'est pas optimisée pour le navigateur que vous utilisez. Passez à Google Chrome, Firefox, Internet Explorer ou Safari 9+! Téléchargez Google Chrome