TP - Binary Search Tree

Overview

Arbre binaire de recherche

Dans ce TP nous allons implémenter un arbre binaire de recherche (ABR) en Rust.

L’objectif est de nous familiariser avec le système de vérification d’emprunts mémoire de Rust (appellé le Borrow Checker en anglais). L’implémentation d’un ABR est particulièrement adapté car elle nécessite de manipuler les nombreuses références vers les nœuds de l’arbre.

Arbre Binaire de Recherche

Les nœuds d’un ABR possèdent chacun une valeur associée unique (deux nœuds ne peuvent pas avoir la même valeur). Il existe un ordre total sur l’ensemble des valeurs.

Pour un nœud d’un ABR de valeur x, toutes les valeurs du sous-arbre gauche sont plus petites que x et toutes les valeurs du sous-arbre droit sont plus grandes que x.

Un ABR permet de déterminer si une valeur est présente avec une complexité logarithmique.

Représentation d’un ABR en Rust

Une première tentative pour représenter un nœud en Rust est

pub struct Node {
    value: i32,
    left: Node,
    right: Node
}
  1. Essayez de compiler le type suivant avec Rust. Expliquez l’erreur obtenue.

Dans l’erreur retournée, le compilateur nous suggère d’introduire une indirection. Les champs left et right vont contenir des références indirectes vers d’autres nœuds.

Parce que nous souhaitons allouer l’ABR sur le tas, nous allons utiliser une référence Box<> en introduisant un type Box<Node>.

pub struct Node {
    value: i32,
    left: Box<Node>,
    right: Box<Node>
}

Ce nouveau code compile ! Malheureusement il ne nous permet pas de représenter des arbres vides. Cela est indispensable, en effet les feuilles de l’arbre possèdent des sous-arbre gauche et droit vides.

Dans un langage comme C ou C++, nous pourrions utiliser un pointeur nul pour représenter le cas de l’arbre vide. Néanmoins dans Rust les références nulles sont interdites pour éviter des erreurs à l’exécution. À la place Rust préconise l’utilisation d’un type Option<> qui représente une valeur optionelle.

Notre structure devient donc

pub struct Node {
    value: i32,
    left: Option<Box<Node>>,
    right: Option<Box<Node>>
}

et de manière à permettre de manipuler un arbre vide directement, nous allons la réecrire de la manière suivante,

#[derive(Debug)]
pub struct Tree(Option<Box<Node>>);

#[derive(Debug)]
pub struct Node {
    value: i32,
    left: Tree,
    right: Tree,
}

Les macros [derive(Debug)] permettent d’obtenir des routines d’affichage pour le debug automatiquement. En les utilisants il est possible d’afficher un objet arbre avec la syntaxe println!("{:?}", mon_arbre).

Pour représenter un arbre composé d’un unique nœud racine avec la valeur 12 nous écririons,

    let t = Tree(Some(Box::new(Node {
            value: 12,
            left: Tree(None),
            right: Tree(None),
            })));

    println!("{:#?}", t);
  1. Modifiez l’exemple précédent pour rajouter un fils gauche (8) et un fils droit (27). Vérifiez que l’arbre s’affiche correctement.

Implémentation de quelques constructeurs

Comme nous venons de le voir, il est pour l’instant fastidieux de manipuler nos structures. Pour faciliter l’utilisation nous allons rajouter quelques constructeurs.

  1. Implémentez le constructeur pub fn new() → Self pour la structure Tree, cette méthode retournera un arbre vide.

  2. Implémentez le constructeur pub fn leaf(value: i32) → Self qui retournera un nœud de valeur value sans enfants.

  3. Rajoutez des tests pour ces deux méthodes.

Implémentation de contains et insert

Pour tester si une valeur appartient à l’ABR il suffit de parcourir l’arbre en partant de la racine. Pour chaque nœud l’on compare la valeur cherchée à la valeur courante. Si elle sont identiques alors nous retournons true. Si ce n’est pas le cas nous recursivons soit sur l’arbre gauche lorsque la valeur est inférieure ou l’arbre droit lorsque la valeur est supérieure.

  1. Implémentez dans Tree la méthode pub fn contains(&self, value: i32) → bool qui retourne vrai si et seulement si value est présente dans un des nœuds de l’arbre.

Pour rajouter une nouvelle valeur à l’ABR on utilise une méthode similaire. Nous parcourons l’arbre: - si la racine est vide, nous le remplaçons par une feuille avec la nouvelle valeur; - si la racine est pleine, nous vérifions que la valeur n’est pas déjà présente (en effet il n’y a pas de doublons dans un ABR) puis nous recursivons sur le sous-arbre gauche ou le sous-arbre droit de manière à préserver l’invariant d’ordre.

  1. Implémentez la méthode pub fn insert(&mut self, value: i32) → bool qui insère value dans l’ABR. La méthode retourne true lorsque l’insertion est possible et false lorsque la valeur est déjà présente dans l’arbre.

  2. Rajoutez des tests unitaires pour les méthodes contains et insert.

Implémentation de delete

La supression d’une valeur est un peu plus compliquée. Une méthode de supression efficace est expliquée sur l’article français de Wikipedia.

  1. Implémentez la méthode pub fn delete(&mut self, value: i32) en suivant l’algorithme précédent. La méthode retourne true lorsque la supression est possible et retourne false lorsque la valeur n’est pas trouvée dans l’ABR.

