1. Fundamentos de Algoritmos
¿Qué es un Algoritmo?
Un algoritmo es un conjunto finito y ordenado de pasos que resuelve un problema específico.
Ejemplo paso a paso - Algoritmo para preparar un sándwich:
- Tomar dos rebanadas de pan
- Colocar jamón sobre una rebanada
- Colocar queso sobre el jamón
- Colocar la segunda rebanada de pan encima
- El sándwich está listo
Complejidad Algorítmica Básica
Mide la eficiencia de un algoritmo en términos de tiempo y espacio.
Ejemplo sencillo - Análisis de un algoritmo que suma elementos:
func suma(arr []int) int{
total := 0
for i :=0; i < len(arr); i++ {
total += arr[i]
}
return total
}
Análisis:
- La inicialización toma tiempo constante: O(1)
- El bucle se ejecuta n veces (n = tamaño del arreglo)
- Complejidad total: O(n) - crece linealmente con el tamaño de entrada
2. Algoritmos de Búsqueda Simples
Búsqueda Lineal
El método más sencillo: recorre el arreglo elemento por elemento.
Ejemplo paso a paso:
Buscando el valor 6 en [4, 2, 7, 1, 6, 9, 3]
- ¿4 = 6? No
- ¿2 = 6? No
- ¿7 = 6? No
- ¿1 = 6? No
- ¿6 = 6? Sí, ¡Encontrado en la posición 4!
Búsqueda Binaria
Más eficiente pero requiere un arreglo ordenado.
Ejemplo paso a paso:
Buscando el valor 7 en [1, 2, 3, 4, 5, 6, 7, 8, 9]
- Mitad: 5 (índice 4)
- 7 > 5, por lo que buscamos en la mitad derecha: [6, 7, 8, 9]
- Mitad: 7 (índice 6 del arreglo original)
- 7 = 7, ¡Encontrado!
3. Algoritmos de Ordenamiento Básicos
Ordenamiento por Inserción
Simula cómo ordenamos cartas en nuestra mano.
Ejemplo paso a paso:
Ordenando [5, 2, 4, 6, 1, 3]
- [5] está ordenado
- Insertamos 2: [2, 5]
- Insertamos 4: [2, 4, 5]
- Insertamos 6: [2, 4, 5, 6]
- Insertamos 1: [1, 2, 4, 5, 6]
- Insertamos 3: [1, 2, 3, 4, 5, 6]
Ordenamiento por Selección
Busca repetidamente el elemento mínimo y lo coloca al principio.
Ejemplo paso a paso:
Ordenando [64, 25, 12, 22, 11]
- Mínimo = 11, intercambiamos con 64: [11, 25, 12, 22, 64]
- Mínimo = 12, intercambiamos con 25: [11, 12, 25, 22, 64]
- Mínimo = 22, intercambiamos con 25: [11, 12, 22, 25, 64]
- Mínimo = 25, ya está en su lugar: [11, 12, 22, 25, 64]
- Resultado: [11, 12, 22, 25, 64]
Ordenamiento Burbuja
Compara pares adyacentes y los intercambia si están en orden incorrecto.
Ejemplo paso a paso:
Ordenando [5, 1, 4, 2, 8]
Primera pasada:
- [5, 1, 4, 2, 8] → [1, 5, 4, 2, 8]
- [1, 5, 4, 2, 8] → [1, 4, 5, 2, 8]
- [1, 4, 5, 2, 8] → [1, 4, 2, 5, 8]
- [1, 4, 2, 5, 8] (no hay cambio)
Segunda pasada:
- [1, 4, 2, 5, 8] → [1, 4, 2, 5, 8]
- [1, 4, 2, 5, 8] → [1, 2, 4, 5, 8]
- [1, 2, 4, 5, 8] (no hay cambios)
Tercera pasada: sin cambios, terminamos con [1, 2, 4, 5, 8]
4. Estructuras de Datos Básicas
Arreglos y Matrices
Colecciones indexadas de elementos del mismo tipo.
Ejemplo paso a paso - Operaciones con arreglos:
- Crear un arreglo:
arr = [10, 20, 30, 40, 50]
arr := []int{10, 20, 30, 40, 50}
- Acceder al elemento en posición 2:
arr[2]= 30
fmt.Println(arr[2])
- Modificar un elemento:
arr[1] = 25→ [10, 25, 30, 40, 50]
arr[1] = 25
- Recorrer el arreglo:
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
Listas Enlazadas
Secuencia de nodos donde cada uno apunta al siguiente.
Ejemplo paso a paso - Creación de una lista enlazada simple:
- Creamos el nodo con valor 10 (cabeza)
- Creamos el nodo con valor 20 y lo conectamos a la cabeza
- Creamos el nodo con valor 30 y lo conectamos al nodo anterior
- Resultado: 10 → 20 → 30
5. Algoritmos Recursivos
Un algoritmo que se llama a sí mismo con un caso base para terminar.
Ejemplo paso a paso - Cálculo del factorial:
Factorial(4):
- ¿4 = 0? No, entonces 4 * Factorial(3)
- ¿3 = 0? No, entonces 3 * Factorial(2)
- ¿2 = 0? No, entonces 2 * Factorial(1)
- ¿1 = 0? No, entonces 1 * Factorial(0)
- ¿0 = 0? Sí, devuelve 1
- Subiendo: 1 * 1 = 1
- Subiendo: 2 * 1 = 2
- Subiendo: 3 * 2 = 6
- Subiendo: 4 * 6 = 24
- Resultado: Factorial(4) = 24
Ejemplo paso a paso - Secuencia de Fibonacci:
Fibonacci(5):
- ¿5 <= 1? No
-
Calculamos Fibonacci(4) y Fibonacci(3) - Para Fibonacci(4): - ¿4 <= 1? No - Calculamos Fibonacci(3) y Fibonacci(2) - (seguimos el proceso recursivo) - Para Fibonacci(3): - (similar al anterior)
- Resultado: Fibonacci(5) = 5
6. Algoritmos de Ordenamiento Avanzados
Mergesort
Divide el arreglo a la mitad, ordena cada mitad y luego las combina.
Ejemplo paso a paso:
Arreglo: [5, 2, 4, 7, 1, 3, 6]
- Dividimos en mitades: - [5, 2, 4] y [7, 1, 3, 6]
- Seguimos dividiendo: - [5], [2, 4] y [7], [1, 3, 6] - [5], [2], [4] y [7], [1], [3, 6] - [5], [2], [4] y [7], [1], [3], [6]
- Combinamos en orden: - [2, 5], [4] y [1, 7], [3, 6] - [2, 4, 5] y [1, 3, 6, 7] - [1, 2, 3, 4, 5, 6, 7]
Quicksort
Utiliza la estrategia “divide y vencerás” con un elemento pivote.
Ejemplo paso a paso:
Ordenando [7, 2, 1, 6, 8, 5, 3, 4]
- Elegimos un pivote (usemos 4)
- Colocamos elementos menores a la izquierda y mayores a la derecha - [2, 1, 3] 4 [7, 6, 8, 5]
- Recursivamente ordenamos los subarreglos - Para [2, 1, 3]: - Pivote: 3 - [1, 2] 3 [] - [1, 2] ya está ordenado: [1, 2, 3] - Para [7, 6, 8, 5]: - Pivote: 5 - [] 5 [7, 6, 8] - Para [7, 6, 8]: - Pivote: 8 - [7, 6] 8 [] - Para [7, 6]: - Pivote: 6 - [] 6 [7] - Resultado: [6, 7] - Resultado: [6, 7, 8] - Resultado: [5, 6, 7, 8]
- Resultado final: [1, 2, 3, 4, 5, 6, 7, 8]
7. Estructuras de Datos Avanzadas
Pilas y Colas
Estructuras con reglas específicas de acceso.
Ejemplo paso a paso - Operaciones con una pila:
- Creamos una pila vacía:
pila = [] - Insertamos (push) elementos:
-
pila.push(5)→ [5] -pila.push(8)→ [5, 8] -pila.push(3)→ [5, 8, 3] - Eliminamos (pop) elementos:
-
pila.pop()→ devuelve 3, pila = [5, 8] -pila.pop()→ devuelve 8, pila = [5]
Árboles Binarios
Cada nodo tiene como máximo dos hijos.
Ejemplo paso a paso - Construcción de un árbol binario de búsqueda:
Insertamos: 5, 3, 7, 2, 4, 6, 8
graph TD
A[5] -->|Paso 1| B[5]
B -->|Paso 2: 3 < 5| C[5]
C -->|Izquierda| D[3]
C -->|Paso 3: 7 > 5| E[5]
E -->|Izquierda| F[3]
E -->|Derecha| G[7]
E -->|Paso 4: 2 < 5, 2 < 3| H[5]
H -->|Izquierda| I[3]
I -->|Izquierda| J[2]
H -->|Derecha| K[7]
H -->|Paso 5: 4 < 5, 4 > 3| L[5]
L -->|Izquierda| M[3]
M -->|Izquierda| N[2]
M -->|Derecha| O[4]
L -->|Derecha| P[7]
L -->|Paso 6: 6 > 5, 6 < 7| Q[5]
Q -->|Izquierda| R[3]
R -->|Izquierda| S[2]
R -->|Derecha| T[4]
Q -->|Derecha| U[7]
U -->|Izquierda| V[6]
Q -->|Paso 7: 8 > 5, 8 > 7| W[5]
W -->|Izquierda| X[3]
X -->|Izquierda| Y[2]
X -->|Derecha| Z[4]
W -->|Derecha| AA[7]
AA -->|Izquierda| AB[6]
AA -->|Derecha| AC[8]
El diagrama muestra paso a paso cómo se va construyendo el árbol binario de búsqueda al insertar cada elemento. El árbol final se ve así:
graph TD
A[5] -->|Izquierda| B[3]
A -->|Derecha| C[7]
B -->|Izquierda| D[2]
B -->|Derecha| E[4]
C -->|Izquierda| F[6]
C -->|Derecha| G[8]
Recorridos en Árboles Binarios
Existen tres formas principales de recorrer un árbol binario:
graph TD
A[Recorridos de Árboles Binarios] --> B[Preorden Raíz-Izquierda-Derecha]
A --> C[Inorden Izquierda-Raíz-Derecha]
A --> D[Postorden Izquierda-Derecha-Raíz]
B --> E["Preorden para árbol ejemplo: 5, 3, 2, 4, 7, 6, 8"]
C --> F["Inorden para árbol ejemplo: 2, 3, 4, 5, 6, 7, 8 (ordenado en BST)"]
D --> G["Postorden para árbol ejemplo: 2, 4, 3, 6, 8, 7, 5"]
8. Técnicas de Diseño de Algoritmos - Divide y Vencerás con Árboles
El algoritmo Mergesort puede ser representado como un árbol de operaciones:
graph TD
A["[5, 2, 4, 7, 1, 3, 6]"] --> B["[5, 2, 4]"]
A --> C["[7, 1, 3, 6]"]
B --> D["[5]"]
B --> E["[2, 4]"]
E --> F["[2]"]
E --> G["[4]"]
C --> H["[7]"]
C --> I["[1, 3, 6]"]
I --> J["[1]"]
I --> K["[3, 6]"]
K --> L["[3]"]
K --> M["[6]"]
D --> N["[5]"]
F --> O["[2]"]
G --> P["[4]"]
H --> Q["[7]"]
J --> R["[1]"]
L --> S["[3]"]
M --> T["[6]"]
O --> U["[2]"]
P --> V["[4]"]
U & V --> W["[2, 4]"]
N & W --> X["[2, 4, 5]"]
R --> Y["[1]"]
S --> Z["[3]"]
T --> AA["[6]"]
Q --> AB["[7]"]
Y & Z --> AC["[1, 3]"]
AA & AB --> AD["[6, 7]"]
AC & AD --> AE["[1, 3, 6, 7]"]
X & AE --> AF["[1, 2, 3, 4, 5, 6, 7]"]
9. Algoritmos de Grafos
Representación de Grafos
graph LR
A((A)) --- B((B))
A --- C((C))
B --- D((D))
C --- D
subgraph "Matriz de Adyacencia"
MA[" A B C D<br>A 0 1 1 0<br>B 1 0 0 1<br>C 1 0 0 1<br>D 0 1 1 0"]
end
subgraph "Lista de Adyacencia"
LA["A: [B, C]<br>B: [A, D]<br>C: [A, D]<br>D: [B, C]"]
end
BFS (Búsqueda en Anchura)
graph TD
A((A)) --> B((B))
A --> C((C))
B --> D((D))
B --> E((E))
C --> F((F))
E --> F
subgraph "Orden de Visita BFS"
BFS["Nivel 0: A
Nivel 1: B, C
Nivel 2: D, E, F
Recorrido BFS: A, B, C, D, E, F"]
end
DFS (Búsqueda en Profundidad)
graph TD
A((A)) --> B((B))
A --> C((C))
B --> D((D))
B --> E((E))
C --> F((F))
E --> F
A -->|1| B
B -->|2| D
B -->|3| E
E -->|4| F
F -->|5| C
subgraph "Orden de Visita DFS"
DFS["1. A (inicio)<br>2. B (primer hijo de A)<br>3. D (primer hijo de B)<br>4. E (segundo hijo de B)<br>5. F (hijo de E)<br>6. C (segundo hijo de A, ya visitamos F)<br><br>Recorrido DFS: A, B, D, E, F, C"]
end
Algoritmo de Dijkstra
graph TD
A((A)) -->|2| B((B))
A -->|4| C((C))
B -->|1| C
B -->|3| D((D))
C -->|1| D
graph TD
subgraph "N°3: Desde B"
P3["A = 0 ✓<br>B = 2 ✓<br>C = 3 (mejora)<br>D = 5<br>No visitados: C,D"]
end
subgraph "N°2: Desde A"
P2["A = 0 ✓<br>B = 2<br>C = 4<br>D = ∞<br>No visitados: B,C,D"]
end
subgraph "N°1: Inicialización"
P1["A = 0<br>B = ∞<br>C = ∞<br>D = ∞<br>No visitados: A,B,C,D"]
end
graph TD
subgraph "Resultado Final"
RF["Distancias mínimas desde A:<br>A: 0<br>B: 2<br>C: 3<br>D: 4"]
end
subgraph "N°5: Desde D"
P5["A = 0 ✓<br>B = 2 ✓<br>C = 3 ✓<br>D = 4 ✓<br>Todos visitados"]
end
subgraph "N°4: Desde C"
P4["A = 0 ✓<br>B = 2 ✓<br>C = 3 ✓<br>D = 4 (mejora)<br>No visitados: D"]
end
Estructuras de Datos Jerárquicas Adicionales
Árbol AVL (Árbol Binario Autobalanceado)
graph TD
subgraph "Árbol Desequilibrado (Factor de Balance: 2)"
A1[10] -->|Izquierda| B1[5]
A1 -->|Derecha| C1[15]
B1 -->|Izquierda| D1[3]
B1 -->|Derecha| E1[7]
D1 -->|Izquierda| F1[1]
end
subgraph "Rotación Derecha"
A2[5] -->|Izquierda| B2[3]
A2 -->|Derecha| C2[10]
B2 -->|Izquierda| D2[1]
C2 -->|Izquierda| E2[7]
C2 -->|Derecha| F2[15]
end
subgraph "Factor de Balance (FB)"
FB["FB = altura(izquierda) - altura(derecha)<br><br>FB = -1, 0, 1 → Árbol balanceado<br>FB < -1 o FB > 1 → Requiere rotación"]
end
Árbol B (para Bases de Datos)
graph TD
A["[15, 30]"] -->|≤ 15| B["[5, 10]"]
A -->|15-30| C["[20, 25]"]
A -->|≥ 30| D["[35, 40, 45]"]
B -->|≤ 5| E["[1, 3]"]
B -->|5-10| F["[7, 8]"]
B -->|≥ 10| G["[12, 13]"]
C -->|≤ 20| H["[16, 18]"]
C -->|20-25| I["[22, 23]"]
C -->|≥ 25| J["[27, 28]"]
D -->|≤ 35| K["[32, 33]"]
D -->|35-40| L["[37, 38]"]
D -->|40-45| M["[42, 43]"]
D -->|≥ 45| N["[47, 48]"]
subgraph "Propiedades del Árbol B"
P1["• Todas las hojas están al mismo nivel<br>• Cada nodo tiene entre m/2 y m hijos<br>• Los datos están ordenados<br>• Optimizado para almacenamiento en disco"]
end
Los diagramas Mermaid muestran claramente la estructura y funcionamiento de los árboles y grafos, facilitando la comprensión de estos conceptos fundamentales en algoritmos.
Estos diagramas ilustran:
- La construcción paso a paso de un árbol binario de búsqueda
- El árbol binario final con todos los elementos
- Los diferentes tipos de recorridos en árboles (preorden, inorden, postorden)
- La visualización del algoritmo Mergesort como un árbol de operaciones
- Las representaciones de grafos con matriz y lista de adyacencia
- Los algoritmos BFS y DFS con el orden de visita de los nodos
- El algoritmo de Dijkstra para encontrar el camino más corto
- Los árboles AVL con sus rotaciones para mantener el balance
- La estructura de un árbol B utilizado en bases de datos
Estos diagramas son una herramienta poderosa para comprender y visualizar los conceptos fundamentales de estructuras de datos, algoritmos y estructuras de datos avanzadas.