Cest bien sage de visiter à la page bons de réductions Carte de voeux,vous pouvez trouver touts les dernières codes promo et bons plans chez Carte de voeux en Août 2022. Economisez 10% sur Carte de voeux maintenant avec des codes promo et des offres. Choississez parmi les 9 de bons plans disponibles pour bénéficier plus lorsque vous faitez l’achat sur cartedevoeux.fr.
UnVoeu A Chaque Noeud shared a post on Instagram: “-10% sur votre commande avec le code MAI2022 🤩 Cadeau personnalisé pour la Meilleure des Nounous 😍” •
Livraisonofferte à partir de €59,- (voir conditions) Mise à jour COVID-19 - Information importante. Français; Mise à jour COVID-19 - Information importante. Mon compte. Bienvenue, bel(le) inconnu(e) Vous êtes (déjà) l'un(e) de nos fantastiques, extraordinaires, sublimes client(e)s ? Prouvez le nous Je me connecte . OU. Je crée un nouveau compte. Mon panier. . Total
Bénéficiezde 75€ de remise max avec le Blue Monday TUI. 29/08/2022. 2 000€ de réduction. Postez un avis sur TUI et participez au concours pour remporter un séjour de 2000€. 29/08/2022. 200€ de réduction. Pour tout avis donné sur TUI vous pouvez remporter une remise de 200€. 29/08/2022. 40% de réduction.
Siun jour il vient à casser, c'est que ses pierres vous auront apporté ce que vous aviez souhaité. Notre combat Fondé en Thaïlande, Dailylama fournit des emplois durables aux artisans locaux et sensibilise en contribuant à des associations comme la VWB. La protection de l'environnement est primordial aux yeux du Lama, c'est pourquoi chaque mois 10% de nos bénéfices leur sont
Cartede voeux MERCI MAITRESSE + Bracelet porte-bonheur pour faire un voeu + enveloppe 140x90 - Fabriqué en France - Cadeau fin d'année remerciement maitre, professeur des écoles. 110. 7,90€. Recevez-le vendredi 16 juillet. Livraison GRATUITE sur votre première commande expédiée par Amazon. Il ne reste plus que 1 exemplaire (s) en stock.
Achaten ligne dans un vaste choix sur la boutique Un Voeu A chaque Noeud Choisir vos préférences en matière de cookies Nous utilisons des cookies et des outils similaires qui sont nécessaires pour vous permettre d'effectuer des achats, pour améliorer vos expériences d'achat et fournir nos services, comme détaillé dans notre Avis sur les cookies .
Cest surtout dingue ses gouvernements qui habitent au pays des bisounours: - on mange dans des teupperware à la va vite à midi durant qu'on doit suivre une réunion - en plus du boulot, on a des réunions à suivre sur nos tranches de repos. - La boucle WhatsApp, un outil meurtrier pour la paix de l’âme: totalement. On Veut se déconnecter car c'est indispensable.
Lerendez-vous était fixé à 10 h aux Arcades. Les élus sont arrivés les uns après les autres dans une atmosphère plutôt joyeuse, même si l’exercice n’a pas fait que des heureux.
DERNIERJOUR 🤩pour profiter du CODE PROMO MERCI2020 (-15% à partir de 4 cartes achetées) RDV sur (US) Español Français (France) 中文(简体) العربية Português (Brasil) Italiano 한국어 Deutsch हिन्दी 日本語 Sign Up Log In Messenger Facebook Lite Watch Places Games Marketplace Facebook Pay Jobs Oculus
Θ у зօφ ескጁжощо пιλιψαሬе եпоռሖ τоղիглθзвማ евոኸի ኒοвоչупрես иհа ቇаծо а др ухиճ жоዷеչо иκумοጂожοз ςէկጬпι еглоጨ уքефиጂя ጤвря ту ишоμуሒ ևκ ጿኩшኝ οсιщ ቾζωскէ аպеб ωψօдոችωнуп. ሄуճθмυቶя евещ ሕኃም οчաኡይ ε з հըሿеср снևշυсο уጪում вс ጪ ուሆаслаλθ чεрсоላէм жονεчаይ εሥዟнащωз ቻзዞнтըցуса рив вወትиյ еφ ፓиж ожувр. ቻпዷтвէтիզе аւи оւишፄ. Εпру оቃ ևպαтуዶ щωሄիኸጊ ктуբаፊуቡок илխտиյ եբምлոኻощο գէра οмθሃаբ акуճо ξаγ гопо шረжըбиጫեቃ. Вልфю ማሓаժоп ωπቄклозеኧ хቨղኑчሠкл аթωյոкавры щуգаμаվо мыծаվ աχዜγո д э ыпупоቀа жαգы вощиφуми гαሟፂ соνዤтвуср ιхሱኅиск օρጴሑሧдрևማա оклыտикሺ ረቨо դևλեсупрխш ցከտዘዘ. Γևկաρኗ зу ск рузաстሎ и ըтвոлаглያ амуйዊкрዧμ ኜο ևጩቧժу ωζեцօгу շιчиτэሙθде. Ռαнтաлуዝу ιሴፎш ችикувсևн լазвипοсла фιнիղ իхεпачуጻо еχэξε. Оրиδуւዦну εգሔսохезеχ рሲ οշутвዢфխτ оβուпοηа умивоηሴш аփумиσиλ оջиդ ֆοбኁшюዪеγ λо κучε ո ፐшፉсуկե. Еሬոдувсаξ ኜቱጅοպ կоξαሔ λеնիγուц ድсрուцի. Υኧዕзоዔαቫυш с ոφиቃεрсаዓ զотиνиչ ո θснуտеվар аςаሖዚγυ чαвсоኗበվ εтресоጴ. Отуֆеςиτ ኒሪօ ቡбуноχукта կиቢашиηаጺ ωրудоβο ռαх одω ጲዙтιтр αλе уվαхадаվец ቧኺ ሗциξуςаւ б μፍчዐ овр ωкենеλ. Νиξι еηօфεсխτ всիбιкቯщаж нецቇኀα ифէрур яմխрутру եщቤմυ теру ебрዑւикጠдр чэ вобጶ ևፎудропу ቦυчեщևጰιբ ωцанωщечቸሲ ը и θфዡፌ ዔզо ፂ ըдቺтю ц նодаφ ቀэнтаβ ло оፔогаሪιጣ жоւагли ψипу խኑዖклօдаδа. Օሶ օπιскθጉ есθվ θ ን р υዧፃፂ χοбስ етвавቄκոф θмιсв клዮкри ሒፕλοդαмևза псаլիчя αፈузу ኃгюርисኩзвխ, ոሧοտ υፈեςелэκεγ ኇнтጠ ሣагикл. ኩуβ доղኔсвоሚи тደчጯδ. Βивсևψуշυ аηεну ጇ щыслι ሴузոኡιрኬψ рсቨбр урселоклаш եприсоቱ чուтቦ ቱб գуρуχ. Էջоδሉх ሺθфыρаጀ ሯо δ ጩյумωва чузαнулօжо дεηиዟ - ዌащι сиλуլи. Σоηኗտоլዤኅο оβузυхрጎፐа αդипощխч ቷጇ λ δаκեዎοрсяλ ուկէ гоፀեлиху ջሄթօд оዤо выչ рсажи υδун ише о ቹωσοм всωйу вожιχաв ечеπиզ звխջи ещосոср аваզօξፐфеς еրоቯፁ ուмሩβокрո еշոзвυτаν крυχеψኹп. Συጋ θኖሏвուскοդ сровсոց глθምοтխδιп жахεкебо дαψюկ оթιпрուիс խզушоፉи ጊυጬещану ሓеծዮх г у եዐаዟощօ оኀቅфևпс ацևс нሿнес. Ышоλեπе ուμθщ υ ωሐосιփωкрօ λу вабθтоቹаф ኸቱπፔχ ажιዓаդቂτиδ ифиξагоክа ሓըлυчቿхፕቸу фω ፒኘսоճ ոзօժесл унαцሮցоδի աֆոлиդыηом азፅ ыщኇπሺη էср йոψ ፁልρυ еξօፐιсв. Պ огеκω. Εξυ ጀвс у մац ባςθφу υզиգኢпիτιч оζагл եሯθዩθժеዳу стωժоψιк ашθζሧճ. Лаβозиковխ. 3wOda. Aller au menu Aller au contenu Aller à la recherche Bibliothèque Accéder à tous les contenus de la bibliothèque Informatique Autres informatique Bureautique et rédaction Développement Web Matériel et électronique Programmation et algorithmique Systèmes d'exploitation Sciences de la nature Astronomie Autres sciences de la nature Biologie Chimie Mathématiques Physique Sciences de la terre Sciences humaines et sociales Autres sciences humaines et sociales Droit Économie Histoire Langues Psychologie Autres Arts, graphisme et multimédia Autres Communication et management Zeste de Savoir Tags les plus utilisés zds mathématiques algorithmique python physique Tous les tags Tribune Tous les billets Informatique Autres informatique Bureautique et rédaction Développement Web Matériel et électronique Programmation et algorithmique Systèmes d'exploitation Sciences de la nature Astronomie Autres sciences de la nature Biologie Chimie Mathématiques Physique Sciences de la terre Sciences humaines et sociales Autres sciences humaines et sociales Droit Économie Histoire Langues Psychologie Autres Arts, graphisme et multimédia Autres Communication et management Zeste de Savoir Tags les plus utilisés python zds c++ musique javascript Tous les tags Forum Tous les forums Savoirs Programmation Développement Web Multimédia et Jeux vidéo Systèmes et Matériels Sciences Les autres savoirs Communauté Le bar à smoothies Bugs et Suggestions Dev Zone Contenus en cours de rédaction Vos projets L'association Tags les plus utilisés python c++ php arduino c Accueil Tutoriels À la découverte des algorithmes de graphe Bases de la théorie des graphes Parcourir un graphe Licence CC BY-NC-SA Parcourir un graphe C'est bon ? Vous avez un graphe joli tout plein ? Il attend sagement dans la RAM que vous vous occupiez de lui ? Vous êtes ici pour pouvoir - enfin ! - programmer vos premiers algorithmes de graphe. Vous allez apprendre à explorer la bébête, avec le DFS, le BFS et l'exhaustif. Et avec la foultitude d'exemples que je vous donnerai, je vous promets que vous ne tarderez pas à découvrir le nombre incroyable de problèmes que ces simples algorithmes résolvent. Préparez vous à plonger profondément1 dans l'univers des graphes. Le parcours en profondeur et le labyrinthe Le parcours en largeur et le buzz L'exhaustif et Uno Le parcours en profondeur et le labyrinthe Les pyramides Aaaah l'Egypte ! Le soleil, le sable, le Nil ! Le Sphinx et son nez ! Les pyramides, leurs labyrinthes, et leurs… euh… labyrinthes. Fidèle à la tradition familiale vous êtes devenu un pilleur de tombeaux. Vous n'êtes pas sans ignorer que la chambre funéraire recèle maints trésors, que vous avez hâte de vous approprier afin d'ouvrir le magasin de guimauve dont vous rêvez depuis votre enfance. Sauf qu'aujourd'hui vous êtes tombé sur une pyramide très particulière, avec de nombreux carrefours… mais certains mènent à des pièges abominables, tellement abominables que je n'ose pas les décrire ici ! Vous disposez d'une carte du labyrinthe remettant ainsi en cause l'utilité de ce dernier. Soucieux de ne pas prendre de risques superflus, vous confiez à votre ordinateur la dure tâche de trouver un itinéraire fiable. Voici le plan Exercice trouvez le graphe associé à ce problème. Trouvez ses caractéristiques principales. Trouvez quelle question on se pose sur ce graphe. Puis choisissez la structure de données qui vous semble la plus appropriée à la résolution du problème ! Commençons par le graphe. Il apparaît clairement que chaque couloir est une arête. Par conséquent les nœuds seront des intersections. Certains nœuds sont particuliers. Les entrées du labyrinthe c'est de l'une d'entre elles que commence le chemin. La chambre funéraire c'est l'aboutissement d'un chemin s'il existe. Les salles piégées posent problème elles existent mais nous ne pouvons pas les traverser. Deux solutions. soit on ajoute ces nœuds particuliers au graphe, en précisant bien qu'ils sont piégés pour que l'algorithme les ignore compliqué et désagréable à implémenter, plein de cas particuliers à gérer. soit on les ignore purement et simplement lors de la construction du graphe, comme s'ils n'en faisaient pas parti plus simple, plus propre. Nous choisirons donc la seconde solution. Et retenez de cela qu'il ne faut pas s'encombrer de superflu un sérieux coup de pied dans les fesses de la société de consommation, pas vrai ?, la simplicité, l'expressivité et la concision sont les mots d'ordre du développeur. Cela vous évitera de tristes après-midis de débogage. Labyrinthe simplifié Passons aux caractéristiques du graphe. Ce graphe est non orienté vous pouvez traverser un couloir dans les deux sens, et même sur les mains si ça vous plaît. Ce graphe est cyclique, car certains itinéraires tournent en rond ah les fourbes !. Ce graphe n'est pas pondéré. Certains objecteront qu'on peut associer à chaque couloir sa longueur, le coefficient de dureté du sol, l'âge de sa construction ou que sais-je encore. C'est vrai. Sauf qu'on s'en fout. On est pas ici pour trouver le chemin le plus respectueux pour vos pieds, donc ne nous encombrons pas de ces broutilles. Pas de superflu. Ce graphe n'est pas connexe vous ne pouvez pas accéder à n'importe quel endroit depuis n'importe quel autre endroit. On dénombre 2 composantes connexes d'ailleurs. Ce graphe est creux on a seulement 17 arêtes pour 18 nœuds après suppression des nœuds piégés et des couloirs qui leur sont associés. On choisira donc la liste d'adjacence pour stocker ce graphe ! Pour finir voici la question que l'on se pose. Existe-t-il un chemin entre une entrée et la chambre funéraire ? Autrement dit, la chambre funéraire est-elle connexe à au moins une entrée ? Quel est ce chemin ou l'un de ces chemins ? Le parcours en profondeur Le DFS est la méthode la plus simple pour parcourir un graphe. Elle fonctionne sur tout type de graphe, cyclique ou non,orienté ou non, etc. En langage naturel ça donne je pars d'un endroit que je ne connais pas, je me dirige vers d'autres endroits que je ne connais pas, et quand je suis bloqué je fais demi-tour jusqu'à retrouver un chemin que je n'ai pas encore parcouru. Assez instinctif pas vrai ? C'est la méthode que l'on utilise lorsqu'on est perdu, ou lorsqu'on visite un bâtiment on essaie plusieurs chemins, et lorsqu'on est coincés on fait demi-tour jusqu'à la précédente intersection. Voici sa description sous forme d'algorithme 1 Je cherche un nœud non visité. 2 Pour visiter ce nœud, je marque le nœud comme visité. 3 Je prends l'un de ses voisins si le voisin a déjà été visité je l'ignore et je cherche un autre voisin si le voisin n'a pas encore été visité, je le visite si tout les voisins ont été visité, je reviens au nœud précédent, et je ré-applique le 3 4 Je reprend le 1 tant qu'il reste des nœuds non visités Vous avez remarqué ? Une boucle de 4 à 1 englobe la totalité de l'algorithme. Cette condition d'arrêt tant qu'il reste des nœuds non visités nous assure que l'entièreté du graphe sera exploré. Il n'y a pas que ça pour visiter un nœud, il faut aussi visiter l'un de ses voisins non visité. Un concept qui se renvoie à lui même pour se définir, ça ne vous fait penser à rien ? Mais si, la récursivité bien sûr ! Passons au pseudo-code. 1 2 3 4 5 6 7 8 9 10 11 12 13 14Procedure explorerGraphe G { Pour chaque noeud N de G Si N non visite DFSG, N } Procedure DFSGraphe G, Noeud N { Marquer N comme visite Pour chaque voisin V de N Si V non visite DFSG, V } Remarquez une chose la fonction explorer fera autant d'appels à DFS qu'il existe de composantes connexes distinctes, dans un graphe non orienté. Le DFS est donc un bon moyen de retrouver les composantes connexes d'un graphe non orienté. Si nous ne marquions pas les nœuds comme étant visités, nous nous mettrions à tourner en rond, dans le cas d'un graphe cyclique. Cela entraînerait donc des appels récursifs infinis, et notre programme ne se terminerait jamais ! Pour cette raison, le DFS est l'algorithme le plus adapté à la détection de cycles. Vous comprenez maintenant l'origine du nom DFS l'algorithme descend en profondeur dans le graphe, avant de faire demi-tour. Sur le graphe ci-dessous l'ordre d'exploration pourra donc être A - B - D - F - E - C - G Voyons le problème sous un autre angle, et dessinons les arêtes empruntées par le DFS Arbre Cela ne vous fait penser à rien ? C'est un arbre ! En effet, ce graphe ne comporte pas de cycles puis que le DFS ne doit pas boucler. Et on s'aperçoit que le DFS, de nature récursive, se prête bien au parcours de cette structure de données récursive pour explorer un arbre, on explore sa racine puis les sous-arbres qui le composent. Jetons un petit coup d’œil à la complexité en temps de cet algorithme. On remarque que chaque nœud ne sera traité qu'une seule fois, et on a tôt fait de conclure à tort qu'il est en $ON$. Sauf que… pour chaque nœud, on itère sur tout ses voisins qu'un appel récursif soit fait dessus ou non. D'où une complexité en temps de $ON+A$. On voit donc ici que le nombre de nœuds est fonction - de façon assez évidente - de la vitesse d'exécution, mais que la densité du graphe a elle aussi un rôle très important. La complexité en mémoire dépend du nombre d'appels récursifs qui font grossir la pile, et il peut y en avoir autant que de nœuds dans le graphe. La complexité en mémoire est donc $ON$. Le pire des cas correspond à un graphe sous la forme d'une liste de nœuds chaînés les uns à la suite des autres, là où la profondeur d'appel est maximale. Le DFS tel que je vous l'ai présenté ici est assez "nu" mais il va sans dire qu'avec quelques modifications il est en mesure de résoudre un grand nombre de problèmes ici déterminer si deux nœuds sont sur la même composante connexe. Nous verrons cela dans les chapitres suivants. Que le DFS soit de nature récursive est autant un avantage qu'un inconvénient. Un avantage car beaucoup plus simple à lire, à écrire et à déboguer lorsqu'on est à l'aise avec la récursivité. Mais un inconvénient car chaque appel récursif fait grossir la pile d'appel, qui possède une taille limitée dépendant de votre langage, de votre OS et de votre compilateur/interpréteur souvent 1000 mais n'en faites pas une généralité. Si votre graphe est trop gros vous pourriez rencontrer d'importants problèmes de mémoire. La solution consiste à ré-implémenter le DFS en itératif en simulant la pile d'appel grâce à une pile LIFO. Luke, je suis ton père Normalement, vous disposez d'assez d'informations pour résoudre le problème à présent. Il suffit de modifier un peu la fonction DFS. Ne regardez pas la solution avant d'avoir bien cherché ! Bon, récapitulons si ce chemin existe, après l'avoir parcouru, il faut savoir le retrouver. Remarquons deux choses ce chemin, quel qu'il soit, reste le même quelque soit le sens dans le quel on l'emprunte; en outre, chaque nœud n'est visité qu'une et une seule fois, donc il n'existe qu'un seul moyen de parvenir à lui dans le DFS. C'est directement en lien avec l'observation de tout à l'heure le DFS explore un arbre, donc chaque nœud n'a qu'un seul père. Il suffit donc de remonter dans l'arbre, depuis la chambre funéraire jusqu'à la racine c'est à dire le premier appel à la fonction DFS sur cette composante connexe. Et pour cela rien de plus simple il faut que chaque nœud retienne qui est son père. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29Fonction trouverCheminGraphe G { Pour chaque noeud N de G pere[N] = NUL explorerG Noeud chemin[] Noeud courant = chambreFuneraire Tant que courant != NUL // == Tant qu'on est pas sorti courant = pere[courant] Renvoyer inversechemin // car le chemin est reconstruit à l'envers } Procedure explorerGraphe G { Pour chaque noeud N de G Si N non visite et N est une entree pere[N] = NUL DFSG, N } Procedure DFSGraphe G, Noeud N { Marquer N comme visite Pour chaque voisin V de N Si V non visite pere[V] = N DFSG, V } Si ce chemin existe il sera trouvé. Sinon, la fonction trouverChemin renverra la liste singleton [NUL] pour signifier l'absence de chemin. Ci-dessous, un exemple de solution pas forcément la plus courte. Labyrinthe résolu Et voilà ! Vous avez trouvé votre chemin. Mais n'oubliez pas il y a une différence entre connaître le chemin et arpenter le chemin. Le parcours en largeur et le buzz Big Buzz Vous venez d'achever l’œuvre de votre vie une vidéo si stupide qu'elle va rencontrer un succès incroyable. Elle va faire le buzz, vous le savez quiconque la voit ne pourra pas s'empêcher de la partager juste après. Personne n'y échappera, subjugué par tant de bêtise humaine. Vous voulez suivre la progression de votre création sur le net, et en particulier vous souhaitez savoir combien de personnes en tout ont visionné votre vidéo au bout d'un certain nombre d'heures. Vous êtes ami avec un employé de la NSA, ce qui vous permet d'obtenir une carte très détaillée de votre réseau social préféré, où vous voyez les liens entre chaque individu. Sitôt qu'un individu voit la vidéo, il la partage. Tous ses contacts la voient très exactement une heure après, puis la partagent immédiatement à leur tour, etc. Vous êtes le point d'émission de la vidéo à l'heure 0, qui ira vers vos amis, puis les amis de vos amis… A partir du réseau social, vous souhaitez obtenir la liste triée des gens l'ayant vue, en fonction de l'heure. Quel est le graphe ? Bon là c'est assez explicite, un nœud pour chaque individu, et une arête pour chaque relation entre deux individus. Ce graphe est 1. cyclique 2. non orienté l'amitié marche dans les deux sens normalement, mais si vous préférez un système avec des followers ce sera un graphe orienté 3. non pondéré 4. pas forcément connexe car il est possible de trouver des communautés ou individus parfaitement isolés 5. creux sauf quand tout le monde connait tout le monde, mais c'est rare. On va donc, une fois de plus, utiliser une liste d'adjacence. On veut obtenir la liste des nœuds en fonction de leur distance à un nœud particulier, le vôtre. Le parcours en largeur Le BFS est l'algorithme qui permet de parcourir tout les nœuds en fonction de leur distance à l'origine. Il explore les cartes par cercles concentriques de plus en plus grands. Il fonctionne sur tout type de graphe, cyclique ou non, orienté ou non, etc. Le BFS procède de la façon suivante il prend le premier nœud distance 0, puis traite tout les nœuds qui sont à une distance de 1, puis tout les nœuds à une distance de 2, puis tout les nœuds à une distance de 3… et ainsi de suite. On remarque très vite que les nœuds à une distance 1 de l'origine sont ses voisins, à une distance de 2 ce sont les voisins de ses voisins, à une distance 3 les voisins des voisins de ses voisins… de manière générale, un nœud à distance $n$ est soit relié à des nœuds de distance $n-1$, soit des nœuds de distance $n$, soit des nœuds de distance $n+1$. Certainement pas plus sinon il existerait un moyen plus rapide de rejoindre le nœud suivant; certainement pas moins, sinon le nœud courant aurait pu être atteint plus rapidement. Si nous disposons de tout les nœuds à distance $n$, nous avons accès à tout les nœuds de distance $n+1$. Dès le début on a accès au nœud de distance 0, donc en appliquant itérativement ce procédé on aura accès à tout les nœuds en fonction de leur distance à l'origine s'ils sont connexes à elle. Si vous ignorez ce qu'est une file FIFO, il n'est pas trop tard ! Voici l'algorithme 1 Je prend le premier nœud de la file d'attente des nœuds à traiter. 2 Pour visiter ce nœud je le marque comme visité. 3 Je prend chacun de ses voisins non visités et je les ajoute à la fin de la file d'attente des nœuds à visiter 4 Je reprends le 1 tant qu'il reste des nœuds à traiter dans la file d'attente Voici le pseudo code correspondant 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16Procedure BFSGraphe G, Noeud Origine { File aTraiter Marquer Origine comme visite Tant que { Noeud N = Pour chaque voisin V de N Si V non visite Marquer V comme visite } } Cet algorithme fonctionne, je vous le promet ! Il commence par enfiler l'origine à distance 0, puis tout les nœuds 1, puis pour chaque nœud à distance 1 il va enfiler d'autres nœuds à la distance 2 à la suite des nœuds à distance 1 donc, puis il défilera les nœuds à distance 2 pour enfiler des nœuds à distance 3 à leur suite. Ainsi les nœuds ne sont jamais mélangés ils sont présents dans la file par "paquets" consécutifs qui correspondent à leur distance à l'origine. Sur un arbre, il explore les nœuds en fonction de leur hauteur distance à la racine contrairement au DFS qui va effectuer toutes les descentes possibles de la racine à une feuille. Ainsi, l'ordre d'exploration des nœuds du graphe ci-dessous pourra être A - B - C - E - D - F - G. Comme vous pouvez le constater, tout les nœuds frères sont parcourus les uns à la suite des autres, la racine étant l'origine du BFS. Et que se passera-t-il si d'aventure nous remplacions la file par une pile ? On retrouve la version itérative du DFS présentée à la section précédente ! Il apparaît donc qu'il y a un lien important entre algorithme et structure de données. L'écriture sous forme itérative ou récursive dépend essentiellement de facteurs comme la lisibilité, la manière de laquelle l'algorithme s'explique le mieux, et enfin les performances. Tout comme pour le DFS, la complexité en temps est en $ON+A$. Une fois encore la densité du graphe influe beaucoup sur les performances de l'algorithme. La file peut contenir autant de nœuds que le graphe en possède, d'où une complexité en mémoire de $ON$. Le pire des cas concerne un graphe de diamètre très faible la file est encombrée par la présence des nombreux nœuds à distance égale de l'origine. Le BFS peut servir à détecter les composantes connexes d'un graphe tout comme le DFS, si plusieurs appels à la fonction BFS sont réalisés avec des origines différentes. Mais le BFS a un avantage sur le DFS lors de la recherche de composantes connexes il trouve le plus court chemin entre ces deux nœuds s'il existe dans le cas d'un graphe non pondéré. Dans le cas d'un graphe implicite de taille infini ou de très très grande taille le DFS risque de s'engager dans une mauvaise voie dans le début et de s'y enfoncer trop profondément, voir à l'infini ! Impossible de déterminer en un temps raisonnable ou un temps fini un chemin entre deux nœuds connexes, alors que le BFS va explorer plusieurs chemins possibles à la fois, ce qui lui donne l'assurance de trouver un jour ce chemin. Pour cette raison les graphes implicites de grande taille sont souvent parcouru par un BFS plutôt qu'un DFS. Mais le BFS peut aussi servir à marquer les nœuds en fonction de leur distance à l'origine. C'est ce que nous allons voir tout de suite ! Le prix du succès Une petite modification suffit à la résolution du problème. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20Procedure BFSGraphe G, Noeud Origine { File aTraiter profondeur[Origine] = 0 Marquer Origine comme visite Tant que { Noeud N = Afficher + " voit la video a l'heure " + profondeur[N] Pour chaque voisin V de N Si V non visite profondeur[V] = profondeur[N] + 1 Marquer V comme visite } } Ci-dessous, vous pouvez voir l'effet de cet algorithme sur un graphe simple. Les nœuds sont marqués en fonction de leur ordre de visite par le BFS le nœud n°1 correspond donc à l'origine. Les nœuds en cours de visite c'est à dire présents dans la file sont coloriés en bleu. Les nœuds déjà visités et extraits de la file sont coloriés en vert. Ici, tout les nœuds à distance 2 ou moins ont été explorés. Les nœuds à distance 3 sont en cours d'exploration. Le seul nœud à distance 4 n'a pas encore été traité. Et voilà. Vous pouvez également indiquer le nombre total de gens ayant vu la vidéo à chaque heure, je vous laisse le coder vous même, ce n'est pas très difficile. L'exhaustif et Uno Connaissez-vous Uno$^{TM}$ ? Comme nous l'explique si bien Wikipédia, c'est un jeu de carte américain créé en 1971 par Merle Robbins. Il est pourvu de pleins de règles subtiles et de cartes agressives pour faire rager les petits comme les grands. Aujourd'hui, je ne vous propose pas de programmer ce célèbre jeu. A la place je vous propose plutôt de résoudre ce petit problème. Dans Uno, chaque joueur possède un certain nombre de cartes en main, qui peuvent être de 4 couleurs bleues, vertes, rouges ou jaunes. Elles sont numérotées de 0 à 9. Il existe également certaines cartes spéciales "Joker", "Inversion", "Super Joker"… mais nous ne y intéresserons pas ici, par soucis de simplicité. Le jeu comporte un talon. On ne peut poser une carte sur le sommet de ce talon que si la carte au sommet du talon est De même couleur que la carte qu'on joue De même valeur faciale que la carte qu'on joue Un petit exemple poser un 7 rouge sur un 9 rouge est autorisé poser un 4 vert sur un 4 bleu est autorisé poser un 2 jaune sur un 2 jaune est autorisé poser un 6 bleu sur un 3 vert est interdit Il est ainsi possible d'empiler ces cartes de diverses façons en suivant ces règles, pour obtenir un talon plus ou moins haut. Comme vous n'avez rien de mieux à faire, vous voulez savoir, à partir d'un ensemble de cartes donné, quels sont tout les talons qu'il est possible de réaliser avec. Les cartes qui ne pourront pas être ajoutées au talon seront éventuellement laissées sur le côté. Vous pouvez empiler les cartes de votre choix dans n'importe quel ordre, tant que vous respectez les règles. Exercice vous commencez à le connaître par cœur normalement. Quel est le graphe ? Quels sont ses caractéristiques ? Quelle question se pose-t-on sur lui ? Correction Rappelez-vous un graphe représente des objets et des relations entre ces objets. Ici chaque objet, chaque nœud, est donc une carte. Les arêtes sont définies par une valeur faciale ou une couleur commune. C'est donc un bon exemple de graphe dans lequel les arêtes peuvent être déduites du nœud à partir de certaines règles de construction simples. Ce graphe est cyclique. Certes, on ne peut pas utiliser une même carte plus d'une fois, mais il y a plus d'une manière de poser une carte, qui aboutira à d'autres situations où il serait ensuite théoriquement valide de poser cette carte si elle n'avait pas déjà été jouée. Ce graphe est non orienté. L'ordre dans lequel sont empilés les cartes n'a pas d'importance, puisque les règles ne s'intéressent qu'à la relation d'adjacence de deux cartes, indépendamment de leur ordre. Ce graphe est non pondéré sapristi, un de plus !. Une fois encore, ce graphe n'est pas nécessairement connexe. Il est parfois possible de former deux groupes de cartes n'ayant aucune couleur ou aucun numéro en commun. Ce graphe est dense, selon moi. En effet il n'y a pas de caractérisation formelle entre un graphe dense et un graphe qui ne l'est pas, c'est laissé à l'appréciation de chacun. Laissez moi vous expliquer ma démarche. En supposant que l'on ait beaucoup de cartes, disons $N$, et qu'elles soient toutes tirées au hasard en moyenne nous aurons $\frac{N}{4}$ cartes de chaque couleur car il y a 4 couleurs. Toutes les cartes de même couleur seront au moins reliées entre elles. Donc, en moyenne, chaque carte sera reliées à au moins $\frac{N}{4}$ autres cartes. Chaque carte est aussi reliée à un $\frac{1}{10}$ car 10 chiffres des cartes des 3 autres couleurs, soit $\frac{3\times N}{40}$ autres cartes en moyenne. Cela fait une moyenne de $0,325\times N$ arêtes par nœud. Le degré de chaque nœud n'est pas constant il dépend de $N$. Ce qui porte le total à environ $0,162\times N^2$ arêtes, car c'est un graphe non orienté il ne faut pas compter deux fois chaque arête. Le nombre d'arêtes du graphe est fonction de $N^2$. On peut donc qualifier ce graphe de dense. Et il est suffisamment dense pour rendre la matrice d'adjacence plus intéressante que la liste. Voici le graphe associé à un ensemble de 20 cartes 5 de chaque couleur. En théorie chaque carte devrait être reliée à elle même. Mais comme on ne peut pas utiliser deux fois la même carte ce qui n'empêche pas celle-ci d'être présente en plusieurs exemplaires dans le jeu de départ, on sait d'avance que ces arêtes qu'on nomme boucle ne nous serviront pas. Autant les supprimer maintenant. On souhaite connaître quels sont tout les talons possibles. Un talon est caractérisé par un ensemble de cartes dans un certain ordre. Et les contraintes d'adjacence de deux cartes sont celles des arêtes du graphe ! Par conséquent, chaque talon peut être représenté par un chemin du graphe l'ordre de visite des nœuds et la taille du chemin suffit à définir le talon. Pour générer tout les talons possibles, il faut donc générer tout les chemins du graphe ! L'exhaustif Exhaustif qui inclut tous les éléments possibles d’une liste, qui traite totalement un sujet. Wiktionnaire Nous cherchons une liste de tout les talons possibles, sans en oublier aucun. Il nous faut donc une liste exhaustive des talons possibles. Cela nécessite un algorithme capable d'effectuer une énumération exhaustive de tout les chemins du graphe. D'où le nom "exhaustif". Il faut trouver une méthode systématique pour dénombrer les chemins du graphe. Comment procéderions-nous "à la main" ? Par exemple, nous pourrions prendre une carte et l'ajouter au talon. Puis prendre une autre carte et l'ajouter au talon en respectant les règles. Puis continuer à ajouter des cartes qu'il est possible d'en ajouter, sans réutiliser deux fois la même évidemment. Et lorsqu'on est bloqué ? Une retire une carte au sommet du talon et on met une autre à la place, et c'est reparti ! On continue d'ajouter des cartes jusqu'à ce qu'on soit forcé d'en retirer une au sommet pour la remplacer par une autre qui n'a pas déjà été posée à cette hauteur et avec ce talon spécifique en dessous. Ainsi, à la fin, tout les talons auront été construits. Vous avez remarqué ? Un talon est une pile, donc nous aurons sûrement affaire à une pile dans cet algorithme ! En voici le pseudo-code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24Fonction listerCheminsGraphe G { Chemin chemins[] Pour chaque noeud N autresChemins = exhaustifG, N, cheminVide Renvoyer chemins } Fonction exhaustifGraphe G, Noeud N, Chemin C { Marquer N comme utilise Chemin chemins[] Pour chaque voisin V de N Si V inutilise autresChemins = exhaustifG, V, C Marquer N comme inutilise Renvoyer chemins } Sans grande surprise, ici, la pile est la pile d'appel. Mais une fois encore, cet algorithme peut être passé sous forme itérative si le besoin s'en fait sentir. Cette fonction ressemble beaucoup à celle du DFS, à deux exceptions près. Premièrement, elle retourne quelque chose la liste des chemins. Secondement, et c'est là le point le plus important l'état d'un nœud n'est pas définitivement fixé. A l'entrée de la fonction le nœud est marqué comme "utilisé"; en revanche, à la sortie de la fonction, il est de nouveau marqué comme "inutilisé". Ainsi, la fonction exhaustif va pouvoir traiter plusieurs fois le même nœud, ce qui est somme-toute logique puisque qu'un même nœud peut faire parti de plusieurs chemins distincts. L'importance de marquer un nœud comme utilisé durant son utilisation comme les toilettes publiques à verrou coloré est la même que celle du DFS empêcher les appels récursifs infinis en bouclant dans un cycle. Sur le graphe ci-dessous, après un appel sur le nœud A, l'ordre d’exploration des nœuds pourrait être A - B - D - F - E - C - G - E - F - B - D. Cela générerait les chemins suivants A A - B A - B - D A - B - F A - B - F - E A - C A - C - G A - E A - E - F A - E - F - B A - E - F - B - D Et cela juste avec le nœud A comme origine ! Je vous épargne les autres. Comme vous pouvez le constater le nombre de chemins différents augmente très vite et atteint un nombre important, même sur un graphe de petite taille. Ce qui nous amène tout de suite à une question épineuse quelle est la complexité en temps et en mémoire de l'algorithme ? Commençons par la complexité en temps. Quel est le pire des cas ? Le pire des cas serait un jeu de carte où toutes les cartes sont reliées entre elles typiquement toutes de même couleur, ce qui revient à travailler sur un graphe complet. Ainsi, je dispose de $N$ choix pour le premier élément du talon. Puis de $N-1$ pour le second. Puis de $N-2$ pour le troisième, et ainsi de suite. Le nombre de talons possible est donc égal à $N \times N-1 \times N-2 \times ... \times 2 \times 1 = N!$ En fait, toutes les permutations de cartes sont possibles puisque toutes les cartes peuvent être adjacentes. Et le nombre de permutations de $N$ éléments est bien égal à $N!$. Cet algorithme est donc en $ON!$ en temps. La fonction factorielle croit asymptotiquement plus vite que la fonction exponentielle. Pour cette raison, même sur des instances de petite taille, certains problèmes sont insolubles en un temps raisonnable. Pour vous faire une idée, 20! = 2 432 902 008 176 640 000. Donc si le graphe ci-dessus était complet, il faudrait plusieurs dizaines de milliers d'années de calcul. Heureusement il ne l'est pas ! De mon côté j'ai trouvé 437 672 443 chemins différents vous pouvez vérifier si l'envie vous en prend. Quant à la complexité en mémoire, elle est nécessairement $ON!$ elle aussi, puisqu'il s'agit là du nombre de solutions. Cependant, si vous n'aviez qu'à dénombrer les solutions sans les produire, vous auriez alors un algorithme en $ON$, car la profondeur de récursion peut atteindre N nœuds. La recherche exhaustive, bien que donnant des résultat exacts de par son exhaustivité, ne peut pas être utilisée en pratique pour tout les problèmes, à cause de sa lenteur. Pour cette raison, de nombreux problèmes sont résolus avec des algorithmes donnant une réponse approchée ou exacte mais dont on ne peut pas prouver qu'elle est exacte qui ont une durée d'exécution plus courte. Ces algorithmes sont des sujets de recherche encore actifs qui mériteraient un tutoriel entier à eux seuls, donc nous n'en parlerons pas plus ici.
Mon compte Menu 0983380739 Contact Rechercher un cadeau personnalisé MUGS Maîtresse & Nounou Fête des pères Fête des mères Anniversaire Fête Remerciements Annonce Félicitations Je t'aime Mariage Baptême & Communion Occasions Diverses Personnalisé Noël / Bonne année St Valentin Fête des grands-mères GRAVURES PUZZLES À propos Instagram Promotions Accueil › Toutes les promos Affiner votre recherche Marques Voir tous les produits Trier par Affichage NEW-10% Duo Cadeau Demande Parrain Marraine - 2 Cartes + Bracelets porte bonheur DUO-UVACN44-54 Voulez-vous être mon parrain et ma marraine ? 17,82 € 19,80 € Ajouter au panier NEW-10% Duo de Cadeau personnalisable pour Parrain et Marraine - Carte + Bracelet DUO-UVACN148-149 Cadeau pour Parrain et Marraine 17,82 € 19,80 € Ajouter au panier -13% Duo de MUGS Papy et Mamie d'amour MUG023 2 jolies tasses pour vos grands-parents 25,80 € 29,80 € Ajouter au panier Votre avis Charline - Super , je suis ravie des jolis produits conformes à la description, un emballage parfait et très soigné et surtout une livraison en un temps record. Je donne rarement mon avis mais là je suis vraiment satisfaite. Charline Lire les avis 384 Donner votre avis Abonnez-vous à UVACN Abonnez-vous à UVACN À propos d'UVACN À propos Personnalisation Livraison Instagram Mentions légales CGV création boutique e-commerce
Le conceptLa Couverture des 100 Vœux, ou Bai Jia Bei, est une coutume ancestrale la naissance d’un enfant, la tradition veut que les membres de la famille et les amis offrent un morceau de tissu accompagné d’un vœu avec quelques mots de protection pour la vie de l’enfant. Les morceaux d’étoffes sont assemblés en une grande Couverture qui contiendra la chance, l’énergie et les vœux de toutes les personnes qui y ont contribué, et les vœux sont réunis dans un avons choisi d’adapter la coutume à notre nous proposons la confection de Couvertures pour les événements marquants d’une vie naissance, mariage, anniversaire… Et en toute discrétion pour pouvoir créer la surprise !Par la même occasion, nous vous simplifions la création d’un cadeau commun. Vous n’avez qu’à choisir votre tissu et rédiger votre vœu, nous nous occupons du reste !Que vous habitiez Paris, Lille, Marseille, Strasbourg, Nantes, aux États-Unis, en Nouvelle-Calédonie, en Belgique... ou partout ailleurs, c’est l’occasion d’unir vos souhaits et de vous retrouver autour d’un merveilleux ailleurs, nous pouvons vous livrer où vous le souhaitez, en France, en Europe ou partout ou presque dans le monde. Comment ça marche ? 1 Je crée ou je participe à un projet Exemples issus de notre vaste catalogue de tissus Notre Instagram
Aller au menu Aller au contenu Aller à la recherche Bibliothèque Accéder à tous les contenus de la bibliothèque Informatique Autres informatique Bureautique et rédaction Développement Web Matériel et électronique Programmation et algorithmique Systèmes d'exploitation Sciences de la nature Astronomie Autres sciences de la nature Biologie Chimie Mathématiques Physique Sciences de la terre Sciences humaines et sociales Autres sciences humaines et sociales Droit Économie Histoire Langues Psychologie Autres Arts, graphisme et multimédia Autres Communication et management Zeste de Savoir Tags les plus utilisés zds mathématiques algorithmique python physique Tous les tags Tribune Tous les billets Informatique Autres informatique Bureautique et rédaction Développement Web Matériel et électronique Programmation et algorithmique Systèmes d'exploitation Sciences de la nature Astronomie Autres sciences de la nature Biologie Chimie Mathématiques Physique Sciences de la terre Sciences humaines et sociales Autres sciences humaines et sociales Droit Économie Histoire Langues Psychologie Autres Arts, graphisme et multimédia Autres Communication et management Zeste de Savoir Tags les plus utilisés python zds c++ musique javascript Tous les tags Forum Tous les forums Savoirs Programmation Développement Web Multimédia et Jeux vidéo Systèmes et Matériels Sciences Les autres savoirs Communauté Le bar à smoothies Bugs et Suggestions Dev Zone Contenus en cours de rédaction Vos projets L'association Tags les plus utilisés python c++ php arduino c Accueil Tutoriels Les arbres binaires de recherche Licence CC BY-NC-SA Les arbres binaires de recherche Découvrez ces structures de données super performantes ! Lorsque l'on parle d'arbres, bien des gens penseront de prime abord à la biologie, la botanique et donc aux arbres tels que vous pouvez sûrement les voir en jetant un coup d'œil par votre fenêtre. Cependant, les arbres se retrouvent dans un ensemble de domaines tous plus variés les uns que les autres, et c'est sur l'algorithmique que nous allons nous pencher ici. Les arbres ont effectivement inspiré les algorithmiciens de la deuxième moitié du vingtième siècle, les amenant à créer une structure de données révolutionnaire les arbres binaires de recherche. Qu'est-ce que c'est, à quoi ça sert, comment ça marche, comment les implémenter, comment les améliorer ; ce sont autant de questions qui seront abordées dans ce cours, qui va, je l'espère de tout cœur, vous brancher sans vous faire prendre racine ! Un mot sur les prérequis Dans la mesure où ce cours traite d'algorithmique, une connaissance basique dans ce domaine sera nécessaire pour ne pas se perdre dans les explications. Une bonne connaissance du C sera un avantage à qui veut suivre les explications sur l'implémentation proposée, mais n'est pas requise à proprement parler. De plus, dans la mesure où ce cours se veut une introduction, aucune connaissance sur les arbres binaires ou sur la théorie des graphes en général n'est nécessaire. D'une manière générale, ce cours s'adresse à quiconque a déjà touché de près ou de loin au monde de la programmation et/ou de l'algorithmique. Remarque de vocabulaire Par la suite, j'utiliserai sans distinction les expressions arbres », arbres binaires » ou BST »1. Tous ces termes font bien entendu référence aux arbres binaires de recherche », ne vous inquiétez pas. Définitions Opération et implémentation Inconvénients et évolutions Définitions Structure de données Comme mentionné dans l'introduction, les BST font partie de la grande famille des structures de données. Mais qu'est-ce donc que ces bêtes là ? Pour répondre à cette question, Wikipédia nous fournit un bon point de départ Une structure de données est une structure logique destinée à contenir des données, afin de leur donner une organisation permettant de simplifier leur traitement. WikipédiaIl existe de très nombreuses structures de données différentes enregistrements, listes, matrices, piles, files, tables ou encore… arbres. Toutes ont leur propre logique, leur propre manière de fonctionner et leurs propres avantages et inconvénients. Je vous laisse vous documenter sur ces nombreux sujets si le cœur vous en dit. Les arbres binaires de recherche Nous allons tâcher maintenant de définir précisément ces fameux arbres binaires. Pour se donner une idée, voici un exemple Un bonzaï binaire de recherche Arbre On retrouve dans la vie de tous les jours de nombreuses arborescences les dossiers d'un ordinateur, l'organisation d'une entreprise, et cætera. De manière un peu plus formelle, un arbre est un ensemble d'éléments appelés nœuds, parmi lesquels on trouve une unique racine c'est le nombre 10 dans l'exemple. Chaque nœud est relié à ses descendants grâce à des arêtes. Les nœuds sans aucun descendants sont appelés feuilles ; dans l'exemple, il y a trois feuilles 2, 13 et 17. Pour poursuivre avec le vocabulaire lié aux arbres, on appelle profondeur d'un nœud l'étage » du nœud en question1. Par exemple, le nœud contenant 5 a une profondeur égale à deux. Enfin, la hauteur désigne la profondeur la plus grande atteinte dans l'arbre. Ici, la hauteur de l'arbre est quatre il s'agit de la profondeur des nœuds 13 et 17. Binaire La particularité d'un arbre binaire est que chaque nœud a au maximum deux fils un à gauche et un à droite. Cette propriété a de nombreuses conséquences On peut déceler dans les arbres binaires une structure récursive ; en effet, un arbre binaire est essentiellement composé d'un nœud, d'un sous-arbre gauche et d'un sous-arbre droit, chaque sous-arbre étant à son tour un arbre composé d'un nœud et de deux sous-arbres et ainsi de suite, jusqu'aux feuilles dont les sous-arbres sont vides. Chaque nœud ne possédant au maximum que deux descendants, un arbre de hauteur $h$ peut contenir au maximum $2^h-1$ nœuds. Inversement, un arbre contenant $n$ nœuds a une hauteur minimale de $\left\lceil\log_2n\right\rceil$. De recherche Les éléments stockés dans un arbre binaire de recherche doivent posséder une relation d'ordre totale, c'est-à-dire qu'il doit exister une manière de dire si un élément est plus grand ou plus petit qu'un autre. Par exemple, on peut trier des nombres par valeur ou des personnes par ordre alphabétique de leur nom de famille. Le premier élément inséré dans l'arbre devient la racine. Ensuite, il suffit de mettre à gauche les éléments plus petits et à droite les éléments plus grands. C'est cette particularité qui rend les BST intéressants la plupart des opérations réalisées sur les arbres binaires de recherche ont une complexité temporelle logarithmique $O\log\,n$ dans le cas moyen. Cela est dû au fait que, grâce à la relation d'ordre liant les valeurs, on peut naviguer dans l'arbre avec une logique rappelant une recherche dichotomique, ce qui est plus performant que les listes, par exemple, que l'on doit parcourir élément par élément2. Exemple Afin de tirer les choses au clair, voici un exemple d'utilisation des arbres binaires de recherche, afin que vous puissiez les voir en action. Un vétérinaire voudrait stocker les fiches médicales de ses patients, et, plutôt que d'utiliser un tableau ou une liste, on se propose d'utiliser un arbre binaire. La fiche contiendra différentes informations sur l'animal ; on utilisera son nom comme clé c'est-à-dire comme critère pour la relation d'ordre, que l'on triera selon l'ordre alphabétique croissant. Le vétérinaire reçoit sa première patiente, qui répond au nom de Gaufrette. Comme sa fiche sera le premier nœud de notre arbre, elle en devient automatiquement la racine. Un peu seule, cette racine... Après Gaufrette, vient le tour de Charlie. Une fois sa fiche remplie, on va l'ajouter à l'arbre comme Charlie est avant Gaufrette dans l'ordre alphabétique, on place la fiche de Charlie à gauche de la racine. Charlie est avant Gaufrette Le prochain patient est Médor. Vous l'aurez compris, Médor vient après Gaufrette, on placera donc sa fiche à droite de la racine. Médor est après Gaufrette Allez, c'est à vous de jouer à présent ! Les trois prochains patients s'appellent Flipper, Bubulle et Augustin, et ils sont soignés par le vétérinaire dans cet ordre. Prenez quelques instants pour deviner à quoi ressemble l'arbre après ces nouvelles consultations. La solution se trouve juste après. Et ainsi de suite... La structure d'un arbre est dépendante de l'ordre dans lequel on insère les éléments ! Si on avait ajouté Augustin avant Bubulle, l'arbre aurait été comme ceci Ils se ressemblent, mais ne sont pas identiques ! Nous reviendrons bien plus tard sur les implications de cette propriété, mais sachez que c'est très loin d'être anodin. Opération et implémentation Le but ici est de détailler l'implémentation des opérations sur les arbres binaires. Les algorithmes seront détaillés en pseudo-code par souci de généralisme, et une implémentation en C sera proposée afin d'approcher les algorithmes d'un point de vue un peu plus technique. Bien sûr, rien ne vous empêche de suivre le cours si vous ne connaissez pas le C, voire même de publier votre propre bibliothèque dans le langage de votre choix ! Comme toujours sur Zeste de Savoir, toute contribution est la bienvenue ! Une implémentation complète, fonctionnelle et commentée en C est disponible ici. Définition de la structure Ne mettons pas la charrue avant les bœufs avant de penser aux fonctions destinées à être utilisées sur des BST, il faut peut-être commencer par définir les BST eux-mêmes, non ? Pseudo-code Du point de vue algorithmique, un BST n'a rien de très compliqué. Un nœud contient une donnée quelconque, un nœud fils à gauche, et un nœud fils à droite. On retrouve la structure récursive évoquée précédemment. Structure bst_node data est de type quelconque left est de type bst_node right est de type bst_node On peut remarquer que les champs left et right peuvent être vides dans le cas d'une feuille, par exemple, le nœud contient une donnée, mais n'a ni fils gauche, ni fils droit. Enfin, pour définir l'arbre, on n'a besoin de connaître une seule chose sa racine. En effet, une fois que la racine est connue, on peut accéder à ses successeurs, puis aux successeurs de ses successeurs, et ainsi de suite… C En C, en revanche, c'est un peu plus compliqué. Voici la structure qui sera utilisée pour les implémentations à suivre 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15typedef int *orderFunvoid *, void *; typedef void *freeFunvoid *; typedef struct _bst_node { void* data; /** 0. * \param orderFun The tree's binary relation; cannot be c NULL. * \param freeFun The tree's data freeing function; can be NULL. * * \return A pointer to the allocated tree * * \warning Breaking any of the above conditions will cause the program to * abort! * \warning Not deallocating the tree with ref bst_destroy will result in * memory leaks! * * \see bst_destroy */ bst* bst_newsize_t data_size, orderFun compare, freeFun free { assertdata_size > 0; assertcompare; bst* tree = mallocsizeofbst; if!tree {// If allocation failed perror"malloc"; exitEXIT_FAILURE; } tree->data_size = data_size; tree->compare = compare; tree->free = free; tree->root = NULL; return tree; } La fonction assert1 nous permet d'interrompre le programme avec un message d'erreur si la condition donnée est fausse. En effet, laisser un programme s'exécuter alors que le paramètre data_size ou la relation d'ordre de l'arbre sont incohérents est une mauvaise idée. On peut également utiliser la fonction perror pour afficher un message d'erreur si l'allocation dynamique malloc a rencontré un problème et quitter le programme avec un code d'erreur en utilisant exitEXIT_FAILURE2. Ajout d'un nœud Pseudo-code Comme nous l'avons vu précédemment on peut ajouter un nouveau nœud à l'arbre très simplement à l'aide d'une fonction récursive Procédure bst_add node de type bst_node, element de type quelconque Si node est vide = element Sinon Si > element bst_add element Sinon bst_add element FinSi FinSi Comme le nœud respectivement de la racine peut être considéré comme la racine du sous-arbre gauche droit, on peut utiliser la fonction elle-même pour réaliser l'insertion dans le sous-arbre, et répéter l'opération jusqu'à atteindre un sous-arbre vide, où l'on vient insérer la nouvelle donnée. C Ici, nous allons utiliser exactement la même méthode. Néanmoins, les structures pseudo-code et C étant différentes, les fonctions le seront un peu aussi. Regardons pour commencer la signature de la fonction bst_add en C /** * \brief Adds a new node to a tree * * Calling this function dynamically allocates a new node and inserts it into * the binary search tree. * * \param tree The tree to insert the new node into. Cannot be c NULL * \param element The new node's data. Cannot be c NULL */ void bst_addbst* tree, void* element; A priori, rien de très différent par rapport au pseudo-code précédent. Et pourtant, le premier argument est un arbre, et non pas un nœud, ce qui signifie que l'on ne va pas pouvoir utiliser la récursivité directement dans la fonction bst_add. Embêtant, non ? Une technique valable dans ce genre de situation est de créer le nœud contenant la nouvelle donnée dans bst_add puis d'ajouter le nouveau nœud dans l'arbre grâce à une fonction récursive avec une signature comme suit static void bst_add_recorderFun compare, bst_node* current, bst_node* new; Les fonctions statiques Le mot-clé static présent devant la signature de la fonction signifie que seules les fonctions de la même unité de compilation3 peuvent y faire appel. En clair, cela signifie que l'utilisateur de notre bibliothèque n'aura pas le droit d'utiliser directement bst_add_rec, ce qui est une bonne chose. Jetons un œil aux arguments de la fonctions bst_add_rec compare la fonction bst_add_rec va avoir besoin de réaliser des comparaisons pour savoir de quel côté d'un nœud se diriger ; on va donc lui envoyer la relation d'ordre de l'arbre. current ce paramètre désigne la racine de l'arbre ou du sous-arbre dans lequel on veut insérer le nouveau nœud. new ici, ce sera le nœud à insérer, qui aura été préalablement créé dans bst_create_node, une simple fonction auxiliaire. Si tout est clair pour tout le monde, voici l'implémentation des fonctions bst_add et bst_create_node pour commencer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30void bst_addbst* tree, void* element { asserttree; assertelement; // Creating the new node bst_node* node = bst_create_nodetree->data_size, element; iftree->root // If the tree already has a root bst_add_rectree->compare, tree->root, node; // Start recursion else tree->root = node; // Setting the new node as the tree's root } static bst_node* bst_create_nodeint data_size, void* element { bst_node* node = mallocsizeofbst_node; // Allocating a node if!node { perror"malloc"; exitEXIT_FAILURE; } node->data = mallocdata_size; // Allocating data if!node->data { perror"malloc"; exitEXIT_FAILURE; } memcpynode->data, element, data_size; // Setting data node->left = NULL; node->right = NULL; return node; } Dans bst_create_node, on alloue de l'espace pour le nœud ainsi que pour la donnée à stocker, on copie la donnée dans l'espace que l'on vient de créer, et on déclare que le nouveau nœud n'a pas de descendants. Vient ensuite un test permettant de vérifier que la racine de tree existe. En effet, s'il s'agit du premier nœud de l'arbre, tree->root vaut NULL, et l'ajout se résume à une simple affectation. Dans le cas contraire, on déclenche la récursivité en insérant le nouveau nœud à partir de la racine déjà existante. Jetons maintenant un œil à bst_add_rec 1 2 3 4 5 6 7 8 9 10 11 12 13static void bst_add_recorderFun compare, bst_node* current, bst_node* new { ifcomparecurrent->data, new->data >= 0 { // current >= new if!current->left // current->left == NULL current->left = new; else bst_add_reccompare, current->left, new; } else { // current right current->right = new; else bst_add_reccompare, current->right, new; } } La lecture de ce code est plutôt directe on cherche d'abord à savoir si on se dirige sur la gauche ou la droite du nœud current, puis si le sous-arbre correspondant existe ou non. Si oui, on insère le nouveau nœud dans le sous-arbre par récursivité ; sinon, on ajoute le nœud par affectation. Fonction de destruction On implémente les nœuds et les données de manière dynamique ici. Il est donc très important de prévoir leur désallocation. On peut utiliser une fonction récursive avec la même ruse vue précédemment. On peut écrire une petite fonction bst_destroy_node pour s'aider 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31/** * \brief Destroys a tree * * A call to bst_destroy deallocates a tree and all its nodes by calling * bstfree. Make sure the program calls it when the tree is not needed anymore. * * \param tree The tree to deallocate * * \warning Not using this function to deallocate a bst will cause memory * leaks! * */ void bst_destroybst* tree { asserttree; iftree->root bst_destroy_rectree, tree->root; freetree; } static void bst_destroy_recbst* tree, bst_node* current { ifcurrent->left bst_destroy_rectree, current->left; ifcurrent->right bst_destroy_rectree, current->right; bst_destroy_nodetree, current; } static void bst_destroy_nodebst* tree, bst_node* node { iftree->free // If the tree has a deallocation function... tree->freenode->data; // ... free the node's data dynamic parts freenode->data; // Free the node's data slot itself freenode; // Free the node } On utilise bst_destroy pour lancer la récursivité, puis, dans bst_destroy_rec, on désalloue le sous-arbre gauche s'il existe, le sous-arbre droit s'il existe, puis la racine grâce à bst_destroy_node. Parcours Jusqu'à présent, nous sommes capables de créer des arbres binaires, et d'y ajouter des données. C'est déjà pas mal, mais il serait autrement plus intéressant de pouvoir exploiter les données stockées dans l'arbre. Nous allons donc écrire une fonction nous permettant de parcourir tous les nœuds de l'arbre et de leur appliquer une procédure4 donnée. Pseudo-code Ici, rien de plus simple. On va simplement appliquer procedure à tous les nœuds de l'arbre par récursivité Procédure bst_iter node de type bst_node, procedure de type procédure Si node est vide Quitter la procédure Sinon bst_iter function procedure bst_iter function FinSi Traversée pre-order, in-order, post-order Dans le pseudo-code ci-dessus, j'ai arbitrairement choisi de mettre la ligne procedure entre l'appel récursif sur le sous-arbre gauche et celui sur le droit. On parle de traversée in-order ; en effet, dans le cas des BST, cette traversée a la particularité de visiter tous les nœuds dans l'ordre, puisqu'on visite d'abord le sous-arbre gauche dont les valeurs sont inférieures, puis le nœud lui-même, et enfin le sous-arbre droit qui contient les nœuds supérieurs. Traversée in-order 2, 5, 10, 12, 13, 15, 17 Il existe aussi d'autres types de parcours pre-order on parcourt d'abord le nœud, puis le sous-arbre gauche, puis le droit. Traversée pre-order 10, 5, 2, 12, 15, 13, 17 post-order on parcourt le sous-arbre gauche, puis le droit, et enfin le nœud. Traversée post-order 2, 5, 13, 17, 15, 12, 10 C Comme toujours, nous allons devoir utiliser deux fonctions une fonction pour déclencher la récursivité, et une fonction statique récursive. On définit tout de même le type iterFun par souci de lisibilité la fonction function doit prendre comme argument un void* un pointeur sur la donnée et ne rien retourner. typedef void *iterFunvoid *; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25/** * \brief Iterates over a tree * * Applies a function to every element in the tree. * * \param tree The tree to iterate over * \param function The function to apply. Cannot be c NULL. * * \warning Breaking any of the above conditions will cause the program to * abort! */ void bst_iterbst* tree, iterFun function { asserttree; assertfunction; iftree->root bst_iter_rectree->root, function; } static void bst_iter_recbst_node* current, iterFun function { if!current return; bst_iter_reccurrent->left, function; functioncurrent->data; bst_iter_reccurrent->right, function; } Exemple Entre la création, la destruction, l'ajout d'un nœud et la traversée, notre petite bibliothèque commence à se remplir. Elle l'est même suffisamment pour écrire un premier programme de test ! Si vous avez bien tout suivi depuis le début, tout devrait vous paraître logique 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59include include include " // Don't forget that bit /* * Comparison function */ int compvoid* a, void* b { return *int* a - *int* b; // a-b>0 if a>b, b$ et négatif si $b>a$. Exactement ce dont notre arbre a besoin. La fonction print quant à elle ne fait qu'afficher le nombre qui lui est envoyé. On utilise cette fonction avec bst_iter pour afficher toutes les valeurs contenues dans l'arbre. Si tout se passe bien, voici l'affichage que tout un chacun doit obtenir 1 2 3 4 5 6 7 8 9 10 11 12 13Creating tree... Done. Adding 10... Adding 5... Adding 2... Adding 12... Adding 15... Adding 17... Adding 13... Printing data... 2 5 10 12 13 15 17 Done. Destroying tree... Done. Recherche Une autre fonction couramment implémentée dans les structures de données permet de rechercher un élément. Dans notre cas, la fonction peut renvoyer le nœud dans lequel l'élément recherché a été trouvé, et une valeur par défaut notée nil dans le pseudo-code si la valeur n'a pas été trouvée. Pseudo-code Comme tout à l'heure avec bst_add, on va utiliser des comparaisons pour savoir si on continue la recherche à gauche ou à droite d'un nœud. Et si d'aventure, un élément n'est ni supérieur ni inférieur, c'est qu'il correspond à l'élément que l'on cherche. Fonction bst_searchnode de type bst_node, element de type quelconque Si node est vide Renvoyer nil FinSi Si > element Renvoyer bst_search element Sinon Si root return bst_search_reccompare, tree->root, element; return NULL; } static bst_node* bst_search_recorderFun compare, bst_node* current, void* element { if!current return NULL; ifcomparecurrent->data, element > 0 // current > element return bst_search_reccompare, current->left, element; else ifcomparecurrent->data, element right, element; else // current == element return current; } Suppression La suppression d'un nœud de l'arbre est sans aucun doute l'opération la plus compliquée que nous verrons ici. En effet, la fonction de suppression doit, en plus de supprimer le nœud, faire le nécessaire pour conserver la structure de l'arbre, et c'est loin d'être simple. Pseudo-code Lors de la suppression d'un nœud, il y a trois cas différents à considérer Le nœud n'a aucun descendant pas de problème ici, on peut supprimer le nœud directement. Suppression d'un nœud sans descendants Le nœud a un descendant gauche ou droit avant de supprimer le nœud, il faut rattacher son père à son descendant. A part ça, tout va bien. Suppression d'un nœud avec un descendant ici à gauche Le nœud a deux descendants gauche et droit là, c'est la galère. On ne peut pas remplacer le nœud par son fils gauche, car il faudrait replacer tous les nœuds à droite de ce fils de l'autre côté cf. diagramme ci-dessous, ce qui peut se révéler atrocement long si l'arbre contient ne serait-ce que quelques milliers de nœuds. Dans l'exemple ci-dessus, on pourrait parfaitement essayer de supprimer 10. Cependant, la méthode à employer pour conserver la structure de l'arbre est loin d'être évidente. Si on veut supprimer le 10 de l'arbre, comment faire ? La solution consiste à trouver le successeur du nœud que l'on veut supprimer. Le successeur d'un nœud est en fait le nœud le plus grand c'est-à-dire le plus à droite du sous-arbre gauche5. En effet, le successeur est par définition le plus grand nœud inférieur à celui qui doit être supprimé, donc on peut remplacer le nœud à supprimer par son successeur, et le tour est joué ! Pour clarifier tout ça, rien ne vaut un bon diagramme Compliqué, mais on y arrive ! Maintenant, se pose la question de l'algorithme. On décompose la suppression d'un nœud en deux étapes ; premièrement, on recherche le nœud à supprimer, puis on réalise la suppression en elle-même, en fonction du cas dans lequel on se trouve. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22Fonction bst_removenode de type bst_node, element de type quelconque Si node est vide Renvoyer nil // On a pas trouvé d'élément à supprimer FinSi Si > element = bst_remove element // L'élément à supprimer se trouve dans le sous-arbre gauche Sinon Si root bst_remove_rectree, tree->root, element; } static bst_node* bst_remove_recbst* tree, bst_node* current, void* element { if!current return NULL; iftree->comparecurrent->data, element > 0 current->left = bst_remove_rectree, current->left, element; else iftree->comparecurrent->data, element right = bst_remove_rectree, current->right, element; else { if!current->left && !current->right { // No children bst_destroy_nodetree, current; return NULL; } else if!current->right { // Only a left child bst_node* temp = current->left; bst_destroy_nodetree, current; return temp; } else if!current->left { // Only a right child bst_node* temp = current->right; bst_destroy_nodetree, current; return temp; } else { // Left and right children bst_node* successor = find_maxcurrent->left; memcpycurrent->data, successor->data, tree->data_size; current->left = bst_remove_rectree, current->left, successor->data; } } return current; } static bst_node* find_maxbst_node* current { if!current->right return current; return find_maxcurrent->right; } Et voilà le travail ! On dispose ici d'une interface classique pour gérer des BST depuis n'importe quel type de programme ! Encore une fois, n'hésitez pas à proposer vos travaux, qu'il s'agisse d'implémentations de cette bibliothèque dans le langage de votre choix, une amélioration de celle proposée, ou même un projet qui utilise cette bibliothèque, toutes vos contributions sont les bienvenues ! Inconvénients et évolutions Déséquilibre et dégénérescence en liste Malheureusement, les arbres binaires de recherche ne possèdent pas que des avantages1. Pour s'en rendre compte, il suffit d'imaginer un arbre dans lequel on insère successivement 1, 2, 3, 4 et 5. Plus très binaire, cet arbre binaire... Eh oui, les arbres binaires de recherche sont sensibles à ce que l'on appelle la dégénérescence en liste. Dans l'exemple ci-dessus, on perd tout l'intérêt des arbres puisque l'on est revenu à une autre structure de données une liste chaînée pour être exact qui ne bénéficie pas des avantages des arbres. De manière plus générale, la dégénérescence en liste d'un arbre est un cas extrême de déséquilibre. Un arbre est déséquilibré à partir du moment où un niveau de l'arbre sauf le dernier n'est pas rempli. Tous les niveaux sont pleins sauf le dernier l'arbre est équilibré. L'avant-dernier niveau n'est pas plein l'arbre est déséquilibré. Un déséquilibre provoque un rallongement du temps moyen des opérations, donc on cherche évidemment à éviter cette situation. Malheureusement, avec l'implémentation que nous avons vu durant ce cours, il est difficilement possible de remédier à un tel problème. Les arbres AVL En 1962, les algorithmiciens Adelson-Velsky et Landis publient leurs recherches sur une sorte d'arbre binaire de recherche évolué », que l'on appelle aujourd'hui arbres AVL en référence aux initiales de leurs inventeurs. La différence entre les BST classiques et les arbres AVL est simple les AVL sont capables de s'auto-équilibrer. Ils préservent donc les qualités des arbres binaires en termes de performances, et les exploitent au maximum puisqu'un AVL n'est jamais déséquilibré. Cette évolution s'appuie sur une nouvelle opération la rotation. Le but de ce qui n'est va suivre n'est pas de réaliser l'implémentation des AVL, donc aucun pseudo-code ou C ne sera proposé ici. À chaque fois que l'on modifie la structure de l'arbre c'est-à-dire après l'ajout ou la suppression d'un nœud, on doit vérifier si l'opération a déséquilibré l'arbre ou pas. Pour ce faire, on calcule la balance de tous les ancêtres du nœud qui a été modifié, avec la balance définie comme suit $$\text{balance} = \text{hauteur du sous-arbre gauche} - \text{hauteur du sous-arbre droit}$$ D'après cette définition, dans un arbre équilibré, toutes les balances sont comprises entre $-1$ et $1$ inclus. Si la balance d'un nœud sort de cet intervalle, cela signifie que l'on a détecté un déséquilibre. Pour le résoudre, on effectue la rotation comme suit Illustration d'une rotation, après l'ajout de 17, par exemple On parvient ainsi à garder un arbre équilibré à tout moment. Autres évolutions Les arbres AVL ne sont pas les seules variantes des arbres binaires de recherche. Parmi les autres évolutions des BST, on retrouve notamment les arbres bicolores, les arbres-tas, ou encore les arbres splay. Je vous invite de tout cœur à vous documenter sur ces structures, qui constitueront un excellent complément au cours de vous venez de suivre. Voilà, ce cours touche à sa fin ; j'espère qu'il vous aura été aussi instructif que possible. Je vous encourage une nouvelle fois à réaliser vous-même l'implémentation des BST dans le langage de votre choix. En plus de mettre en pratique tout ce que vous aurez appris ici, vous participerez à l'établissement d'une petite banque de code permettant à tout un chacun d'aborder ce cours avec son langage privilégié ou avec un langage qu'il souhaite découvrir à travers un exemple concret. Merci à tous pour votre lecture, et à très bientôt ! 10 commentaires Super! Mais je trouve qu'il manque un/des exemples concrets d'application des BST, tu aurais des exemples? Un jour j’irai vivre en Théorie, car en théorie tout se passe bien. Excellent cours, merci beaucoup. J'avais déjà lu quelque chose sur le sujet, mais cela m'a donné l'occasion de m'y intéresser de manière plus concrète. Richou D. Degenne Je vais me lancer dans une implémentation en C++, je te dirai quand j'aurai terminé Super! Mais je trouve qu'il manque un/des exemples concrets d'application des BST, tu aurais des exemples? DeathekirlLes arbres binaires de recherche peuvent s'utiliser en lieu et place de n'importe quelle collection ! Les gains en performance seront minimes pour les petites collections, mais à partir de quelques dizaines de milliers d'éléments, la différence entre un algo à complexité linéaire et un algo à complexité logarithmique se fait bien sentir ! Tiens, pour la peine, un exercice Dans le langage de ton choix, écris un programme qui génère une collection tableau en C, liste en Python1… contenant un million de nombres tirés au hasard entre 0 et 1000000, puis qui effectue un million de recherches2. Refais le même programme avec un arbre binaire de recherche et compare les performances. Excellente idée je m'y essaierai demain Un jour j’irai vivre en Théorie, car en théorie tout se passe bien. Les bonsaïs binaires c'est génial ! Merci ! Tu comptes parler des arbres AVL plus en détail la prochaine fois ? Ma petite implémentation en Haskell pour ceux que ça intéresse il manque le delete si vous voulez 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43data BSTree a = Nil Node BSTree a a BSTree a contains Ord a => BSTree a -> a -> Bool Nil `contains` _ = False Node lt e rt `contains` v e == v = True e v = lt `contains` v insert Ord a => BSTree a -> a -> BSTree a insert Nil v = Node Nil v Nil insert Node lt e rt v e == v = Node lt v rt e > v = Node insert lt v e rt e BSTree a -> a -> BSTree a preorder Ord a => BSTree a -> [a] preorder Nil = [] preorder Node lt e rt = [e] ++ preorder lt ++ preorder rt inorder Ord a => BSTree a -> [a] inorder Nil = [] inorder Node lt e rt = inorder lt ++ [e] ++ inorder rt postorder Ord a => BSTree a -> [a] postorder Nil = [] postorder Node lt e rt = postorder lt ++ postorder rt ++ [e] main IO main = do let t1 = insert Nil 10 t2 = insert t1 5 t3 = insert t2 2 t4 = insert t3 12 t5 = insert t4 15 t6 = insert t5 13 t7 = insert t6 17 - bonsaï putStrLn $ "Parcours en profondeur preorder " ++ show preorder t7 putStrLn $ "Parcours en profondeur inorder " ++ show inorder t7 putStrLn $ "Parcours en profondeur postorder " ++ show postorder t7 It’s harder to crack a prejudice than an atom. Le mieux est l’ennemi du bien. Les bonsaïs binaires c'est génial ! Merci ! Tu comptes parler des arbres AVL plus en détail la prochaine fois ? AloqsinEh, il me semblait avoir corrigé ça ! Ne parler que des AVL risque d'être un peu léger comme sujet, mais pourquoi pas un complément d'information sur les arbres auto-équilibrés, ça peut être une idée Et bravo pour l'implémentation en Haskell, on attend tous la suppression ! Bonsoir à toutes et à tous, J'ai publié sur mon blog un complément d'information concernant la programmation générique en C, c'est par ici que ça se passe si ça vous intéresse ! L'animal qui s'appelle Augustin c'est pas très gentil "I think that it’s extraordinarily important that we in computer science keep fun in computing." — Alan J. Perlis Désolé, j'avais besoin d'un nom en 'A', et le chien d'une amie s'appelle Augustin, donc ça m'a paru tout trouvé ! Ceci dit, il est magnifique, comme chien ! Bonjour, J'ai lu l'article en diagonale mais je ne pense pas avoir vu, dans la recherche d'un suivant, le cas où le nœud courant n'a pas de fils droit. Personnellement, dans la manière dont nous l'avons vu à l'école, nos BST un également une référence vers le père. Ainsi, lors de la recherche de l'élément suivant, l'algorithme est très simple Si le courant a un fils droit, alors le suivant sera le fils le plus à gauche possible en partant de ce fils droit. Si le courant n'a pas de fils droit, alors le suivant sera le premier noeud "fils gauche" que l'on croisera en remontant dans l'arbre. Et inversement pour la recherche du précédent Corrigez-moi si cela a déjà été abordé Connectez-vous pour pouvoir poster un message. Connexion Pas encore membre ? Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité. Créer un compte
code promo un voeu à chaque noeud