Tipos de Datos, Estructuras de Datos y Ficheros
Tipos Elementales, Estructuras Convencionales y Dinámicas, Organización y Acceso a Ficheros
📌 Introducción
Los tipos de datos y las estructuras de datos constituyen los fundamentos esenciales de la programación y el desarrollo de software. Comprender cómo los datos se representan en memoria, cómo se organizan mediante estructuras apropiadas, y cómo se almacenan persistentemente en ficheros es crucial para diseñar algoritmos eficientes y sistemas informáticos robustos. Los tipos elementales de datos (enteros, reales, caracteres, booleanos) proporcionan los bloques de construcción básicos con los que operan los programas. Las estructuras de datos —tanto convencionales (arrays, registros) como dinámicas (listas enlazadas, árboles, grafos)— permiten organizar colecciones de datos de manera que faciliten operaciones específicas de inserción, eliminación, búsqueda y recorrido. Los ficheros, por su parte, posibilitan el almacenamiento persistente de información más allá de la ejecución del programa, con diferentes organizaciones (secuencial, directa, indexada) optimizadas para distintos patrones de acceso. Este tema proporciona una base conceptual sólida y práctica que todo profesional de informática debe dominar, independientemente del paradigma de programación o tecnología específica que utilice posteriormente.
1. Tipos Elementales de Datos
1.1. Concepto de Tipo de Dato
Un tipo de dato define el conjunto de valores que una variable puede tomar y las operaciones que se pueden realizar sobre esos valores. Los tipos de datos proporcionan:
- Abstracción: Ocultan detalles de representación en memoria
- Seguridad: Previenen operaciones inválidas mediante verificación de tipos
- Eficiencia: Permiten al compilador/intérprete optimizar almacenamiento y operaciones
- Legibilidad: Hacen el código más comprensible declarando qué representa cada variable
1.2. Tipos Elementales Fundamentales
| Tipo | Descripción | Rango/Valores | Tamaño Típico | Operaciones |
|---|---|---|---|---|
| Entero (Integer) | Números enteros sin parte decimal | int: -2,147,483,648 a 2,147,483,647 long: ±9 quintillones aprox. |
int: 4 bytes long: 8 bytes |
+, -, *, /, %, comparaciones |
| Real/Flotante (Float/Double) | Números con parte decimal (coma flotante) | float: ±3.4E38 (7 dígitos precisión) double: ±1.7E308 (15 dígitos) |
float: 4 bytes double: 8 bytes |
+, -, *, /, potencias, raíces, trascendentes |
| Carácter (Char) | Símbolos individuales (letras, dígitos, signos) | ASCII: 0-255 Unicode: 0-1,114,111 |
1 byte (ASCII) 2-4 bytes (Unicode) |
Comparaciones, concatenación, conversiones |
| Booleano (Boolean) | Valores lógicos | true (verdadero) false (falso) |
1 bit (teórico) 1 byte (típico) |
AND, OR, NOT, XOR, comparaciones |
| Cadena (String) | Secuencia de caracteres | Longitud variable (típicamente hasta límites de memoria) | Variable | Concatenación, subcadenas, búsqueda, comparación |
1.3. Representación en Memoria
Enteros: Complemento a Dos
Los enteros con signo se representan típicamente usando complemento a dos, que permite aritmética eficiente:
Ejemplo 8 bits: +5: 00000101 -5: 11111011 (invertir bits de +5 y sumar 1) Ventaja: Suma/resta funcionan igual independientemente del signo +5 + (-5) = 00000101 + 11111011 = 00000000 = 0
Reales: Formato IEEE 754
Los números de coma flotante se representan según el estándar IEEE 754:
ℹ️ Estructura IEEE 754 (32 bits float)
- 1 bit de signo: 0 = positivo, 1 = negativo
- 8 bits exponente: Con sesgo de 127
- 23 bits mantisa: Parte fraccionaria normalizada (bit implícito 1)
Valor = (-1)^signo × 1.mantisa × 2^(exponente-127)
Ejemplo: 5.75 = 101.11₂ = 1.0111₂ × 2²
- Signo: 0
- Exponente: 2 + 127 = 129 = 10000001₂
- Mantisa: 0111 0000 0000 0000 0000 000
Caracteres: ASCII y Unicode
- ASCII (American Standard Code for Information Interchange): 7 bits (128 caracteres), extendido a 8 bits (256 caracteres). ‘A’ = 65, ‘a’ = 97, ‘0’ = 48
- Unicode: Estándar universal con >1 millón de caracteres
- UTF-8: Codificación variable 1-4 bytes, compatible con ASCII
- UTF-16: 2 o 4 bytes
- UTF-32: 4 bytes fijos
1.4. Tipado Fuerte vs. Débil
| Característica | Tipado Fuerte | Tipado Débil |
|---|---|---|
| Definición | Tipos estrictamente enforced, conversiones explícitas requeridas | Conversiones implícitas automáticas entre tipos |
| Verificación | Errores de tipo detectados en compilación/ejecución | Conversiones automáticas pueden ocultar errores |
| Seguridad | Mayor seguridad, menos bugs sutiles | Más flexible pero potencialmente inseguro |
| Ejemplos Lenguajes | Java, C#, Python, Haskell | C, JavaScript, PHP |
| Ejemplo Código | int x = 5; String s = «hola»; // x = s; // ERROR! |
var x = 5; var s = «hola»; x = s; // OK en JS |
1.5. Tipado Estático vs. Dinámico
- Tipado Estático (Java, C++, C#, Go): Tipos determinados en compilación
- Ventajas: Detección temprana de errores, optimización, documentación implícita, IDEs más potentes
- Desventajas: Más verboso, menos flexible
- Tipado Dinámico (Python, JavaScript, Ruby, PHP): Tipos determinados en ejecución
- Ventajas: Código más conciso, mayor flexibilidad, desarrollo rápido
- Desventajas: Errores de tipo solo se detectan al ejecutar, menor rendimiento
2. Estructuras Convencionales de Datos
2.1. Concepto de Estructura de Datos
Una estructura de datos es una forma particular de organizar y almacenar datos para que puedan ser accedidos y modificados eficientemente. La elección de la estructura de datos apropiada depende de:
- Operaciones requeridas: Búsqueda, inserción, eliminación, ordenación, recorrido
- Frecuencia de operaciones: Qué operaciones son más comunes
- Restricciones de espacio: Memoria disponible
- Restricciones de tiempo: Requisitos de rendimiento
- Complejidad de implementación: Simplicidad vs. sofisticación
2.2. Arrays (Arreglos/Vectores)
✅ Características de Arrays
- Definición: Colección de elementos del mismo tipo almacenados contiguamente en memoria
- Acceso: Directo mediante índice en tiempo O(1)
- Tamaño: Fijo en lenguajes estáticos (C, Java), dinámico en otros (Python)
- Dimensiones: Unidimensionales (vectores), multidimensionales (matrices, tensores)
- Memoria: Contigua, predecible, cache-friendly
Operaciones en Arrays
| Operación | Complejidad | Descripción |
|---|---|---|
| Acceso por índice | O(1) | array[i] accede directamente al elemento i |
| Búsqueda (no ordenado) | O(n) | Recorrido secuencial hasta encontrar elemento |
| Búsqueda (ordenado) | O(log n) | Búsqueda binaria |
| Inserción al final | O(1) | Si hay espacio disponible |
| Inserción en posición | O(n) | Requiere desplazar elementos |
| Eliminación | O(n) | Requiere desplazar elementos para llenar hueco |
Arrays Multidimensionales
Los arrays multidimensionales se pueden almacenar en memoria de dos formas:
- Row-major order (fila principal): Elementos de una fila están contiguos. Usado en C, C++, Python
Array 2D: [1 2 3] Memoria: [1 2 3 4 5 6 7 8 9] [4 5 6] [7 8 9] - Column-major order (columna principal): Elementos de una columna están contiguos. Usado en Fortran, MATLAB
Array 2D: [1 2 3] Memoria: [1 4 7 2 5 8 3 6 9] [4 5 6] [7 8 9]
2.3. Registros (Records/Structs)
Un registro es una estructura de datos heterogénea que agrupa elementos de diferentes tipos bajo un único nombre.
// Ejemplo en C
struct Empleado {
int id;
char nombre[50];
float salario;
struct Fecha fechaContratacion;
};
// Ejemplo en Python (usando dataclass)
from dataclasses import dataclass
@dataclass
class Empleado:
id: int
nombre: str
salario: float
fecha_contratacion: datetime
Características de Registros
- Heterogeneidad: Campos pueden ser de diferentes tipos
- Acceso por nombre: empleado.nombre, empleado.salario
- Memoria: Campos almacenados secuencialmente (con posible padding para alineación)
- Anidación: Registros pueden contener otros registros
- Uso: Modelar entidades del mundo real con múltiples atributos
2.4. Cadenas de Caracteres (Strings)
Las cadenas son secuencias de caracteres. Su implementación varía según lenguaje:
- C: Array de char terminado en '\0' (null-terminated)
char saludo[] = "Hola"; // Internamente: ['H','o','l','a','\0']
- Java/C#: Objetos inmutables con longitud explícita
String saludo = "Hola"; // Objeto String inmutable
- Python: Secuencias inmutables de caracteres Unicode
saludo = "Hola" # str inmutable, soporta Unicode nativo
Operaciones Comunes en Cadenas
- Concatenación: "Hola" + " Mundo" → "Hola Mundo"
- Subcadenas: "Hola"[0:2] → "Ho"
- Longitud: len("Hola") → 4
- Búsqueda: "Hola".find("la") → 2
- Reemplazo: "Hola".replace("o", "a") → "Hala"
- División: "uno,dos,tres".split(",") → ["uno", "dos", "tres"]
2.5. Enumeraciones (Enums)
Tipo de dato que consiste en un conjunto de constantes nombradas:
// Java
enum DiaSemana {
LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
}
// Python
from enum import Enum
class DiaSemana(Enum):
LUNES = 1
MARTES = 2
MIERCOLES = 3
JUEVES = 4
VIERNES = 5
SABADO = 6
DOMINGO = 7
Ventajas de Enumeraciones:
- Código más legible (nombres descriptivos vs. números mágicos)
- Seguridad de tipos (solo valores válidos permitidos)
- Facilita refactorización
- Autodocumentación del conjunto de valores posibles
3. Estructuras Dinámicas de Datos
3.1. Concepto de Estructura Dinámica
Las estructuras dinámicas utilizan asignación dinámica de memoria (heap) y punteros/referencias para crecer y decrecer en tiempo de ejecución según necesidad. A diferencia de arrays con tamaño fijo, las estructuras dinámicas se adaptan a volúmenes de datos variables.
⚠️ Gestión de Memoria en Estructuras Dinámicas
- Lenguajes con gestión manual (C, C++): Programador responsable de malloc/free o new/delete. Riesgo de memory leaks y dangling pointers
- Lenguajes con garbage collection (Java, C#, Python): Recolector de basura automáticamente libera memoria no referenciada
- Lenguajes con ownership (Rust): Sistema de tipos garantiza seguridad de memoria en compilación sin GC
3.2. Listas Enlazadas
Lista Simplemente Enlazada
Secuencia de nodos donde cada nodo contiene datos y referencia al siguiente nodo.
Estructura Nodo:
┌─────────┬──────┐
│ Dato: 5 │ Next │─── →
└─────────┴──────┘
Lista: [10] → [20] → [30] → NULL
Implementación Python:
class Nodo:
def __init__(self, dato):
self.dato = dato
self.siguiente = None
class ListaEnlazada:
def __init__(self):
self.cabeza = None
def insertar_inicio(self, dato):
nuevo = Nodo(dato)
nuevo.siguiente = self.cabeza
self.cabeza = nuevo
Ventajas y Desventajas de Listas Enlazadas
| Ventajas | Desventajas |
|---|---|
| Inserción/eliminación O(1) conociendo posición | Acceso secuencial O(n), no acceso directo |
| Tamaño dinámico, crece/decrece según necesidad | Overhead de memoria por punteros |
| No requiere memoria contigua | Cache-unfriendly, mala localidad espacial |
| Fácil implementación de pilas/colas | No permite búsqueda binaria |
Variantes de Listas Enlazadas
- Lista doblemente enlazada: Cada nodo tiene punteros a siguiente Y anterior, permite recorrido bidireccional
NULL ← [10] ↔ [20] ↔ [30] → NULL
- Lista circular: Último nodo apunta al primero, no hay NULL
┌────────────────┐
↓ │ [10] → [20] → [30]┘ - Lista con nodo centinela: Nodo especial ficticio simplifica lógica de inserción/eliminación
3.3. Pilas (Stacks)
Estructura LIFO (Last In, First Out - Último en Entrar, Primero en Salir)
ℹ️ Operaciones de Pila
- push(x): Añadir elemento en el tope
- pop(): Eliminar y retornar elemento del tope
- peek()/top(): Ver elemento del tope sin eliminarlo
- isEmpty(): Verificar si pila está vacía
Complejidad: Todas las operaciones son O(1)
Visualización:
┌───┐
│ 3 │ ← tope (push 3)
├───┤
│ 2 │
├───┤
│ 1 │
└───┘
Aplicaciones:
- Evaluación de expresiones (notación postfija)
- Gestión de llamadas a funciones (call stack)
- Deshacer/Rehacer en editores
- Navegación hacia atrás en navegadores
- Parsing de lenguajes
3.4. Colas (Queues)
Estructura FIFO (First In, First Out - Primero en Entrar, Primero en Salir)
ℹ️ Operaciones de Cola
- enqueue(x): Añadir elemento al final
- dequeue(): Eliminar y retornar elemento del frente
- front(): Ver elemento del frente sin eliminarlo
- isEmpty(): Verificar si cola está vacía
Complejidad: Todas las operaciones son O(1)
Visualización:
frente final
↓ ↓
[1] → [2] → [3] → [4] → [5]
enqueue(6): añadir al final
dequeue(): eliminar desde frente (retorna 1)
Aplicaciones:
- Buffers de impresión
- Manejo de procesos en CPU (scheduling)
- Breadth-First Search (BFS) en grafos
- Gestión de tareas asíncronas
- Colas de mensajes en sistemas distribuidos
Variantes de Colas
- Cola circular: Implementación eficiente usando array con índices wrap-around
- Cola de prioridad: Elementos tienen prioridad, se extrae el de mayor prioridad (implementada típicamente con heap)
- Deque (Double-Ended Queue): Permite inserción/eliminación en ambos extremos
3.5. Árboles
Estructura jerárquica donde cada nodo tiene un padre (excepto raíz) y cero o más hijos.
Terminología de Árboles
- Raíz: Nodo superior sin padre
- Hoja: Nodo sin hijos
- Nivel: Distancia de un nodo a la raíz (raíz en nivel 0)
- Altura: Nivel máximo en el árbol
- Grado: Número de hijos de un nodo
- Subárbol: Árbol formado por un nodo y sus descendientes
Árbol Binario
Cada nodo tiene como máximo dos hijos (izquierdo y derecho)
Representación:
(10)
/ \
(5) (15)
/ \ / \
(3) (7)(12)(20)
Implementación:
class NodoArbol:
def __init__(self, dato):
self.dato = dato
self.izquierdo = None
self.derecho = None
Árbol Binario de Búsqueda (BST)
Árbol binario con propiedad de ordenación: para cada nodo, todos los valores en subárbol izquierdo son menores y todos en subárbol derecho son mayores.
✅ Operaciones en BST (promedio)
- Búsqueda: O(log n) - comparar y descender por rama apropiada
- Inserción: O(log n) - buscar posición y añadir hoja
- Eliminación: O(log n) - casos: hoja, un hijo, dos hijos
Peor caso: O(n) si árbol degenerado (lista enlazada)
Solución: Árboles auto-balanceados (AVL, Red-Black Tree)
Recorridos de Árboles Binarios
| Recorrido | Orden de Visita | Ejemplo (árbol anterior) | Uso Típico |
|---|---|---|---|
| Preorden (Preorder) | Raíz → Izquierda → Derecha | 10, 5, 3, 7, 15, 12, 20 | Copiar árbol, serialización |
| Inorden (Inorder) | Izquierda → Raíz → Derecha | 3, 5, 7, 10, 12, 15, 20 | BST en orden ascendente |
| Postorden (Postorder) | Izquierda → Derecha → Raíz | 3, 7, 5, 12, 20, 15, 10 | Eliminar árbol, calcular expresiones |
| Por Niveles (Level-order) | Nivel por nivel, izq. a der. | 10, 5, 15, 3, 7, 12, 20 | BFS, imprimir por niveles |
3.6. Grafos
Conjunto de vértices (nodos) conectados por aristas (edges). Generalizan árboles permitiendo ciclos y múltiples padres.
Tipos de Grafos
- Dirigidos vs. No dirigidos: Aristas con/sin dirección
- Ponderados vs. No ponderados: Aristas con/sin peso asociado
- Conexos vs. Disconexos: Existe/no existe camino entre cualquier par de vértices
- Cíclicos vs. Acíclicos: Contienen/no contienen ciclos
Representaciones de Grafos
| Representación | Descripción | Espacio | Ventajas | Desventajas |
|---|---|---|---|---|
| Matriz de Adyacencia | Matriz n×n donde M[i][j]=1 si hay arista i→j | O(V²) | Verificación de arista O(1), simple para grafos densos | Ineficiente para grafos sparse, O(V²) espacio siempre |
| Lista de Adyacencia | Array de listas, cada vértice tiene lista de vecinos | O(V+E) | Eficiente para grafos sparse, recorrer vecinos rápido | Verificación de arista O(V) en peor caso |
| Lista de Aristas | Lista de pares (u,v) representando aristas | O(E) | Simple, compacto | Ineficiente para búsqueda de vecinos |
Algoritmos Fundamentales en Grafos
- Recorrido: DFS (Depth-First Search), BFS (Breadth-First Search)
- Camino más corto: Dijkstra, Bellman-Ford, Floyd-Warshall
- Árbol de Expansión Mínima: Kruskal, Prim
- Ordenación Topológica: Para grafos dirigidos acíclicos (DAG)
- Detección de Ciclos: Mediante DFS con colores
- Componentes Conexas: Identificar subgrafos desconectados
3.7. Tablas Hash (Hash Tables)
Estructura que mapea claves a valores mediante función hash, proporcionando acceso en tiempo promedio O(1).
ℹ️ Componentes de Tabla Hash
- Array subyacente: Almacena elementos
- Función hash: h(key) → índice en array
- Manejo de colisiones: Cuando dos claves tienen mismo hash
Resolución de Colisiones
| Método | Descripción | Ventajas | Desventajas |
|---|---|---|---|
| Encadenamiento (Chaining) | Cada celda contiene lista enlazada de elementos con mismo hash | Simple, factor de carga >1 posible, eliminación sencilla | Memoria extra para punteros, cache-unfriendly |
| Direccionamiento Abierto (Open Addressing) | Buscar siguiente posición libre mediante probing | Mejor localidad cache, sin punteros extra | Factor de carga debe mantenerse bajo, eliminación compleja |
| - Linear Probing | Secuencia h(k), h(k)+1, h(k)+2, ... | Simple, buena localidad | Clustering primario |
| - Quadratic Probing | Secuencia h(k), h(k)+1², h(k)+2², ... | Reduce clustering primario | Clustering secundario |
| - Double Hashing | Secuencia h(k), h(k)+h2(k), h(k)+2*h2(k), ... | Minimiza clustering | Dos funciones hash necesarias |
4. Ficheros
4.1. Concepto de Fichero
Un fichero (archivo) es una colección de información relacionada almacenada en dispositivo de almacenamiento secundario (disco duro, SSD, etc.) que persiste más allá de la ejecución del programa. Los ficheros permiten:
- Persistencia: Datos sobreviven al cierre del programa
- Compartición: Múltiples programas pueden acceder al mismo fichero
- Almacenamiento masivo: Datos que no caben en memoria RAM
- Backup y recuperación: Copias de seguridad de información crítica
- Transferencia: Intercambio de datos entre sistemas
4.2. Atributos de Ficheros
Los sistemas de ficheros asocian metadatos a cada fichero:
| Atributo | Descripción | Ejemplo |
|---|---|---|
| Nombre | Identificador legible por humanos | documento.txt, foto.jpg |
| Tipo/Extensión | Indica formato del contenido | .txt, .pdf, .exe, .csv |
| Tamaño | Número de bytes | 1,024 bytes (1 KB) |
| Ubicación | Dirección en disco | Bloques 1000-1005 |
| Permisos | Control de acceso (lectura, escritura, ejecución) | rwxr-xr-- (Unix) |
| Propietario | Usuario/grupo dueño del fichero | usuario:grupo |
| Fechas | Creación, modificación, último acceso | 2023-10-15 14:30:00 |
4.3. Tipos de Ficheros según Contenido
Ficheros de Texto
- Contenido: Secuencia de caracteres legibles (ASCII, UTF-8)
- Estructura: Líneas separadas por caracteres newline (\n, \r\n)
- Legibilidad: Pueden abrirse con editores de texto
- Portabilidad: Alta entre sistemas operativos (aunque newline varía)
- Ejemplos: .txt, .csv, .json, .xml, .html, código fuente
- Ventajas: Legibles por humanos, fácil debugging, versionado amigable (git)
- Desventajas: Mayor tamaño, parsing necesario, menor eficiencia
Ficheros Binarios
- Contenido: Secuencia de bytes que representan datos en formato interno de la máquina
- Estructura: Definida por especificación del formato
- Legibilidad: No legibles en editores de texto, requieren software específico
- Portabilidad: Potencialmente problemática (endianness, tamaños de tipos)
- Ejemplos: .exe, .dll, .jpg, .mp3, .pdf, .docx, bases de datos
- Ventajas: Compactos, acceso directo sin parsing, eficientes
- Desventajas: No legibles por humanos, dependientes de formato/software
4.4. Organización de Ficheros
4.4.1. Ficheros Secuenciales
Registros almacenados uno tras otro en orden de entrada.
✅ Características de Ficheros Secuenciales
- Estructura: Registros consecutivos, sin índices ni punteros
- Acceso: Secuencial desde principio, no acceso directo
- Inserción: Solo al final (append) sin reorganizar
- Actualización: Requiere reescribir fichero completo típicamente
- Búsqueda: Secuencial O(n), recorrer hasta encontrar
- Ordenación: Puede mantenerse orden lógico pero no físico obligatorio
- Uso: Logs, backups, archivos históricos, procesamiento batch
Representación:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ Reg 1 │ Reg 2 │ Reg 3 │ Reg 4 │ Reg 5 │
└─────────┴─────────┴─────────┴─────────┴─────────┘
Para leer Registro 4: leer Reg 1, Reg 2, Reg 3, Reg 4
Ejemplo Python:
# Escritura
with open('empleados.txt', 'a') as f:
f.write(f"{id},{nombre},{salario}\n")
# Lectura secuencial
with open('empleados.txt', 'r') as f:
for linea in f:
procesar(linea)
Ventajas y Desventajas de Ficheros Secuenciales
| Ventajas | Desventajas |
|---|---|
| Simple de implementar y entender | Acceso lento a registros específicos |
| Eficiente para procesamiento completo del fichero | Actualización/eliminación ineficiente |
| No requiere estructuras adicionales (índices) | No adecuado para consultas aleatorias |
| Óptimo para datos con acceso secuencial natural | Inserción solo al final |
4.4.2. Ficheros Directos (Acceso Aleatorio)
Registros de tamaño fijo almacenados de modo que cualquier registro puede accederse directamente mediante su posición.
ℹ️ Características de Ficheros Directos
- Estructura: Registros de tamaño fijo, posición calculable
- Acceso: Directo a cualquier registro en tiempo O(1)
- Cálculo posición: Posición_byte = registro_num × tamaño_registro
- Inserción: En cualquier posición libre
- Actualización: In-place, sin mover otros registros
- Búsqueda: Directo si se conoce posición, secuencial si búsqueda por contenido
- Uso: Bases de datos, índices, aplicaciones que requieren acceso aleatorio rápido
Representación (registros de 100 bytes cada uno):
Byte: 0 100 200 300 400 500
┌───────┬───────┬───────┬───────┬───────┐
│ Reg 0 │ Reg 1 │ Reg 2 │ Reg 3 │ Reg 4 │
└───────┴───────┴───────┴───────┴───────┘
Para leer Registro 3: seek(3 * 100 = 300), read(100 bytes)
Ejemplo Python:
import struct
# Definir formato registro (id: int, nombre: 50 chars, salario: float)
formato = 'i50sf' # int, 50 char string, float
tam_registro = struct.calcsize(formato)
# Leer registro específico
def leer_registro(fichero, num_registro):
with open(fichero, 'rb') as f:
f.seek(num_registro * tam_registro)
datos = f.read(tam_registro)
return struct.unpack(formato, datos)
# Escribir registro en posición
def escribir_registro(fichero, num_registro, id, nombre, salario):
with open(fichero, 'r+b') as f:
f.seek(num_registro * tam_registro)
datos = struct.pack(formato, id, nombre.encode(), salario)
f.write(datos)
Ventajas y Desventajas de Ficheros Directos
| Ventajas | Desventajas |
|---|---|
| Acceso rápido a cualquier registro O(1) | Registros deben ser tamaño fijo (desperdicio de espacio) |
| Actualización in-place eficiente | Fragmentación interna por padding |
| Lectura/escritura aleatorias eficientes | No adecuado para registros de longitud variable |
| Ideal para índices y tablas hash | Gestión de registros eliminados (marcas de borrado) |
4.4.3. Ficheros Indexados
Combinan datos almacenados secuencialmente con índices separados que permiten acceso directo.
✅ Estructura de Fichero Indexado
- Fichero de datos: Registros almacenados (pueden ser tamaño variable)
- Fichero índice: Tabla de pares (clave, dirección) ordenada por clave
- Búsqueda:
- Buscar clave en índice (búsqueda binaria O(log n) si ordenado)
- Obtener dirección/puntero del registro
- Acceder directamente al registro en fichero de datos
- Múltiples índices: Posibles índices por diferentes campos
Estructura:
Fichero Índice (ordenado por ID):
┌────────┬──────────┐
│ ID │ Dirección│
├────────┼──────────┤
│ 101 │ 0 │
│ 205 │ 150 │
│ 310 │ 300 │
│ 415 │ 480 │
└────────┴──────────┘
Fichero de Datos:
Byte: 0 150 300 480
┌───────────┬───────────┬───────────┬───────────┐
│ Reg ID101 │ Reg ID205 │ Reg ID310 │ Reg ID415 │
└───────────┴───────────┴───────────┴───────────┘
Buscar empleado ID=310:
1. Búsqueda binaria en índice → encuentra dirección 300
2. Seek a posición 300 en fichero datos
3. Leer registro
Tipos de Índices
- Índice primario: Ordenado según clave primaria, fichero datos también ordenado por esa clave
- Índice secundario: Sobre campo no clave, permite búsquedas por campos alternativos
- Índice denso: Entrada en índice por cada registro
- Índice disperso: Entradas solo para algunos registros (fichero datos debe estar ordenado)
- Índice multinivel: Índice sobre índice, árbol B+
Ventajas y Desventajas de Ficheros Indexados
| Ventajas | Desventajas |
|---|---|
| Acceso rápido por clave mediante índice | Overhead de mantener índices actualizados |
| Registros pueden ser tamaño variable | Espacio adicional para almacenar índices |
| Múltiples índices para diferentes consultas | Inserción/eliminación más lenta (actualizar índices) |
| Acceso secuencial también posible | Complejidad de implementación mayor |
4.5. Métodos de Acceso a Ficheros
4.5.1. Acceso Secuencial
Lectura de registros en orden, desde el principio hasta el final.
# Python - Acceso secuencial
with open('datos.txt', 'r') as f:
for linea in f:
procesar(linea)
# Java - Acceso secuencial
BufferedReader br = new BufferedReader(new FileReader("datos.txt"));
String linea;
while ((linea = br.readLine()) != null) {
procesar(linea);
}
br.close();
Características:
- Simple y eficiente para procesar todo el fichero
- Aprovecha lectura anticipada del sistema operativo (read-ahead)
- Óptimo para ficheros en medios secuenciales (cintas magnéticas históricamente)
- No requiere seek, posición avanza automáticamente
4.5.2. Acceso Directo/Aleatorio
Lectura/escritura en cualquier posición del fichero mediante seek.
# Python - Acceso aleatorio
with open('datos.bin', 'r+b') as f:
# Leer byte en posición 1000
f.seek(1000)
dato = f.read(4)
# Escribir en posición 5000
f.seek(5000)
f.write(nuevo_dato)
# Posicionamiento relativo
f.seek(100, 1) # Avanzar 100 bytes desde posición actual
f.seek(-50, 2) # Retroceder 50 bytes desde final
# Java - Acceso aleatorio
RandomAccessFile raf = new RandomAccessFile("datos.bin", "rw");
raf.seek(1000);
int dato = raf.readInt();
raf.seek(5000);
raf.writeInt(nuevoDato);
raf.close();
Operaciones seek:
- seek(offset, whence): Posicionar puntero de fichero
- whence=0: Desde inicio (absoluto)
- whence=1: Desde posición actual (relativo)
- whence=2: Desde final
- tell(): Obtener posición actual del puntero
4.5.3. Acceso Indexado
Uso de índices para localizar rápidamente registros por clave.
# Pseudocódigo acceso indexado
def buscar_por_clave(indice, fichero_datos, clave_buscada):
# 1. Búsqueda en índice
entrada = indice.buscar(clave_buscada) # Búsqueda binaria O(log n)
if entrada:
# 2. Acceso directo a datos
fichero_datos.seek(entrada.direccion)
registro = fichero_datos.read(entrada.tamaño)
return registro
else:
return None
4.5.4. Acceso Hash
Uso de función hash para calcular posición directa del registro.
# Pseudocódigo acceso hash
def buscar_por_hash(fich
ero, clave):
# Calcular posición mediante función hash
posicion = hash(clave) % tamaño_fichero
# Acceder directamente a esa posición
fichero.seek(posicion * tamaño_registro)
registro = fichero.read(tamaño_registro)
if registro.clave == clave:
return registro
else:
# Manejo de colisiones (linear probing, chaining, etc.)
return resolver_colision(fichero, clave, posicion)
Ventajas del acceso hash:
- Acceso en tiempo O(1) promedio
- No requiere índices separados
- Muy eficiente para grandes volúmenes de datos
Desventajas del acceso hash:
- Colisiones requieren resolución
- Espacio desperdiciado si factor de carga bajo
- No mantiene orden de claves
- Búsquedas por rango ineficientes
4.6. Operaciones sobre Ficheros
Operaciones Básicas
| Operación | Descripción | Ejemplo (Python) |
|---|---|---|
| Abrir | Establecer conexión con el fichero | f = open('datos.txt', 'r') |
| Leer | Obtener datos del fichero | contenido = f.read() |
| Escribir | Grabar datos en el fichero | f.write('Hola mundo') |
| Cerrar | Liberar recursos y asegurar escritura | f.close() |
| Posicionar | Mover puntero a posición específica | f.seek(100) |
| Eliminar | Borrar fichero del sistema | os.remove('datos.txt') |
| Renombrar | Cambiar nombre del fichero | os.rename('viejo.txt', 'nuevo.txt') |
Modos de Apertura de Ficheros
| Modo | Significado | Descripción |
|---|---|---|
| 'r' | Read (Lectura) | Solo lectura, fichero debe existir |
| 'w' | Write (Escritura) | Escritura, crea nuevo o sobrescribe existente |
| 'a' | Append (Añadir) | Escritura al final, crea si no existe |
| 'r+' | Read/Write | Lectura y escritura, fichero debe existir |
| 'w+' | Write/Read | Lectura y escritura, crea nuevo o sobrescribe |
| 'a+' | Append/Read | Lectura y añadir al final |
| 'rb', 'wb', 'ab' | Modo binario | Añadir 'b' para ficheros binarios |
Gestión de Recursos: Context Managers
# Python - Uso recomendado con context manager (with)
# Cierra automáticamente el fichero incluso si hay excepción
with open('datos.txt', 'r') as f:
contenido = f.read()
procesar(contenido)
# Fichero cerrado automáticamente aquí
# Equivalente sin context manager (menos recomendado)
f = open('datos.txt', 'r')
try:
contenido = f.read()
procesar(contenido)
finally:
f.close() # Asegurar cierre incluso con excepción
5. Mapa Conceptual
Mapa Conceptual: Tipos de Datos, Estructuras de Datos y Ficheros
- Entero: números sin decimales (int, long)
- Real: coma flotante (float, double - IEEE 754)
- Carácter: símbolos (char - ASCII/Unicode)
- Booleano: valores lógicos (true/false)
- Cadena: secuencias de caracteres (string)
- Tipado fuerte vs. débil
- Tipado estático vs. dinámico
- Representación en memoria: complemento a 2, IEEE 754
- Colección homogénea, tamaño fijo
- Acceso directo O(1) por índice
- Memoria contigua, cache-friendly
- Multidimensionales: row-major, column-major
- Colección heterogénea (diferentes tipos)
- Acceso por nombre de campo
- Modelado de entidades complejas
- Conjunto de constantes nombradas
- Seguridad de tipos, legibilidad
- Simple, doble, circular
- Inserción/eliminación O(1) conociendo posición
- Acceso secuencial O(n)
- Pila: LIFO (push, pop, peek) O(1)
- Cola: FIFO (enqueue, dequeue, front) O(1)
- Aplicaciones: call stack, BFS, buffers
- Jerárquicos: raíz, nodos, hojas
- Árbol Binario de Búsqueda (BST)
- Recorridos: preorden, inorden, postorden, por niveles
- Operaciones O(log n) promedio
- Vértices y aristas
- Representación: matriz adyacencia, lista adyacencia
- Algoritmos: DFS, BFS, Dijkstra
- Función hash: clave → índice
- Acceso O(1) promedio
- Colisiones: chaining, open addressing
- Texto: legibles, ASCII/UTF-8, mayor tamaño
- Binarios: compactos, eficientes, específicos
- Secuencial: registros consecutivos, acceso secuencial
- Directo: registros tamaño fijo, acceso aleatorio O(1)
- Indexado: índice + datos, búsqueda O(log n)
- Secuencial: inicio a fin
- Aleatorio: seek a posición específica
- Indexado: búsqueda en índice + acceso directo
- Hash: función hash para calcular posición
Leyenda del Mapa Conceptual:
Nivel 1: Concepto principal Nivel 2: Categorías principales Nivel 3: Componentes y detalles6. Preguntas de Evaluación
Pregunta 1
¿Cuál es la principal ventaja de los arrays sobre las listas enlazadas?
Explicación: La principal ventaja de los arrays es el acceso directo (aleatorio) a cualquier elemento mediante su índice en tiempo constante O(1). Dado que los elementos están almacenados contiguamente en memoria, se puede calcular la dirección de cualquier elemento simplemente como: dirección_base + (índice × tamaño_elemento). Las listas enlazadas requieren recorrido secuencial O(n) para acceder a un elemento. Las opciones A y C son incorrectas porque las listas enlazadas tienen ventajas en inserción/eliminación y tamaño dinámico. La opción D es parcialmente incorrecta: aunque los arrays no tienen overhead de punteros, ambas estructuras pueden desperdiciar memoria de formas diferentes.
Pregunta 2
¿Qué estructura de datos sigue el principio LIFO (Last In, First Out)?
Explicación: La pila (stack) es la estructura de datos que implementa el principio LIFO (Last In, First Out - Último en Entrar, Primero en Salir), donde el último elemento añadido es el primero en ser extraído. Las operaciones fundamentales son push (apilar elemento en el tope) y pop (desapilar elemento del tope). La cola (opción A) sigue FIFO (First In, First Out). Las listas enlazadas (opción C) y árboles binarios (opción D) no siguen inherentemente ninguno de estos principios, aunque pueden implementarlos. Aplicaciones comunes de pilas incluyen: gestión de llamadas a funciones (call stack), evaluación de expresiones, navegación hacia atrás en navegadores, y operaciones deshacer/rehacer.
Pregunta 3
¿Cuál es la complejidad temporal promedio de búsqueda en un árbol binario de búsqueda (BST) balanceado?
Explicación: En un árbol binario de búsqueda (BST) balanceado, la búsqueda tiene complejidad O(log n) porque en cada paso se descarta aproximadamente la mitad de los elementos restantes (similar a búsqueda binaria). La altura del árbol balanceado es log n, y la búsqueda desciende nivel por nivel. O(1) (opción A) sería tabla hash. O(n) (opción C) es el peor caso de BST degenerado (similar a lista enlazada) o búsqueda secuencial. O(n log n) (opción D) es típico de algoritmos de ordenación como mergesort o heapsort. Es importante notar que "balanceado" es clave: árboles como AVL o Red-Black garantizan balance automático manteniendo complejidad logarítmica.
Pregunta 4
¿Qué método de resolución de colisiones en tablas hash almacena múltiples elementos con mismo hash en una lista enlazada?
Explicación: El encadenamiento (chaining) resuelve colisiones haciendo que cada celda de la tabla hash contenga una lista enlazada de todos los elementos que tienen el mismo valor hash. Cuando hay colisión, simplemente se añade el nuevo elemento a la lista correspondiente. Ventajas: simple, permite factor de carga >1, eliminación sencilla. Desventajas: memoria extra para punteros, cache-unfriendly. Linear probing, quadratic probing y double hashing (opciones A, B, D) son técnicas de direccionamiento abierto (open addressing) donde se busca otra posición libre en la propia tabla cuando hay colisión, sin usar estructuras auxiliares.
Pregunta 5
¿Cuál es la característica principal de un fichero de organización secuencial?
Explicación: Los ficheros de organización secuencial almacenan registros uno tras otro en orden de entrada, y el acceso es típicamente secuencial desde el principio. Para encontrar un registro específico, hay que recorrer secuencialmente desde el inicio hasta encontrarlo. Ventajas: simple, eficiente para procesamiento completo del fichero, ideal para logs y backups. Desventajas: búsqueda lenta O(n), actualización ineficiente. La opción A describe ficheros directos. La opción C describe ficheros indexados. La opción D describe acceso hash. El acceso secuencial es apropiado cuando se necesita procesar todos los registros (procesamiento batch) o cuando los datos tienen acceso secuencial natural (cronológico).
Pregunta 6
¿Qué tipo de dato se utiliza típicamente para representar valores lógicos verdadero/falso?
Explicación: El tipo de dato booleano (boolean) está específicamente diseñado para representar valores lógicos binarios: verdadero (true) o falso (false). Teóricamente requiere solo 1 bit, aunque típicamente se almacena en 1 byte por razones de alineación de memoria. Soporta operaciones lógicas: AND, OR, NOT, XOR. Es fundamental en estructuras de control (if, while) y expresiones condicionales. Algunos lenguajes (C) no tienen tipo boolean nativo y usan enteros (0=falso, no-cero=verdadero), pero lenguajes modernos (Java, Python, C#) tienen tipo boolean explícito para mayor claridad y seguridad de tipos.
Pregunta 7
¿Cuál es la ventaja principal de una lista doblemente enlazada sobre una lista simplemente enlazada?
Explicación: La lista doblemente enlazada tiene cada nodo con punteros tanto al siguiente como al anterior nodo, permitiendo recorrido en ambas direcciones. Esto facilita: eliminación de un nodo conociendo solo su referencia (sin necesitar el anterior), navegación hacia atrás, implementación de estructuras como deques. La opción A es incorrecta: usa MÁS memoria (dos punteros por nodo vs. uno). La opción C es incorrecta: inserción tiene misma complejidad O(1) si se conoce posición. La opción D es incorrecta: ambos tipos de listas requieren recorrido secuencial, no acceso directo. El trade-off es memoria adicional por mayor flexibilidad de operaciones.
Pregunta 8
¿Qué recorrido de árbol binario visita los nodos en orden ascendente en un BST?
Explicación: El recorrido inorden (in-order traversal) visita nodos en secuencia: subárbol izquierdo → raíz → subárbol derecho. En un árbol binario de búsqueda (BST), esta secuencia produce valores en orden ascendente debido a la propiedad de ordenación del BST (izquierda < raíz < derecha). Ejemplo: BST con valores {3, 5, 7, 10, 12, 15, 20}, recorrido inorden produce exactamente esa secuencia ordenada. Preorden (opción A) visita raíz primero, usado para copiar árbol. Postorden (opción C) visita raíz al final, usado para eliminar árbol. Por niveles (opción D) usa BFS, no garantiza orden ascendente.
Pregunta 9
¿Qué estructura de representación de grafos es más eficiente en espacio para grafos sparse (dispersos)?
Explicación: La lista de adyacencia usa O(V+E) espacio (V=vértices, E=aristas), almacenando solo las aristas existentes. En grafos sparse donde E << V², esto es mucho más eficiente que matriz de adyacencia que siempre usa O(V²) independientemente del número de aristas. Ejemplo: grafo de 1000 vértices con 5000 aristas (sparse): lista de adyacencia ≈6000 entradas, matriz de adyacencia = 1,000,000 celdas. La lista de adyacencia también facilita recorrer vecinos de un vértice. La matriz de adyacencia (opción A) es mejor para grafos densos o cuando necesitamos verificación rápida O(1) de existencia de arista específica.
Pregunta 10
¿Cuál es la diferencia principal entre un fichero de texto y un fichero binario?
Explicación: Los ficheros de texto contienen secuencias de caracteres legibles (ASCII, UTF-8) que pueden abrirse con editores de texto comunes. Los ficheros binarios contienen bytes que representan datos en formato interno de la máquina (enteros, flotantes en su representación binaria directa), no legibles en editores de texto. La opción A es parcialmente cierta pero no la diferencia "principal": ficheros de texto suelen ser más grandes porque números se almacenan como caracteres (ej: 12345 = 5 bytes en texto vs. 4 bytes como int binario), pero esta es consecuencia, no la diferencia fundamental. La opción B es incorrecta: binarios pueden contener texto. La opción D no es característica distintiva: seguridad depende de permisos y cifrado, no del tipo de fichero.
Pregunta 11
¿Qué método de acceso a ficheros permite leer/escribir en cualquier posición mediante la operación seek?
Explicación: El acceso directo (random access) utiliza la operación seek para posicionar el puntero del fichero en cualquier byte específico, permitiendo lectura/escritura en cualquier ubicación sin recorrer registros previos. Ejemplo: seek(1000) posiciona en byte 1000, luego read(100) lee 100 bytes desde esa posición. Es fundamental para ficheros con registros de tamaño fijo donde posición = número_registro × tamaño_registro. El acceso secuencial (opción A) lee desde inicio sin seek. El acceso indexado (opción C) usa seek pero tras buscar en índice primero. El acceso hash (opción D) calcula posición con función hash antes de usar seek. Todos excepto secuencial pueden usar seek, pero "acceso directo/aleatorio" es el término específico para esta capacidad.
Pregunta 12
¿Cuál de las siguientes es una operación válida en una cola (queue)?
Explicación: Las operaciones estándar de una cola (queue) son enqueue (añadir elemento al final) y dequeue (eliminar elemento del frente), implementando el principio FIFO. También típicamente front() para ver elemento del frente sin eliminarlo, e isEmpty() para verificar si está vacía. Push y pop (opción A) son operaciones de pila (stack). Insert y delete (opción C) son genéricas. Add y remove del medio (opción D) violaría el principio FIFO de la cola. Las colas se usan en: gestión de procesos CPU (scheduling), buffers de impresión, BFS en grafos, sistemas de mensajería, y cualquier escenario donde orden de llegada debe preservarse (primero en llegar, primero en ser atendido).
Pregunta 13
¿Qué ventaja tiene un fichero indexado sobre un fichero secuencial?
Explicación: Los ficheros indexados mantienen uno o más índices (típicamente ordenados) que permiten búsqueda rápida O(log n) mediante búsqueda binaria en el índice, seguida de acceso directo al registro. Esto es mucho más eficiente que búsqueda secuencial O(n) en ficheros secuenciales. Además, pueden tener múltiples índices por diferentes campos. Las opciones A, B y D son incorrectas: ficheros indexados ocupan MÁS espacio (índices adicionales), son MÁS complejos de implementar, y requieren mantenimiento constante (actualizar índices en cada inserción/eliminación). El trade-off es: espacio y complejidad adicionales a cambio de búsquedas mucho más rápidas. Apropiados cuando consultas frecuentes por clave justifican el overhead.
Pregunta 14
¿Cuál es la representación estándar de números de coma flotante en la mayoría de los lenguajes de programación?
Explicación: El estándar IEEE 754 define la representación de números de coma flotante (float, double) usado universalmente en hardware y software moderno. Especifica formato con signo (1 bit), exponente (8 bits float, 11 bits double), y mantisa (23 bits float, 52 bits double). Valor = (-1)^signo × 1.mantisa × 2^(exponente-sesgo). También define valores especiales: ±∞, NaN (Not a Number). Complemento a dos (opción A) es para enteros con signo. BCD (opción B) codifica dígitos decimales, usado en algunos sistemas financieros. ASCII (opción D) codifica caracteres. IEEE 754 permite aritmética de coma flotante eficiente en hardware, aunque tiene limitaciones de precisión inherentes (errores de redondeo).
Pregunta 15
¿Qué estructura de datos es más apropiada para implementar un sistema de deshacer/rehacer en un editor de texto?
Explicación: Un sistema de deshacer/rehacer se implementa típicamente con dos pilas: pila de deshacer y pila de rehacer. Cuando usuario realiza acción, se apila en pila de deshacer. Al deshacer (Ctrl+Z), se desapila de pila de deshacer y se apila en pila de rehacer. Al rehacer (Ctrl+Y), se desapila de pila de rehacer y se apila en pila de deshacer. El comportamiento LIFO de las pilas es perfecto: la acción más reciente es la primera en deshacerse. Una cola (opción A) sería FIFO, incorrecto. Árbol binario (opción B) es complejo innecesario. Tabla hash (opción D) no mantiene orden temporal. Algunas implementaciones sofisticadas usan árbol de estados para ramificación, pero dos pilas es la solución estándar y eficiente.
Pregunta 16
¿Qué representa el "grado" de un nodo en un árbol?
Explicación: El grado de un nodo es el número de hijos (descendientes directos) que tiene ese nodo. En árbol binario, el grado máximo es 2. El grado del árbol completo es el grado máximo entre todos sus nodos. Una hoja tiene grado 0 (sin hijos). La distancia a la raíz (opción A) se llama "nivel" o "profundidad" del nodo. La posición en recorrido inorden (opción C) no tiene nombre especial establecido. El número total de nodos en subárbol (opción D) se llama "tamaño del subárbol". El grado es importante para clasificar árboles: árbol n-ario permite grado hasta n, árbol binario grado máximo 2, etc.
Pregunta 17
¿Cuál es la principal diferencia entre tipado estático y tipado dinámico?
Explicación: Tipado estático (Java, C++, C#): tipos de variables determinados y verificados en tiempo de compilación. Ventajas: detección temprana de errores, optimización, documentación implícita, mejor soporte IDE. Desventajas: más verboso, menos flexible. Tipado dinámico (Python, JavaScript, Ruby): tipos determinados en tiempo de ejecución. Ventajas: código más conciso, mayor flexibilidad, desarrollo rápido. Desventajas: errores de tipo solo en ejecución, menor rendimiento. La opción A es incorrecta: el rendimiento depende de múltiples factores, aunque lenguajes dinámicos suelen ser más lentos. La opción B invierte la definición. La opción D ignora diferencias significativas en desarrollo, debugging y rendimiento.
Pregunta 18
¿Qué modo de apertura en Python permite leer y escribir en un fichero existente sin sobrescribirlo completamente?
Explicación: El modo 'r+' abre fichero existente para lectura Y escritura sin truncarlo (mantiene contenido existente), permitiendo modificaciones in-place usando seek. El fichero debe existir. Modo 'w' (opción A) abre para escritura pero SOBRESCRIBE/trunca contenido existente. Modo 'a' (opción C) abre para añadir al final, pero escrituras siempre van al final independientemente de seek. Modo 'x' (opción D) crea fichero nuevo exclusivamente, falla si ya existe. Para actualizar registro específico en fichero: open('datos.bin', 'r+b'), seek(posicion), write(nuevo_dato). También existe 'w+' que crea nuevo/sobrescribe pero permite lectura, y 'a+' que añade y permite lectura.
Pregunta 19
¿Cuál es la complejidad temporal de inserción al inicio de una lista enlazada?
Explicación: Insertar al inicio de lista enlazada es O(1) porque solo requiere: crear nuevo nodo, hacer que su puntero siguiente apunte a la actual cabeza, y actualizar cabeza para apuntar al nuevo nodo. No requiere recorrer la lista ni mover elementos existentes. Pseudocódigo: nuevo.siguiente = cabeza; cabeza = nuevo. Esta es una ventaja clave de listas enlazadas sobre arrays, donde inserción al inicio requiere desplazar todos los elementos O(n). Sin embargo, inserción en posición arbitraria en lista enlazada es O(n) porque primero hay que encontrar esa posición mediante recorrido secuencial. Arrays tienen ventaja en acceso aleatorio O(1) pero desventaja en inserción O(n).
Pregunta 20
¿Qué problema fundamental intentan resolver los ficheros indexados?
Explicación: Los ficheros indexados resuelven el problema de búsqueda lenta en ficheros secuenciales. Manteniendo índice ordenado separado (clave → dirección), la búsqueda se reduce de O(n) secuencial a O(log n) mediante búsqueda binaria en índice, seguida de acceso directo al registro. Esto es crucial para bases de datos y aplicaciones con consultas frecuentes por clave. La opción A es incorrecta: índices AUMENTAN tamaño total. La opción C es incorrecta: tipo de registros no está relacionado con indexación. La opción D es incorrecta: seguridad se maneja con permisos/cifrado. El trade-off de indexación es: espacio y complejidad adicionales (mantener índices) a cambio de búsquedas mucho más rápidas. Múltiples índices permiten búsquedas eficientes por diferentes campos.
Pregunta 21
¿Qué representación de grafo es más eficiente para verificar rápidamente si existe una arista entre dos vértices específicos?
Explicación: La matriz de adyacencia permite verificación de arista en O(1): simplemente acceder matriz[i][j] y ver si es 1/true (existe arista de i a j). Con lista de adyacencia (opción A), hay que recorrer la lista de vecinos de i buscando j, que es O(grado del vértice), pudiendo ser O(V) en peor caso. Con lista de aristas (opción C), hay que buscar el par (i,j) en la lista, O(E) en peor caso. Sin embargo, matriz de adyacencia tiene desventaja de usar siempre O(V²) espacio, mientras lista de adyacencia usa O(V+E), más eficiente para grafos sparse. La elección depende del patrón de operaciones: matriz mejor para grafos densos con muchas consultas de existencia de arista.
Pregunta 22
¿Cuál es la principal ventaja de usar Unicode UTF-8 sobre ASCII para codificar texto?
Explicación: UTF-8 es codificación de Unicode que soporta más de 1 millón de caracteres, incluyendo todos los idiomas del mundo (chino, árabe, cirílico, etc.), símbolos matemáticos, emojis, caracteres históricos, etc. ASCII solo cubre 128 caracteres (7 bits): inglés básico, números, signos de puntuación. UTF-8 es retrocompatible con ASCII: caracteres ASCII ocupan 1 byte idéntico en UTF-8. Caracteres no-ASCII ocupan 2-4 bytes en UTF-8. La opción A es incorrecta: procesamiento puede ser más complejo por longitud variable. La opción C es incorrecta: UTF-8 usa misma o más memoria que ASCII. La opción D es incorrecta: ASCII es más antiguo (1963) que UTF-8 (1993). UTF-8 es estándar moderno obligatorio para aplicaciones globales e internacionalización.
Pregunta 23
¿Qué estructura de datos es más apropiada para implementar un algoritmo de búsqueda en anchura (BFS) en un grafo?
Explicación: BFS (Breadth-First Search) utiliza una cola para explorar el grafo nivel por nivel: visitar nodo, encolar todos sus vecinos no visitados, dequeue siguiente nodo y repetir. El comportamiento FIFO de la cola garantiza que nodos más cercanos (mismo nivel) se procesen antes que nodos más lejanos. Pseudocódigo: encolar raíz, mientras cola no vacía: dequeue nodo, procesar, encolar vecinos no visitados. Una pila (opción A) implementaría DFS (Depth-First Search), que explora en profundidad. Árbol binario (opción C) y tabla hash (opción D) no son estructuras auxiliares para BFS. BFS se usa para: camino más corto en grafos no ponderados, recorrido por niveles de árbol, encontrar componentes conexas.
Pregunta 24
¿Cuál es la diferencia principal entre un registro (struct) y un array?
Explicación: La diferencia fundamental es: registros (structs) agrupan campos de DIFERENTES tipos bajo un nombre (heterogéneo), accedidos por nombre de campo. Arrays agrupan elementos del MISMO tipo (homogéneo), accedidos por índice numérico. Ejemplo registro: struct Empleado {int id; string nombre; float salario;} - tres tipos diferentes. Ejemplo array: int numeros[100] - todos enteros. Registros modelan entidades con múltiples atributos. Arrays modelan colecciones de elementos similares. La opción A es incorrecta: ambos pueden ser estáticos o dinámicos según lenguaje. La opción C es incorrecta: rendimiento depende del uso específico. Los registros son fundamentales para programación estructurada, permitiendo crear tipos de datos complejos que representan conceptos del dominio del problema.
Pregunta 25
¿Qué ventaja proporciona el uso de context managers (with statement en Python) al trabajar con ficheros?
Explicación: El context manager (with statement) garantiza que el fichero se cierre correctamente incluso si ocurre una excepción durante su uso, implementando patrón RAII (Resource Acquisition Is Initialization). Código: with open('datos.txt', 'r') as f: contenido = f.read(). Al salir del bloque with (normalmente o por excepción), el fichero se cierra automáticamente. Sin context manager, hay que usar try/finally para garantizar cierre, más verboso y propenso a errores. La opción A es incorrecta: no hay beneficio de rendimiento significativo. La opción C es incorrecta: concurrencia requiere locks/semáforos. La opción D es incorrecta: permisos son independientes. Context managers previenen memory leaks, corrupción de datos, y agotamiento de file descriptors.
7. Referencias Bibliográficas
Libros Clásicos
- Cormen, T.H., Leiserson, C.E., Rivest, R.L., & Stein, C. (2009). Introduction to Algorithms, 3rd Edition. MIT Press
- Weiss, M.A. (2011). Data Structures and Algorithm Analysis in C++, 4th Edition. Pearson
- Goodrich, M.T., Tamassia, R., & Goldwasser, M.H. (2013). Data Structures and Algorithms in Python. Wiley
- Sedgewick, R., & Wayne, K. (2011). Algorithms, 4th Edition. Addison-Wesley
- Knuth, D.E. (1997). The Art of Computer Programming, Volume 1: Fundamental Algorithms, 3rd Edition. Addison-Wesley
Libros en Español
- Joyanes Aguilar, L. & Zahonero Martínez, I. (2010). Estructuras de Datos en Java. McGraw-Hill
- Cairó, O. (2005). Estructuras de Datos, 3ª Edición. McGraw-Hill
- Deitel, P. & Deitel, H. (2016). Java: Cómo Programar, 10ª Edición. Pearson
Recursos Online
- VisuAlgo: https://visualgo.net - Visualización interactiva de estructuras de datos y algoritmos
- GeeksforGeeks: https://www.geeksforgeeks.org/data-structures/ - Tutoriales exhaustivos con ejemplos
- Coursera - Algorithms Specialization: Universidad de Stanford
- MIT OpenCourseWare - Introduction to Algorithms: Curso completo gratuito
Documentación Oficial
- Python Documentation: https://docs.python.org - Estructuras de datos integradas
- Java Collections Framework: Documentación Oracle
- C++ STL (Standard Template Library): cppreference.com
