\documentclass[11pt]{article}
\usepackage{amsmath,stmaryrd,amssymb,fancybox,fancyhdr,lastpage,a4,mdwlist,multicol}
\usepackage[french]{babel}
\usepackage[utf8x]{inputenc}
\usepackage{tikz}
\usepackage{tikz,tkz-tab,tkz-fct}
\usepackage[algoruled,french,onelanguage,linesnumbered]{algorithm2e}
\usepackage{mdwlist}
\topmargin-0.5cm
\headheight20pt
\textheight23cm
\textwidth17.5cm
\oddsidemargin-1cm
\evensidemargin0cm
\parindent0pt
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\usepackage{amsfonts,graphicx,fancybox,fancyhdr,lastpage}
\usepackage{boxedminipage}
\setcounter{MaxMatrixCols}{10}
%TCIDATA{OutputFilter=LATEX.DLL}
%TCIDATA{Version=5.50.0.2953}
%TCIDATA{}
%TCIDATA{BibliographyScheme=Manual}
%TCIDATA{Created=Thursday, September 11, 2014 16:21:49}
%TCIDATA{LastRevised=Thursday, November 29, 2018 07:17:55}
%TCIDATA{}
%TCIDATA{}
%TCIDATA{CSTFile=CORRIGE.cst}
\input{tcilatex}
\begin{document}
\pagestyle{fancy}
\lhead{{\bf MP}}
\chead{}
\rhead{Corrig\'e TP : arbres binaires}
\lfoot{\sl Lyc\'ee Chateaubriand}
\cfoot{}
\rfoot{\thepage/\pageref{LastPage}}
\renewcommand{\footrulewidth}{0.4pt}
%\SetTitle{PSI* -- 2018/2019 -- Informatique -- Corrig\'{e} du Th\`{e}me 1}
%\SetAuthor{}
%\Setdate{}
%\TitlePage{}
%
%~\vspace{-1cm}
\textbf{Question 1}\vspace{0.2cm}
Pas de difficult\'{e} avec les listes Python~!
\setlength{\columnseprule}{0.5pt} \setlength{\multicolsep}{5pt}
\begin{multicols}{2}
\begin{verbatim}
def est_vide(a):
return a==[]
def arbre_vide():
return []
def cons(e,g,d):
return [e,g,d]
\end{verbatim}
\vfill
\columnbreak
\begin{verbatim}
def contenu(a):
return a[0]
def f_g(a):
return a[1]
def f_d(a):
return a[2]
\end{verbatim}
\vfill
\end{multicols}\vspace{0.2cm}
\textbf{Question 2}\vspace{0.2cm}
Comme il est plus facile de tester si un arbre est vide que de tester s'il
contient un unique n\oe ud, on convient que la hauteur de l'arbre vide est $%
-1$, ce qui permet d'\'{e}tendre au cas de l'arbre vide la relation%
\begin{equation*}
h\left( a\right) =1+\max \left( h\left( fils\_g\left( a\right) \right)
,h\left( fils\_d\left( a\right) \right) \strut \right) .
\end{equation*}%
Cette relation se pr\^{e}te \'{e}videmment \`{a} une programmation r\'{e}%
cursive~:\vspace{0.2cm}
\begin{boxedminipage}{11cm}
\begin{verbatim}
def hauteur(a):
if est_vide(a):
return -1
else:
return 1+max(hauteur(f_d(a)),hauteur(f_g(a)))
\end{verbatim}
\end{boxedminipage}\vspace{0.2cm}
J'utilise la fonction \texttt{max} de Python. Terminaison justifi\'{e}e par
la d\'{e}croissance stricte de la hauteur de l'arbre en param\`{e}tre \`{a}
chaque appel r\'{e}cursif et preuve par r\'{e}currence sur ladite hauteur~!%
\vspace{0.2cm}
\textbf{Question 3}\vspace{0.2cm}
Je construis r\'{e}cursivement la cha\^{\i}ne de caract\`{e}res souhait\'{e}%
e~:
\begin{itemize}
\item si l'arbre est vide, je renvoie la cha\^{\i}ne vide
\item si l'arbre est une feuille (deux fils vides), je renvoie la cha\^{\i}%
ne repr\'{e}sentant le nombre en \'{e}tiquette (conversion avec \texttt{str})
\item sinon, l'\'{e}tiquette est un op\'{e}rateur, je renvoie donc une
parenth\`{e}se, suivie de la cha\^{\i}ne associ\'{e}e au sous-arbre gauche
(premier op\'{e}rande), de l'\'{e}tiquette puis de la cha\^{\i}ne associ\'{e}%
e au sous-arbre droit (second op\'{e}rande).
\end{itemize}
D'o\`{u} le programme~:\vspace{0.2cm}
\begin{boxedminipage}{16.7cm}
\begin{verbatim}
def parcours_infixe(a):
if est_vide(a):
return ''
elif est_vide(f_g(a)) and est_vide(f_d(a)):
return str(contenu(a))
else:
return '('+parcours_infixe(f_g(a))+contenu(a)+parcours_infixe(f_d(a))+')'
\end{verbatim}
\end{boxedminipage}\vspace{0.2cm}
\textbf{N.B.}~: la d\'{e}nomination \textquotedblleft \emph{parcours infixe}%
\textquotedblright\ vient du fait que l'\'{e}tiquette est \'{e}crite \textbf{%
entre} les cha\^{\i}nes correspondant aux sous-arbres. Le r\'{e}sultat pour
l'arbre de l'\'{e}nonc\'{e} est
\begin{equation*}
\fbox{\texttt{((3+(5x(7+2)))-(4+(8x9)))}}
\end{equation*}%
On peut de m\^{e}me programmer r\'{e}cursivement un parcours \emph{pr\'{e}%
fixe }(avec l'op\'{e}rateur avant les op\'{e}randes, comme dans le langage
Lisp, ou comme $f\left( x,y\right) $ en maths~!) ou un parcours \emph{%
postfixe} (avec l'op\'{e}rateur apr\`{e}s les op\'{e}randes, comme dans la
\emph{notation polonaise inverse}, cf. le chapitre 3). Ainsi, le programme
suivant~:\vspace{0.2cm}
\begin{boxedminipage}{17.5cm}
\begin{verbatim}
def parcours_postfixe(a):
if est_vide(a):
return ''
elif est_vide(f_g(a)) and est_vide(f_d(a)):
return str(contenu(a))
else:
return parcours_postfixe(f_g(a))+' '+parcours_postfixe(f_d(a))+' '+contenu(a)
\end{verbatim}
\end{boxedminipage}\vspace{0.2cm}
donne pour l'arbre de l'\'{e}nonc\'{e} la cha\^{\i}ne suivante~: \texttt{3 5
7 2 + x + 4 8 9 x + -}\vspace{0.2cm}
Avec cette notation les parenth\`{e}ses ne sont pas n\'{e}cessaires, un
espace suffit pour s\'{e}parer deux symboles.\vspace{0.5cm}
\textbf{Question 4}\vspace{0.2cm}
J'applique l'indication de l'\'{e}nonc\'{e}~:
\begin{itemize}
\item j'ajoute l'\'{e}l\'{e}ment $x$ \`{a} la fin de $t$ (m\'{e}thode
\texttt{append})~; $x$ se trouve alors dans $t\left[ i\right] $ o\`{u} $i$
vaut $len(t)-1$ (rappelons que $t\left[ 0\right] $ n'est pas utilis\'{e})
\item tant que $t\left[ i\right] $ a un p\`{e}re (condition $i>1$) et est
plus petit que son p\`{e}re $t\left[ p\right] $ (o\`{u} $p=i//2$), j'\'{e}%
change $t[i]$ et $t\left[ p\right] $ et j'actualise les valeurs de $i$ et de
$p$\vspace{0.2cm}
\end{itemize}
D'o\`{u} le programme~:\vspace{0.2cm}
\begin{boxedminipage}{10.2cm}
\begin{verbatim}
def entasser(x,t):
t.append(x)
i=len(t)-1 #l indice de cette derniere valeur
p=i//2 #le pere
while i>1 and t[i]t\left[ f%
\right] $. \vspace{0.1cm}
Si $t\left[ i\right] \leq t\left[ f\right] $ on peut sortir, d'o\`{u} le
\texttt{return None}, puisque nous n'avons rien \`{a} renvoyer, le r\'{e}%
sultat de l'ex\'{e}cution de cette \emph{proc\'{e}dure} est la modification
de \texttt{t}.
D'o\`{u} le programme~:\vspace{0.2cm}
\begin{boxedminipage}{7.7cm}
\begin{verbatim}
def suppr_min(t):
t[1]=t[-1]
t.pop()
n=len(t)-1
i=1
while i<=n//2:
if 2*i==n or t[2*i]t[f]:
t[i],t[f]=t[f],t[i]
i=f
else:
return None
\end{verbatim}
\end{boxedminipage}\vspace{0.2cm}
Noter que les deux premi\`{e}res lignes peuvent \^{e}tre remplac\'{e}es par
\texttt{t[1]=t.pop()}, \textbf{sauf} pour un tableau d'une seule valeur, qui
doit \^{e}tre vid\'{e} par notre fonction.\vspace{0.2cm}
\textbf{Question 6}\vspace{0.2cm}
Selon le pr\'{e}ambule, je remplis un tableau auxiliaire $tas$ (initialis%
\'{e} avec un 0 en premi\`{e}re place, qui restera inutilis\'{e}\ldots ) o%
\`{u} j'entasse les valeurs du tableau initial $ti$, puis je reremplis $ti$
avec les valeurs (dans l'ordre croissant~!) extraites de $tas$ l'une apr\`{e}%
s l'autre (l'utilisation de $suppr\_min$ assure que la racine de $tas$ est
la plus petite de ses valeurs \`{a} chaque \'{e}tape~!).\vspace{0.2cm}
\textbf{N.B.} Les $n$ valeurs\ de $ti$ sont bien stock\'{e}es dans $tas\left[
1\right] ,\ldots ,tas\left[ n\right] $ pour b\'{e}n\'{e}ficier des liens p%
\`{e}re-fils de l'\'{e}nonc\'{e}\ldots \vspace{0.2cm}
La complexit\'{e} en $O\left( n\log _{2}n\right) $ est justifi\'{e}e dans l'%
\'{e}nonc\'{e} (au \textbf{II-4}). On remarquera que cet algorithme
(diabolique) donne un programme de tri efficace et \textbf{it\'{e}ratif}~!
Voici le programme~:\vspace{0.2cm}
\begin{boxedminipage}{5.4cm}
\begin{verbatim}
def tri_tas1(ti):
tas=[0]
for x in ti:
entasser(x,tas)
ti=[]
while len(tas)>1:
ti.append(tas[1])
suppr_min(tas)
return ti
\end{verbatim}
\end{boxedminipage}\vspace{0.2cm}
\textbf{Question 7}\vspace{0.2cm}
Pour effectuer les op\'{e}rations pr\'{e}c\'{e}dentes sans tableau
auxiliaire, je donne comme param\`{e}tre \`{a} la fonction $entasser$ l'%
\textbf{indice} $i$ au lieu de la \textbf{valeur} $x$ (ajout\'{e}e \`{a} la
fin du tas en construction, \`{a} la question \textbf{6}).\vspace{0.2cm}
Pour l'extraction des valeurs dans l'ordre, au lieu de supprimer la valeur
\`{a} la racine comme \`{a} la question \textbf{7}, je l'\'{e}change avec $t%
\left[ n\right] $ ($n$ \'{e}tant fourni en param\`{e}tre) et je recr\'{e}e
un tas avec les valeurs $t\left[ 1\right] ,\ldots ,t\left[ n-1\right] $.%
\vspace{0.2cm}
Bien entendu, la m\'{e}thode ci-dessus va rendre les valeurs initiales en
ordre d\'{e}croissant si je cr\'{e}e des tas avec la plus petite valeur \`{a}
la racine\ldots\ Il suffit d'inverser le sens des in\'{e}galit\'{e}s dans
les tests pour retrouver l'ordre croissant~!
Ci-dessous les programmes (\`{a} peine) modifi\'{e}s~:\vspace{0.2cm}
\begin{boxedminipage}{13.5cm}
\begin{verbatim}
def entasser(i,t): #entasse t[i] parmi t[1],...,t[i-1]
p=i//2 #le pere
while i>1 and t[i]>t[p]:
t[i],t[p]=t[p],t[i]
i,p=p,p//2
def suppr_min(t,n): #echange t[1] et t[n] et
#refait un tas avec les n-1 premieres valeurs
t[1],t[n]=t[n],t[1]
n-=1
i=1
while i<=n//2:
if 2*i==n or t[2*i]>t[2*i+1]:
f=2*i
else:
f=2*i+1
if t[i]t[2*r+1]:
f=2*r
else:
f=2*r+1
if t[r] x : self.gauche.ajoute(x)
elif self.e < x : self.droite.ajoute(x)
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 11}\vspace{0.2cm}
\begin{boxedminipage}{13cm}
\begin{verbatim}
def recherche(self,x):
if self.vide : return False
elif self.e == x : return True
elif self.e > x : return self.gauche.recherche(x)
else : return self.droite.recherche(x)
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 12}\vspace{0.2cm}
\begin{boxedminipage}{16cm}
\begin{verbatim}
def profondeur(self):
if self.vide : return -1
return 1+max(self.gauche.profondeur(),self.droite.profondeur())
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 13}\vspace{0.2cm}
\begin{boxedminipage}{10cm}
\begin{verbatim}
def arbreToTab(self,t,i):
if not self.vide:
t[i]=self.e
self.gauche.arbreToTab(t,2*i)
self.droite.arbreToTab(t,2*i+1)
def affiche(self):
nMax = 2**(self.profondeur()+1)
t = [-1]*nMax
self.arbreToTab(t,1)
afficher(t)
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 14}\vspace{0.2cm}
\begin{boxedminipage}{16cm}
\begin{verbatim}
def taille(self):
if self.vide : return 0
return 1+self.gauche.taille()+self.droite.taille()
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 15}\vspace{0.2cm}
\begin{boxedminipage}{16cm}
\begin{verbatim}
def tri(self):
n = self.taille()
t = [-1]*(n)
self.range(t,0,n-1)
return(t)
def range(self,t,i,j):
if i == j :
t[i]=self.e
print(self.e,i,j)
else :
if not self.gauche.vide :
kg = self.gauche.taille()
t[i+kg]=self.e
self.gauche.range(t,i,i+kg-1)
if not self.droite.vide :
self.droite.range(t,i+kg+1,j)
else :
t[i]=self.e
self.droite.range(t,i+1,j)
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\textbf{Question 16}
Le tri par arbre binaire de recherche consiste \`a construire un arbre binaire de recherche contenant tous les \'el\'ements de l'ensemble \`a trier et \`a faire le parcours infixe de cet arbre. On obtient une complexit\'e en $O(h*n)$. Dans le pire cas, $h = n$ et la complexit\'e n'est pas satisfaisante. Mais si les \'el\'ements ont \'et\'e m\'elang\'es al\'eatoirement, on a une hauteur moyenne en $O(\log n)$. Le tri par arbre binaire de recherche a alors une complexit\'e moyenne en $O(n\log n)$ ce qui est optimal.
{\bf Remarque :} il existe plusieurs variantes des arbres binaires de recherche qui maintiennent un certain \'equilibre de l'arbre et permettent d'assurer une hauteur logarithmique. Dans ce cas, la complexit\'e pire cas du tri est \'egalement un $O(n\log n)$.
\vspace{.5cm}
\textbf{Question 17}\vspace{0.2cm}
\begin{boxedminipage}{16cm}
\begin{verbatim}
def affectation(self,n) :
v,e,g,d = n.vide,n.e,n.gauche,n.droite
self.vide = v
self.e = e
self.gauche = g
self.droite = d
def rechEtSupprMax(self):
if self.droite.vide :
res = self.e
self.affectation(self.gauche)
return res
elif self.droite.gauche.vide and self.droite.droite.vide :
res = self.droite.e
self.droite.vide = True
return res
else :
return self.droite.rechEtSupprMax()
def supprRacine(self):
if self.gauche.vide :
self.affectation(self.droite)
elif self.gauche.gauche.vide and self.gauche.droite.vide :
self.e = self.gauche.e
self.gauche.vide = True
elif self.gauche.droite.vide :
self.e = self.gauche.e
self.gauche.affectation(self.gauche.gauche)
else :
self.e = self.gauche.rechEtSupprMax()
def suppr(self,x):
if not self.vide :
if self.e == x and self.gauche.vide and self.droite.vide :
self.vide = True
elif self.e == x : self.supprRacine()
elif self.e > x and not self.gauche.vide : self.gauche.suppr(x)
elif self.e < x and not self.droite.vide : self.droite.suppr(x)
\end{verbatim}
\end{boxedminipage}\vspace{0.5cm}
\end{document}