Pour aller plus loin…​

  1. Pour l’instant notre ABR utilise des entiers signés 32 bits (i32); néanmoins il est facile en utilisant un type générique de l’étendre à tout type possédant un ordre total. Pour spécifier que votre type générique possède un ordre total vous pouvez utiliser le trait Ord.

  2. Plutôt que de retourner un booleen pour vérifier que les opérations insert et delete se sont bien déroulées il est aussi possible de retourner une erreur à l’aide d’un objet de type Result<>. Cela vous permettra d’explorer un autre mécanisme de gestion d’erreurs dans Rust.

Crédits

You might also like...
🔎 Impossibly fast web search, made for static sites.
🔎 Impossibly fast web search, made for static sites.

Stork Impossibly fast web search, made for static sites. Stork is two things. First, it's an indexer: it indexes your loosely-structured content and c

🦔 Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.
🦔 Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

🦔 Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

⚡ Insanely fast, 🌟 Feature-rich searching. lnx is the adaptable deployment of the tantivy search engine you never knew you wanted.  Standing on the shoulders of giants.
⚡ Insanely fast, 🌟 Feature-rich searching. lnx is the adaptable deployment of the tantivy search engine you never knew you wanted. Standing on the shoulders of giants.

✨ Feature Rich | ⚡ Insanely Fast An ultra-fast, adaptable deployment of the tantivy search engine via REST. 🌟 Standing On The Shoulders of Giants lnx

⚡ Insanely fast, 🌟 Feature-rich searching. lnx is the adaptable deployment of the tantivy search engine you never knew you wanted.  Standing on the shoulders of giants.
⚡ Insanely fast, 🌟 Feature-rich searching. lnx is the adaptable deployment of the tantivy search engine you never knew you wanted. Standing on the shoulders of giants.

✨ Feature Rich | ⚡ Insanely Fast An ultra-fast, adaptable deployment of the tantivy search engine via REST. 🌟 Standing On The Shoulders of Giants lnx

 Rapidly Search and Hunt through Windows Event Logs
Rapidly Search and Hunt through Windows Event Logs

Rapidly Search and Hunt through Windows Event Logs Chainsaw provides a powerful ‘first-response’ capability to quickly identify threats within Windows

Cross-platform, cross-browser, cross-search-engine duckduckgo-like bangs

localbang Cross-platform, cross-browser, cross-search-engine duckduckgo-like bangs What are "bangs"?? Bangs are a way to define where to search inside

🔎 A simple in-memory search for collections and key-value stores.
🔎 A simple in-memory search for collections and key-value stores.

Indicium Search 🔎 A simple in-memory search for collections (Vec, HashMap, BTreeMap, etc) and key-value stores. Features autocompletion. There are ma

weggli is a fast and robust semantic search tool for C and C++ codebases. It is designed to help security researchers identify interesting functionality in large codebases.
weggli is a fast and robust semantic search tool for C and C++ codebases. It is designed to help security researchers identify interesting functionality in large codebases.

weggli Introduction weggli is a fast and robust semantic search tool for C and C++ codebases. It is designed to help security researchers identify int

A Rust API search engine

Roogle Roogle is a Rust API search engine, which allows you to search functions by names and type signatures. Progress Available Queries Function quer

Owner
Bynawers
Bynawers
stringsext - search for multi-byte encoded strings in binary data

title stringsext - search for multi-byte encoded strings in binary data stringsext is a Unicode enhancement of the GNU strings tool with additional fu

Jens Getreu 89 Dec 14, 2022
A simple and lightweight fuzzy search engine that works in memory, searching for similar strings (a pun here).

simsearch A simple and lightweight fuzzy search engine that works in memory, searching for similar strings (a pun here). Documentation Usage Add the f

Andy Lok 116 Dec 10, 2022
Lightning Fast, Ultra Relevant, and Typo-Tolerant Search Engine

MeiliSearch Website | Roadmap | Blog | LinkedIn | Twitter | Documentation | FAQ ⚡ Lightning Fast, Ultra Relevant, and Typo-Tolerant Search Engine ?? M

MeiliSearch 31.6k Dec 31, 2022
High-performance log search engine.

NOTE: This project is under development, please do not depend on it yet as things may break. MinSQL MinSQL is a log search engine designed with simpli

High Performance, Kubernetes Native Object Storage 359 Nov 27, 2022
Perlin: An Efficient and Ergonomic Document Search-Engine

Table of Contents 1. Perlin Perlin Perlin is a free and open-source document search engine library build on top of perlin-core. Since the first releas

CurrySoftware GmbH 70 Dec 9, 2022
Tantivy is a full-text search engine library inspired by Apache Lucene and written in Rust

Tantivy is a full text search engine library written in Rust. It is closer to Apache Lucene than to Elasticsearch or Apache Solr in the sense it is no

tantivy 7.4k Dec 28, 2022
A full-text search and indexing server written in Rust.

Bayard Bayard is a full-text search and indexing server written in Rust built on top of Tantivy that implements Raft Consensus Algorithm and gRPC. Ach

Bayard Search 1.8k Dec 26, 2022
AI-powered search engine for Rust

txtai: AI-powered search engine for Rust txtai executes machine-learning workflows to transform data and build AI-powered text indices to perform simi

NeuML 69 Jan 2, 2023
A full-text search engine in rust

Toshi A Full-Text Search Engine in Rust Please note that this is far from production ready, also Toshi is still under active development, I'm just slo

Toshi Search 3.8k Jan 7, 2023
🔍TinySearch is a lightweight, fast, full-text search engine. It is designed for static websites.

tinysearch TinySearch is a lightweight, fast, full-text search engine. It is designed for static websites. TinySearch is written in Rust, and then com

null 2.2k Dec 31, 2022