Algoritmos e estruturas de dados em Swift: do básico às técnicas avançadas
Compreender algoritmos e estruturas de dados é a base que diferencia um desenvolvedor iniciante de um engenheiro experiente. Para um desenvolvedor iOS/macOS que trabalha com Swift, esse conhecimento impacta diretamente o desempenho do aplicativo, o tempo de resposta da interface e a eficiência do uso de memória. Neste artigo, vamos explorar as principais estruturas de dados e algoritmos que todo desenvolvedor Swift precisa conhecer, com exemplos práticos de código.
1. Estruturas de dados básicas em Swift: arrays, conjuntos e dicionários
O Swift fornece coleções internas poderosas, mas entender seu funcionamento interno ajuda a escrever código mais eficiente.
Arrays (Array)
Um array é uma coleção ordenada de elementos com acesso por índice. Em Swift, Array é implementado como um array dinâmico com complexidade amortizada O(1) para adicionar ao final.
// Criação e operações básicasvar numbers = [1, 2, 3, 4, 5]numbers.append(6) // O(1) na médianumbers.insert(0, at: 0) // O(n) — deslocamento de elementoslet first = numbers[0] // O(1)numbers.removeLast() // O(1)numbers.remove(at: 2) // O(n) — deslocamentoConjuntos (Set)
Set é uma coleção não ordenada de elementos únicos. É implementado com base em uma hash table, oferecendo O(1) para inserção, remoção e busca.
var uniqueNumbers: Set<Int> = [1, 2, 3, 3, 4]print(uniqueNumbers) // [1, 2, 3, 4] — duplicatas removidasuniqueNumbers.insert(5) // O(1)let contains = uniqueNumbers.contains(3) // true, O(1)Dicionários (Dictionary)
Um array associativo para armazenar pares "chave-valor". Também é baseado em hash tables.
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. Estruturas de dados personalizadas: pilhas, filas e listas encadeadas
Embora o Swift não tenha implementações internas de pilha e fila, é fácil implementá-las por conta própria.
Pilha (Stack) — LIFO
Uma pilha funciona pelo princípio "último a entrar, primeiro a sair". É usada para desfazer ações, percorrer árvores em profundidade.
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()!) // 2Fila (Queue) — FIFO
Uma fila funciona pelo princípio "primeiro a entrar, primeiro a sair". É usada para processar tarefas na ordem de chegada.
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 encadeada (Linked List)
Uma lista encadeada consiste em nós, cada um referenciando o próximo. Permite inserir/remover elementos no meio da lista de forma eficiente.
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