Algoritmos y estructuras de datos en Swift: desde lo básico hasta técnicas avanzadas
Comprender los algoritmos y las estructuras de datos es la base que distingue a un desarrollador principiante de un ingeniero experimentado. Para un desarrollador de iOS/macOS que trabaja con Swift, este conocimiento afecta directamente el rendimiento de la aplicación, el tiempo de respuesta de la interfaz y la eficiencia del uso de la memoria. En este artículo, analizaremos las estructuras de datos y algoritmos clave que todo desarrollador de Swift debe conocer, con ejemplos prácticos de código.
1. Estructuras de datos básicas en Swift: arrays, conjuntos y diccionarios
Swift proporciona potentes colecciones integradas, pero comprender su funcionamiento interno ayuda a escribir código más eficiente.
Arrays (Array)
Un array es una colección ordenada de elementos con acceso por índice. En Swift, Array se implementa como un array dinámico con una complejidad amortizada de O(1) para agregar al final.
// Creación y operaciones básicasvar numbers = [1, 2, 3, 4, 5]numbers.append(6) // O(1) en promedionumbers.insert(0, at: 0) // O(n) — desplazamiento de elementoslet first = numbers[0] // O(1)numbers.removeLast() // O(1)numbers.remove(at: 2) // O(n) — desplazamientoConjuntos (Set)
Set es una colección no ordenada de elementos únicos. Está implementada sobre una tabla hash, lo que proporciona O(1) para inserción, eliminación y búsqueda.
var uniqueNumbers: Set<Int> = [1, 2, 3, 3, 4]print(uniqueNumbers) // [1, 2, 3, 4] — duplicados eliminadosuniqueNumbers.insert(5) // O(1)let contains = uniqueNumbers.contains(3) // true, O(1)Diccionarios (Dictionary)
Un array asociativo para almacenar pares "clave-valor". También se basa en tablas hash.
var scores: [String: Int] = ["Alice": 95, "Bob": 87]scores["Charlie"] = 92 // O(1)if let aliceScore = scores["Alice"] { print("Alice: \\(aliceScore)")}scores.removeValue(forKey: "Bob") // O(1)2. Estructuras de datos personalizadas: pilas, colas y listas enlazadas
Aunque Swift no tiene implementaciones integradas de pila y cola, es fácil implementarlas por tu cuenta.
Pila (Stack) — LIFO
Una pila funciona bajo el principio "último en entrar, primero en salir". Se utiliza para deshacer acciones, recorrido en profundidad de árboles.
struct Stack<Element> { private var storage: [Element] = [] mutating func push(_ element: Element) { storage.append(element) } mutating func pop() -> Element? { storage.popLast() } func peek() -> Element? { storage.last } var isEmpty: Bool { storage.isEmpty }}
var stack = Stack<Int>()stack.push(1); stack.push(2); stack.push(3)print(stack.pop()!) // 3print(stack.peek()!) // 2Cola (Queue) — FIFO
Una cola funciona bajo el principio "primero en entrar, primero en salir". Se utiliza para procesar tareas en orden de llegada.
struct Queue<Element> { private var input: [Element] = [] private var output: [Element] = [] mutating func enqueue(_ element: Element) { input.append(element) } mutating func dequeue() -> Element? { if output.isEmpty { output = input.reversed() input.removeAll() } return output.popLast() } var isEmpty: Bool { input.isEmpty && output.isEmpty }}
var queue = Queue<String>()queue.enqueue("First"); queue.enqueue("Second")print(queue.dequeue()!) // "First"Lista enlazada (Linked List)
Una lista enlazada consiste en nodos, cada uno de los cuales hace referencia al siguiente. Permite insertar/eliminar elementos de manera eficiente en el medio de la lista.
class ListNode<T> { var value: T var next: ListNode? init(_ value: T) { self.value = value }}
class LinkedList<T> { var head: ListNode<T>? func append(_ valu