Algoritmos y estructuras de datos en Rust: guía completa para principiantes
Rust es un lenguaje de programación que combina el alto rendimiento de C/C++ con la seguridad de memoria garantizada en tiempo de compilación. Para desarrolladores principiantes, estudiar algoritmos y estructuras de datos en Rust no solo es una forma de dominar conceptos fundamentales de Ciencias de la Computación, sino también una oportunidad para comprender las características únicas del lenguaje: propiedad (ownership), préstamo (borrowing) y tiempos de vida (lifetimes).
En este artículo, analizaremos las estructuras de datos y algoritmos clave que todo desarrollador debería conocer. Todos los ejemplos de código están escritos en Rust y listos para ejecutarse. Comenzaremos con las estructuras básicas proporcionadas por la biblioteca estándar y luego pasaremos a la implementación de algoritmos clásicos.
1. Estructuras de datos principales en Rust
La biblioteca estándar de Rust (std) proporciona un rico conjunto de estructuras de datos listas para usar. Veamos las tres más importantes.
Vectores (Vec<T>)
Vec<T> es un arreglo dinámico que puede cambiar su tamaño. Es el equivalente de ArrayList en Java o list en Python. Los vectores garantizan una disposición contigua de los elementos en memoria, lo que hace que el acceso por índice sea muy rápido (O(1)).
fn main() { // Crear un vector vacío let mut numbers: Vec<i32> = Vec::new(); // Agregar elementos numbers.push(10); numbers.push(20); numbers.push(30); // Acceso por índice let first = numbers[0]; // 10 // Iteración for num in &numbers { println!("{}", num); } // Macro vec! let fruits = vec!["apple", "banana", "cherry"]; println!("Segunda fruta: {}", fruits[1]); // banana}Mapas hash (HashMap<K, V>)
HashMap es una estructura de datos que almacena pares clave-valor. Proporciona un tiempo de acceso promedio O(1) para inserción, búsqueda y eliminación. Es ideal para tareas donde se necesita encontrar datos rápidamente por clave.
use std::collections::HashMap;
fn main() { let mut scores = HashMap::new(); scores.insert(String::from("Alice"), 100); scores.insert(String::from("Bob"), 85); // Obtener valor por clave let alice_score = scores.get(&String::from("Alice")); match alice_score { Some(score) => println!("Alice obtuvo: {}", score), None => println!("Alice no encontrada"), } // Recorrer todos los pares for (name, score) in &scores { println!("{}: {}", name, score); }}Cadenas (String)
En Rust existen dos tipos principales de cadenas: &str (rebanada de cadena) y String (cadena mutable y propietaria). Trabajar con cadenas en Rust requiere atención debido a la codificación UTF-8.
fn main() { let mut hello = String::from("Hola, "); hello.push_str("mundo!"); // Agregar cadena hello.push('!'); // Agregar carácter println!("{}", hello); // Hola, mundo!! // Rebanadas de cadena (¡cuidado con los límites de caracteres!) let slice = &hello[0..5]; // "Hola" println!("Rebanada: {}", slice);}2. Implementación de pila y cola
La pila y la cola son estructuras de datos fundamentales que se utilizan a menudo en algoritmos. En Rust se pueden implementar fácilmente con Vec.
Pila (LIFO — Last In, First Out)
La pila funciona bajo el principio "último en entrar, primero en salir". Vec ya proporciona los métodos push y pop, que implementan el comportamiento de una pila.
fn main() { let mut stack: Vec<i32> = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("Extrayendo: {}", top); } // Salida: 3, 2, 1}