Algoritmos y estructuras de datos en Kotlin: de la teoría a la práctica
\n\nKotlin es un lenguaje moderno, conciso y seguro que se utiliza activamente para el desarrollo en Android, backend e incluso aplicaciones nativas. Sin embargo, su potencia se despliega completamente solo con la aplicación correcta de algoritmos y estructuras de datos. En este artículo, analizaremos conceptos clave con ejemplos prácticos que te ayudarán a escribir código eficiente y mantenible.
\n\n1. Estructuras de datos básicas: listas, pilas y colas
\n\nEmpecemos por lo fundamental. Kotlin proporciona una rica biblioteca estándar, pero entender cómo funcionan las estructuras de datos «bajo el capó» es críticamente importante.
\n\nLista enlazada (LinkedList)
\nImplementemos una lista simplemente enlazada manualmente. Esto ayudará a comprender los principios de trabajo con memoria y referencias.
\n\nclass Node<T>(var value: T, var next: Node<T>? = null)\n\nclass LinkedList<T> {\n private var head: Node<T>? = null\n private var tail: Node<T>? = null\n\n fun add(value: T) {\n val node = Node(value)\n if (head == null) {\n head = node\n tail = node\n } else {\n tail?.next = node\n tail = node\n }\n }\n\n fun printAll() {\n var current = head\n while (current != null) {\n print("${current.value} ")\n current = current.next\n }\n println()\n }\n}\n\n// Uso\nfun main() {\n val list = LinkedList<Int>()\n list.add(1)\n list.add(2)\n list.add(3)\n list.printAll() // Salida: 1 2 3\n}\n\nPila basada en arreglo
\nLa pila funciona bajo el principio LIFO (Last In, First Out). Implementémosla usando una lista mutable de Kotlin.
\n\nclass Stack<T> {\n private val elements = mutableListOf<T>()\n\n fun push(item: T) = elements.add(item)\n fun pop(): T? = if (elements.isNotEmpty()) elements.removeAt(elements.size - 1) else null\n fun peek(): T? = elements.lastOrNull()\n fun isEmpty() = elements.isEmpty()\n}\n\n// Ejemplo: verificación de paréntesis\nfun isBalanced(expression: String): Boolean {\n val stack = Stack<Char>()\n for (char in expression) {\n when (char) {\n '(', '{', '[' -> stack.push(char)\n ')' -> if (stack.pop() != '(') return false\n '}' -> if (stack.pop() != '{') return false\n ']' -> if (stack.pop() != '[') return false\n }\n }\n return stack.isEmpty()\n}\n\nfun main() {\n println(isBalanced("({[]})")) // true\n println(isBalanced("({[})")) // false\n}\n\n2. Algoritmos de ordenamiento: de burbuja a rápido
\n\nEl ordenamiento es uno de los temas más estudiados. Consideremos tres algoritmos clásicos que son fáciles de implementar en Kotlin.
\n\nOrdenamiento de burbuja (Bubble Sort)
\nSimple, pero ineficiente para arreglos grandes (O(n²)).
\n\nfun bubbleSort(arr: IntArray) {\n val n = arr.size\n for (i in 0 until n - 1) {\n for (j in 0 until n - i - 1) {\n if (arr[j] > arr[j + 1]) {\n val temp = arr[j]\n arr[j] = arr[j + 1]\n arr[j + 1] = temp\n }\n }\n }\n}\n\nfun main() {\n val array = intArrayOf(64, 34, 25, 12, 22, 11, 90)\n bubbleSort(array)\n println(array.joinToString()) // 11, 12, 22, 25, 34, 64, 90\n}\n\nOrdenamiento rápido (Quick Sort)
\nUno de los algoritmos más rápidos en el caso promedio (O(n log n)). Usa recursión y partición del arreglo.
\n\nfun quickSort(arr: IntArray, low: Int, high: Int) {\n if (low < high) {\n val pi = partition(arr, low, high)\n quickSort(arr, low, pi - 1)\n quickSort(arr, pi + 1, high)\n }\n}\n\nfun partition(arr: IntArray, low: Int, high: Int): Int {\n val pivot = arr[high]\n var i = low - 1\n for (j in low until high) {\n