Tema 71. Tipos elementales de datos. Estructuras convencionales de datos. Estructuras dinámicas de datos. Ficheros. Tipos de ficheros: descripción, funcionalidad y clasificación. Organización de ficheros. Concepto y tipos: métodos de acceso en el tratamiento de un fichero.

50 min agosto 5, 2026 Media Nuevo

Tabla de contenidos

Tema 71. Tipos elementales de datos. Estructuras convencionales de datos. Estructuras dinámicas de datos. Ficheros. Tipos de ficheros: descripción, funcionalidad y clasificación. Organización de ficheros. Concepto y tipos: métodos de acceso en el tratamiento de un fichero.

Fundamentos de representación, organización y acceso eficiente a datos en memoria y almacenamiento persistente
Oposición: Técnico/a de Función Administrativa, Sistemas y Tecnología de la Información – Servicio Andaluz de Salud (SAS)
Bloque: Temario Específico | Última actualización: Agosto 2026
Preparador: Esteban Castro | Material basado en exámenes oficiales SAS

1. INTRODUCCIÓN Y ENCUADRE

Los programas no trabajan con información abstracta de manera indiferenciada: operan sobre representaciones concretas que ocupan memoria, admiten unas operaciones y excluyen otras. Por eso, el concepto de tipo de dato es previo al de algoritmo. Un tipo determina un dominio de valores, una representación y un repertorio de operaciones válidas. Sobre esos tipos se construyen estructuras que organizan conjuntos de elementos y, cuando la información debe sobrevivir a la ejecución del proceso, se materializa en ficheros o en otros mecanismos persistentes.

El temario distingue tres planos que conviene mantener separados. El primero es el de los tipos elementales, como enteros, reales, caracteres y booleanos. El segundo es el de las estructuras de datos, que pueden tener tamaño y disposición convencional o estática —arrays, matrices y registros— o variar durante la ejecución —listas enlazadas, árboles, grafos y tablas hash—. El tercero es el de los ficheros, donde interesa tanto el contenido lógico como la organización física, la unidad de transferencia, el método de acceso y las operaciones de mantenimiento.

La elección de una estructura nunca es puramente sintáctica. Está condicionada por el patrón de uso: número de elementos, frecuencia de consultas, inserciones y borrados, necesidad de conservar el orden, consumo de memoria, localidad de referencia, concurrencia y persistencia. Una solución correcta funcionalmente puede ser inadecuada por su complejidad temporal, por desperdiciar memoria o por obligar a recorrer un fichero completo para resolver una consulta puntual.

En sistemas de información sanitarios esta materia tiene una dimensión práctica inmediata. Un listado temporal de citas puede representarse en memoria como una colección ordenada; una cola de trabajos modela peticiones pendientes; un árbol facilita búsquedas jerárquicas; una tabla hash soporta localización por clave; y un fichero secuencial o indexado puede emplearse en procesos de intercambio, carga masiva, trazabilidad o explotación. El profesional TIC debe reconocer la abstracción utilizada, sus garantías y sus límites antes de diseñar una interfaz, un proceso batch o una integración.

Idea central: tipo, estructura, organización y método de acceso son conceptos relacionados, pero no equivalentes. El tipo define qué valores y operaciones son válidos; la estructura organiza elementos en memoria; la organización de fichero determina cómo quedan dispuestos en almacenamiento persistente; y el método de acceso describe cómo los recupera el programa.
Perla de examen: no debe confundirse una estructura lógica con su implementación. Una pila define comportamiento LIFO, pero puede implementarse sobre un array dinámico o sobre una lista enlazada; la complejidad efectiva depende de la implementación elegida.

2. TIPOS ELEMENTALES DE DATOS Y REPRESENTACIÓN

2.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

2.2. Tipos Elementales Fundamentales

Tipo Descripción Rango/Valores Tamaño Típico Operaciones
Entero (Integer) Números enteros sin parte decimal Depende del ancho y del signo; ejemplo con signo de 32 bits: −2.147.483.648 a 2.147.483.647 Dependiente del lenguaje y la implementación; son habituales 1, 2, 4 u 8 bytes +, -, *, /, %, comparaciones
Real/Flotante (Float/Double) Números con parte decimal (coma flotante) IEEE 754 binary32: precisión aproximada de 7 cifras; binary64: unas 15–16 cifras, además de infinitos y NaN binary32: 4 bytes; binary64: 8 bytes +, -, *, /, potencias, raíces, trascendentes
Carácter (Char) Símbolos individuales (letras, dígitos, signos) ASCII: 0–127. Unicode: puntos de código hasta U+10FFFF, con valores reservados y no asignados Depende del lenguaje y la codificación; UTF-8 usa de 1 a 4 bytes por punto de código Comparaciones, concatenación, conversiones
Booleano (Boolean) Valores lógicos true (verdadero)
false (falso)
Representación y alineación dependientes de la implementación AND, OR, NOT, XOR, comparaciones

Las cadenas se estudian como estructura secuencial en un apartado posterior. Algunos lenguajes las ofrecen como tipo incorporado, pero conceptualmente no son un valor elemental indivisible, sino una colección de caracteres o unidades de código con reglas propias de codificación e inmutabilidad.

2.3. Representación en Memoria

2.3.1. 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

2.3.2. 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 de fracción del significando: Parte fraccionaria del significando normalizado; en números normales existe un bit inicial implícito

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

2.3.3. Caracteres: ASCII y Unicode

  • ASCII (American Standard Code for Information Interchange): 7 bits (128 códigos). La expresión «ASCII extendido» agrupa varias páginas de códigos de 8 bits y no designa un único estándar. ‘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

2.4. Representación, dominio y operaciones

Un tipo no se limita al número de bytes reservado. En sentido abstracto comprende el conjunto de valores posibles y las operaciones cerradas o definidas sobre ellos. Por ejemplo, la suma de enteros produce normalmente otro entero del mismo dominio, salvo desbordamiento; la división entera puede truncar; y la comparación booleana produce un valor lógico. Esta visión permite razonar con independencia de un lenguaje concreto.

Los tamaños de los tipos primitivos no son universales. Algunos lenguajes fijan tamaños concretos para determinados tipos, mientras que otros los vinculan a la plataforma, a la implementación o a un perfil normativo. Por ello, en material técnico resulta más correcto hablar de rangos cuando el lenguaje los garantiza y evitar presentar como universal que todo int ocupa cuatro bytes.

Los enteros sin signo representan valores no negativos. Con n bits abarcan de 0 a 2^n−1, aunque la etiqueta ^ no está permitida en la plantilla y debe interpretarse aquí como potencia. En los enteros con signo basados en complemento a dos, un bit participa en la codificación del signo y el rango es asimétrico: existe un valor negativo más que positivos distintos de cero. El desbordamiento puede producir excepción, saturación, aritmética modular o comportamiento dependiente del lenguaje.

La coma flotante IEEE 754 representa valores finitos normalizados y subnormales, ceros con signo, infinitos y valores NaN. No todos los decimales tienen representación binaria exacta; por ello, comparaciones como 0.1 + 0.2 == 0.3 pueden no comportarse como espera quien razona en decimal. Para importes monetarios o cálculos que exigen decimal exacto suelen preferirse tipos decimales, enteros escalados o bibliotecas de precisión arbitraria.

Trampa: IEEE 754 no significa que cualquier operación real sea exacta. La representación tiene precisión finita, aplica reglas de redondeo y puede acumular error. Comparar flotantes mediante igualdad exacta solo es correcto cuando se conocen las propiedades de los operandos.
Perla de examen: UTF-8 es una codificación de Unicode de longitud variable y compatible con ASCII para los puntos de código de 0 a 127. Unicode define repertorio y puntos de código; UTF-8 define cómo se codifican en bytes.

3. SISTEMAS DE TIPOS, CONVERSIONES Y SEMÁNTICA

3.1. Tipado fuerte y tipado débil

La oposición entre tipado fuerte y débil no dispone de una definición única aceptada por todos los autores. Se utiliza para describir hasta qué punto un lenguaje impide mezclar valores incompatibles, exige conversiones explícitas y evita reinterpretaciones inseguras. Por ello conviene tratarla como un espectro y explicar la regla concreta, no limitarse a una etiqueta.

Aspecto Mayor disciplina de tipos Mayor coerción o permisividad
Operaciones incompatibles Se rechazan salvo conversión definida. Pueden activar conversiones implícitas.
Reinterpretación de memoria Restringida por el sistema de tipos. Puede permitirse mediante conversiones de bajo nivel.
Riesgo característico Mayor necesidad de conversiones explícitas. Resultados sorprendentes por coerción o pérdida de información.
Ejemplos orientativos Python, Java y Haskell suelen describirse como fuertemente tipados. JavaScript admite numerosas coerciones; la clasificación de C depende del criterio usado, pues combina comprobación estática con conversiones y operaciones de bajo nivel.
Trampa: Python es dinámico y, a la vez, suele considerarse fuertemente tipado. JavaScript también es dinámico, pero permite coerciones más amplias. Por tanto, estático/dinámico y fuerte/débil no son sinónimos.

3.2. Tipado estático y tipado dinámico

En el tipado estático, la compatibilidad de tipos se comprueba principalmente antes de ejecutar, aunque pueden existir comprobaciones dinámicas adicionales. Java, C#, C++, Rust y Go son ejemplos. Favorece detección temprana, refactorización y optimización, pero no garantiza por sí solo ausencia de errores.

En el tipado dinámico, los valores llevan información de tipo que se comprueba durante la ejecución. Python, JavaScript y Ruby son ejemplos. Aporta flexibilidad y metaprogramación; los errores de tipo aparecen cuando se ejecuta la ruta afectada, aunque pruebas, anotaciones y analizadores estáticos pueden detectarlos antes.

El rendimiento no se deduce únicamente del sistema de tipos: compilación JIT, representación de objetos, especialización y optimizaciones de la implementación pueden reducir o ampliar la diferencia.

3.3. Conversión, promoción y coerción

La conversión de tipos puede ser explícita, cuando el programador solicita una transformación, o implícita, cuando la realiza el lenguaje. Una promoción suele ampliar el dominio sin pérdida —por ejemplo, de un entero pequeño a otro mayor—, mientras que una conversión estrecha puede perder rango, precisión o información. Convertir un real a entero puede truncar o redondear según la operación empleada; convertir texto a número puede fallar si la cadena no cumple el formato.

La coerción es una conversión implícita aplicada para hacer compatibles operandos. Facilita la escritura, pero puede ocultar errores: concatenar accidentalmente texto y números, comparar valores de dominios diferentes o aceptar valores nulos donde no proceden. Los lenguajes y herramientas de análisis estático intentan equilibrar comodidad y seguridad.

3.4. Nulidad, opcionalidad y valores centinela

La ausencia de valor debe modelarse deliberadamente. Usar 0, −1 o una cadena vacía como centinela mezcla el dominio real con un significado especial. Los tipos opcionales, anulables o algebraicos distinguen entre “hay valor” y “no hay valor”, obligando a tratar ambos casos. Esta distinción es especialmente relevante en información clínica y administrativa, donde “desconocido”, “no aplicable”, “no informado” y “valor cero” no son equivalentes.

3.5. Tipos por valor y por referencia

En una semántica por valor, la asignación copia el contenido lógico; en una semántica por referencia, varias variables pueden referirse al mismo objeto. El aliasing permite compartir estructuras, pero introduce efectos laterales: una modificación visible a través de una referencia puede aparecer por otra. La inmutabilidad reduce este riesgo y facilita razonamiento, concurrencia y pruebas.

Perla de examen: tipado estático no significa necesariamente tipado fuerte, ni tipado dinámico significa débil. Son ejes distintos: momento de comprobación y grado de restricciones o conversiones permitidas.

4. ESTRUCTURAS CONVENCIONALES: ARRAYS, MATRICES Y REGISTROS

4.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

4.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: El array nativo suele tener longitud fija; colecciones como Python list o Java ArrayList implementan arrays dinámicos redimensionables
  • Dimensiones: Unidimensionales (vectores), multidimensionales (matrices, tensores)
  • Memoria: Contigua, predecible, cache-friendly

4.2.1. 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

4.2.2. 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]

4.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

4.3.1. 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

4.4. Localidad de referencia y coste real del array

El acceso indexado de un array es O(1) porque la dirección del elemento se calcula a partir de la dirección base, el índice y el tamaño del elemento. Esta complejidad asintótica no describe todo el rendimiento: la contigüidad mejora la localidad espacial y permite aprovechar líneas de caché, prelectura y operaciones vectorizadas. Por ello, recorrer un array puede ser más rápido que recorrer una lista enlazada incluso cuando ambas operaciones son lineales.

La inserción en una posición intermedia exige desplazar elementos y suele ser O(n). Los arrays dinámicos reservan capacidad superior al tamaño lógico y crecen por bloques; así obtienen inserción al final de coste amortizado O(1), aunque una ampliación concreta requiere reservar otra zona y copiar elementos.

4.5. Alineación y relleno en registros

Los campos de un registro pueden incorporar bytes de relleno para satisfacer requisitos de alineación. Por eso, el tamaño de una estructura no siempre es la suma exacta de sus campos. Serializar directamente la imagen de memoria de un registro puede fallar entre compiladores, arquitecturas o versiones por diferencias de alineación, endianness y representación. Un formato persistente debe definir explícitamente tamaños, orden de bytes, codificación y versión.

Trampa: un array multidimensional puede representarse en orden por filas o por columnas. El orden lógico de índices no determina por sí solo la disposición física; esta depende del lenguaje, biblioteca o formato.
Perla de examen: array y registro resuelven problemas diferentes. El array agrupa elementos homogéneos accesibles por índice; el registro agrupa campos potencialmente heterogéneos accesibles por nombre.

5. CADENAS, ENUMERACIONES Y OTRAS COLECCIONES CONVENCIONALES

5.1. Cadenas de Caracteres (Strings)

Las cadenas son secuencias de caracteres. Su implementación varía según lenguaje:

  • C: Array de char terminado en ‘’ (null-terminated)
    char saludo[] = "Hola";  // Internamente: ['H','o','l','a','']
  • 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

5.1.1. 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»]

5.2. 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

5.3. Tuplas, conjuntos y mapas

Una tupla agrupa un número fijo de componentes, potencialmente de tipos diferentes, y suele utilizar acceso posicional. Puede verse como un registro sin nombres de campo o como un producto de tipos. Es útil para devolver varios resultados, formar claves compuestas o modelar coordenadas, aunque un registro nombrado mejora la legibilidad cuando cada componente tiene significado estable.

Un conjunto representa elementos sin duplicados y ofrece operaciones de pertenencia, unión, intersección y diferencia. Su implementación puede basarse en tabla hash, árbol equilibrado o vector de bits. La abstracción es la misma, pero cambian orden, complejidad y consumo. Un conjunto hash suele ofrecer pertenencia promedio O(1); un conjunto ordenado basado en árbol suele ofrecer O(log n) y recorrido ordenado.

Un mapa, diccionario o array asociativo relaciona claves con valores. No es un array porque la clave no tiene por qué ser un entero contiguo. Puede implementarse mediante hash o árbol. La elección determina si se conserva orden, si se admiten consultas por rango y cuál es el comportamiento en el peor caso.

5.4. Tipos algebraicos y variantes

Los tipos suma o variantes permiten que un valor pertenezca a una de varias alternativas etiquetadas. Modelan estados mutuamente excluyentes mejor que una estructura con muchos campos opcionales. Combinados con tipos producto —registros y tuplas— permiten representar dominios complejos de forma segura.

Perla de examen: la abstracción “mapa” no implica necesariamente hash. Un mapa ordenado puede implementarse con un árbol balanceado; una tabla hash no proporciona por sí misma recorrido por rango.

6. MEMORIA DINÁMICA Y LISTAS ENLAZADAS

6.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

6.2. Listas Enlazadas

6.2.1. 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

6.2.2. 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

6.2.3. 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

6.3. Asignación dinámica, ciclo de vida y fragmentación

La memoria dinámica se solicita durante la ejecución y se libera cuando deja de ser necesaria. En gestión manual, una fuga aparece cuando se pierde la última referencia a un bloque no liberado; un puntero colgante conserva una dirección cuyo objeto ya no existe; y una doble liberación intenta devolver dos veces el mismo bloque. En sistemas con recolector de basura desaparecen algunas clases de error, pero no el consumo excesivo producido por referencias que siguen vivas innecesariamente.

La fragmentación externa deja huecos libres dispersos que pueden impedir una reserva grande pese a existir memoria total suficiente. La fragmentación interna aparece cuando un bloque asignado contiene espacio no utilizado. Asignadores, pools y arenas intentan reducir estos costes. En estructuras con muchos nodos pequeños, el overhead del asignador y los punteros puede superar el tamaño del dato almacenado.

6.4. Listas y operaciones locales

Una lista enlazada permite inserción o eliminación O(1) cuando se dispone de la referencia al nodo o a su predecesor. Buscar la posición sigue siendo O(n). Decir simplemente que “insertar en una lista es O(1)” es incompleto: si antes hay que localizar el lugar por índice o clave, el coste total incluye esa búsqueda.

Las listas dobles facilitan eliminación y recorrido inverso, pero añaden memoria y más enlaces que mantener. Las listas circulares son útiles en planificación rotatoria y buffers, mientras que los nodos centinela reducen casos especiales en extremos.

Trampa: una lista enlazada no proporciona acceso directo por índice. El nodo i-ésimo se alcanza recorriendo enlaces desde un punto conocido, salvo que exista una estructura auxiliar.
Perla de examen: insertar al principio de una lista simplemente enlazada es O(1); acceder al elemento situado en una posición arbitraria es O(n).

7. PILAS, COLAS, DEQUES Y COLAS DE PRIORIDAD

7.1. 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

7.2. 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

7.2.1. 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

7.3. Implementaciones sobre array y lista

Una pila puede implementarse sobre un array dinámico usando el final como cima. Así, push y pop tienen coste amortizado O(1). Sobre lista enlazada se opera en la cabeza y el coste es O(1) estricto, a cambio de un nodo y una referencia por elemento. Una cola sobre array debe evitar desplazar todos los elementos al extraer; la solución habitual es un buffer circular con índices de frente y final.

Una deque permite insertar y eliminar por ambos extremos. No equivale a permitir operaciones eficientes en cualquier posición. Una cola de prioridad tampoco es una cola FIFO ordinaria: extrae según prioridad, con frecuencia mediante un heap, y conserva el orden de llegada solo si se añade un criterio secundario.

7.4. Aplicaciones en procesamiento y concurrencia

Las pilas aparecen en llamadas de funciones, evaluación de expresiones, retroceso y recorrido en profundidad. Las colas modelan productores y consumidores, trabajos pendientes, eventos y recorrido en anchura. En concurrencia, una cola debe especificar si es bloqueante, limitada, segura para múltiples hilos y qué política aplica cuando se llena.

Perla de examen: DFS se asocia a una pila —explícita o implícita por recursión— y BFS a una cola. La estructura determina el orden en que se explora la frontera.

8. ÁRBOLES Y ESTRUCTURAS JERÁRQUICAS

8.1. Árboles

Estructura jerárquica donde cada nodo tiene un padre (excepto raíz) y cero o más hijos.

8.1.1. 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

8.1.2. Á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

8.1.3. Á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)

8.1.4. 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

8.2. Árboles equilibrados, heaps y tries

Un árbol binario de búsqueda solo garantiza operaciones logarítmicas cuando su altura se mantiene proporcional al logaritmo del número de nodos. Si se insertan claves ordenadas sin balanceo puede degenerar en una cadena y alcanzar O(n). Árboles AVL y rojo-negro aplican rotaciones para limitar la altura; difieren en el equilibrio exigido y en el coste relativo de búsquedas y actualizaciones.

Un heap binario mantiene una propiedad de prioridad entre padre e hijos, pero no el orden total de un BST. Permite consultar el mínimo o máximo en O(1) e insertar o extraer la raíz en O(log n). Es apropiado para colas de prioridad y algoritmos como heapsort, pero no para buscar una clave arbitraria en tiempo logarítmico.

Un trie organiza cadenas por prefijos. El coste depende de la longitud de la clave y permite autocompletado o búsqueda de prefijos, a cambio de consumo de memoria. Los árboles B y B+ aumentan el factor de ramificación para reducir accesos a bloque; son fundamentales en índices de ficheros y bases de datos. En un B+, los datos o referencias se concentran en hojas enlazadas, lo que facilita recorridos por rango.

Trampa: heap, árbol binario de búsqueda y árbol B no son sinónimos. El heap prioriza el extremo; el BST ordena por comparación; el árbol B está diseñado para minimizar operaciones de entrada/salida mediante nodos con muchas claves.
Perla de examen: el recorrido inorden de un BST produce claves en orden no decreciente si se respeta la propiedad de búsqueda.

9. GRAFOS Y TABLAS HASH

9.1. Grafos

Conjunto de vértices (nodos) conectados por aristas (edges). Generalizan árboles permitiendo ciclos y múltiples padres.

9.1.1. 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

9.1.2. 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

9.1.3. 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

9.2. 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

9.2.1. 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

9.3. Coste de las representaciones

Para un grafo con V vértices y E aristas, una matriz de adyacencia consume O(V²) y permite comprobar una arista en O(1). Una lista de adyacencia consume O(V+E) y resulta preferible en grafos dispersos. La respuesta a “qué representación es mejor” depende de densidad, operaciones y necesidad de modificar el grafo.

BFS encuentra caminos mínimos en número de aristas en grafos no ponderados. Dijkstra requiere pesos no negativos; Bellman-Ford admite pesos negativos y detecta ciclos negativos alcanzables; Floyd-Warshall calcula distancias entre todos los pares con coste cúbico. En un DAG, un orden topológico solo existe si no hay ciclos dirigidos.

9.4. Funciones hash, carga y peor caso

Una buena función hash distribuye claves de forma uniforme y es determinista. El factor de carga relaciona elementos y cubetas; al crecer aumenta la probabilidad de colisiones. Muchas implementaciones redimensionan y reinsertan cuando se supera un umbral. El acceso O(1) es promedio o amortizado, no una garantía universal: colisiones adversas pueden degradar a O(n).

El encadenamiento almacena varios elementos por cubeta. El direccionamiento abierto mantiene todos los elementos en el array y busca posiciones alternativas mediante sondeo lineal, cuadrático o doble hash. El borrado en direccionamiento abierto requiere marcas especiales para no romper cadenas de sondeo.

Perla de examen: una matriz de adyacencia favorece la prueba inmediata de existencia de arista; una lista de adyacencia ahorra espacio en grafos dispersos y recorre eficientemente los vecinos existentes.

10. CONCEPTO, ATRIBUTOS Y CLASIFICACIÓN DE FICHEROS

10.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

10.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 Sugiere el formato por convención; debe validarse .txt, .pdf, .exe, .csv
Tamaño Número de bytes 1.024 bytes (1 KiB)
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

10.3. Tipos de Ficheros según Contenido

10.3.1. Ficheros de Texto

  • Contenido: Secuencia de caracteres legibles (ASCII, UTF-8)
  • Estructura: Líneas separadas por caracteres newline (n, rn)
  • 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

10.3.2. Ficheros Binarios

  • Contenido: Secuencia de bytes interpretada según una especificación de formato.
  • Estructura: Puede incluir cabeceras, campos de longitud, offsets, compresión y checksums.
  • Legibilidad: Requiere herramientas que conozcan el formato; no es texto directamente editable.
  • Portabilidad: Puede ser alta si el formato define orden de bytes, tamaños y versión; serializar memoria nativa sin contrato sí es poco portable.
  • Ejemplos: JPEG, PNG, PDF, ejecutables, archivos ZIP y formatos de bases de datos.
  • Ventajas: Puede ser compacto y eficiente, y permitir localización estructurada.
  • Desventajas: Exige parsing conforme al formato, validación y software compatible.

10.4. Fichero lógico, sistema de ficheros y formato

Conviene distinguir el fichero lógico que maneja una aplicación, el formato que interpreta sus bytes y el sistema de ficheros que administra nombres, directorios, permisos y bloques. Un CSV y un JSON son formatos de datos; NTFS, ext4 o XFS son sistemas de ficheros; y la llamada de lectura o escritura opera sobre un descriptor o manejador proporcionado por el sistema operativo.

La extensión es una convención y no garantiza el contenido. La identificación fiable puede requerir cabeceras, números mágicos, metadatos o validación contra un esquema. Un formato binario no es “cifrado” ni “seguro” por ser ilegible a simple vista; confidencialidad e integridad requieren controles criptográficos y de acceso.

10.5. Registros, campos y longitud

Los ficheros orientados a registros pueden usar longitud fija, variable o delimitada. La longitud fija permite calcular posiciones, pero desperdicia espacio y dificulta campos grandes. La variable aprovecha mejor el almacenamiento, aunque necesita delimitadores, prefijos de longitud, tablas de offsets o estructuras equivalentes. En texto, el delimitador debe escaparse o citarse cuando puede aparecer dentro del campo.

Un registro lógico puede ocupar uno o varios bloques físicos. El factor de bloqueo expresa cuántos registros caben en un bloque cuando son de tamaño fijo. Leer por bloques reduce operaciones físicas y aprovecha que la entrada/salida suele ser mucho más costosa que procesar bytes ya presentes en memoria.

Trampa: “fichero binario” describe la interpretación de los bytes, no una propiedad de seguridad. Un fichero de texto también es una secuencia de bytes codificados.
Perla de examen: formato y organización son dimensiones diferentes. Un fichero indexado puede almacenar registros binarios o textuales; el índice es una estructura de localización, no un formato como JSON o XML.

11. ORGANIZACIÓN DE FICHEROS

11.1. Organización de Ficheros

11.1.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)

11.1.2. 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 La inserción intermedia suele requerir reorganización

11.1.3. 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'  # orden little-endian definido: uint32, 50 bytes, float32
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)
Advertencia: para persistencia interoperable debe fijarse explícitamente el orden de bytes, el tamaño de cada campo y la codificación. Usar el formato nativo de una estructura puede introducir padding y dependencias de plataforma.

11.1.4. 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)

11.1.5. 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:
    1. Buscar clave en índice (búsqueda binaria O(log n) si ordenado)
    2. Obtener dirección/puntero del registro
    3. 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

11.1.6. 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+

11.1.7. 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

11.2. Organización secuencial ordenada y no ordenada

En una organización secuencial no ordenada los registros se añaden normalmente al final y las búsquedas por clave requieren exploración. Si el fichero se mantiene ordenado, el recorrido y las operaciones por rango mejoran, pero insertar puede exigir reescritura, uso de áreas de desbordamiento o procesos periódicos de reorganización. El procesamiento por lotes explota bien la secuencia ordenada: dos ficheros ordenados pueden combinarse mediante un recorrido de mezcla lineal.

11.3. Organización directa o relativa

En un fichero relativo, los registros de longitud fija se identifican mediante un número relativo de registro. La posición puede calcularse como cabecera más número de registro multiplicado por longitud. Es eficiente cuando la clave puede transformarse de manera directa y el espacio de direcciones no es excesivamente disperso. Deben gestionarse posiciones vacías, borrados y crecimiento.

11.4. Organización indexada e indexada-secuencial

El índice contiene pares de clave y dirección o punteros a bloques. Puede ser denso —una entrada por registro— o disperso —una entrada por bloque o grupo—; primario o secundario; único o no único; de uno o varios niveles. Los índices multinivel reducen búsquedas a pocos accesos de bloque. Los árboles B/B+ mantienen orden y equilibrio frente a inserciones y eliminaciones.

La organización indexada-secuencial combina un área principal ordenada, índices y, en modelos clásicos, áreas de desbordamiento. Facilita tanto recorrido secuencial como acceso por clave. Requiere mantenimiento: dividir páginas, actualizar entradas, fusionar nodos y reorganizar cuando se acumulan desbordamientos.

11.5. Organización hash

La dirección se deriva de una función sobre la clave. Es adecuada para igualdad exacta y puede ser muy rápida en promedio. No preserva el orden y es poco apropiada para rangos. Las colisiones se resuelven mediante cubetas, encadenamiento, áreas de desbordamiento o direccionamiento abierto. Una mala distribución o un factor de carga alto degrada el rendimiento.

Pregunta oficial relacionada: en la OPE TFA STI SAS 2025, turno libre, pregunta 59, se identificó como característica del fichero indexado que los datos se acompañan de un índice auxiliar que permite localizar registros sin recorrer todo el archivo. La pregunta no está anulada y la respuesta correcta fue la opción B.

12. MÉTODOS DE ACCESO EN EL TRATAMIENTO DE FICHEROS

12.1. Métodos de Acceso a Ficheros

12.1.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

12.1.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

12.1.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

12.1.4. Acceso Hash

Uso de función hash para calcular posición directa del registro.

# Pseudocódigo de acceso hash persistente
def buscar_por_hash(fichero, clave):
    cubeta = hash_estable(clave) % numero_cubetas
    offset = cabecera + cubeta * tamano_cubeta
    fichero.seek(offset)
    registros = leer_cubeta(fichero, tamano_cubeta)
    return buscar_clave_o_desbordamiento(registros, clave)

# La función debe estar especificada y ser estable entre ejecuciones.
# No debe usarse sin más un hash aleatorizado del runtime.

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

12.2. El método de acceso como interfaz de tratamiento

La organización describe cómo están dispuestos los registros; el método de acceso es el conjunto de operaciones mediante el que el programa los trata. Una misma organización puede admitir más de un método. Un fichero indexado puede recorrerse secuencialmente por clave y también consultarse directamente a través del índice.

El acceso secuencial mantiene una posición corriente y procesa registros en orden. Es natural para lecturas completas, copias, clasificación externa y procesos batch. El acceso directo posiciona mediante un desplazamiento o número relativo; requiere que la posición pueda calcularse o conocerse. El acceso indexado traduce una clave a una ubicación por medio de una estructura auxiliar. El acceso hash calcula la cubeta a partir de la clave.

12.3. Acceso por rango, clave primaria y claves secundarias

Las consultas por rango necesitan orden. Un índice B+ permite localizar el primer valor y continuar por hojas enlazadas. Un índice hash resuelve bien igualdad, pero no “entre fecha A y fecha B”. Las claves secundarias pueden no ser únicas; su índice apunta a listas de referencias o a múltiples entradas. El coste de actualización aumenta con cada índice adicional.

12.4. Posicionamiento y desplazamientos

Operaciones como seek cambian el desplazamiento asociado a un manejador. En un fichero de bytes permiten situarse respecto al inicio, posición actual o final, pero no convierten automáticamente un formato variable en acceso semántico por registro. En texto con codificación variable, un desplazamiento arbitrario puede caer dentro de una secuencia multibyte.

Trampa: acceso aleatorio a bytes no equivale a acceso directo a registros lógicos. Para localizar el registro se necesita conocer su offset o disponer de metadatos que lo calculen.
Perla de examen: el método secuencial es óptimo cuando se procesa la mayor parte del fichero en orden; el indexado compensa su espacio y mantenimiento cuando predominan búsquedas selectivas.

13. OPERACIONES, BUFFERING, CONCURRENCIA E INTEGRIDAD

13.1. Operaciones sobre Ficheros

13.1.1. 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’)

13.1.2. 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

13.1.3. 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

13.2. Buffering, caché y entrada/salida

Las bibliotecas interponen buffers para agrupar lecturas y escrituras. La aplicación puede creer que ha escrito cuando los datos solo están en memoria de usuario o del sistema operativo. flush fuerza el vaciado hacia capas inferiores, pero la durabilidad frente a caída puede requerir primitivas adicionales y depende del sistema de ficheros y dispositivo. Cerrar correctamente el manejador libera recursos y completa operaciones pendientes.

13.3. Actualización, borrado y compactación

Actualizar un registro de longitud fija puede sobrescribirlo in situ. Si la longitud variable crece, puede ser necesario moverlo y dejar un puntero, reservar espacio libre o reescribir el fichero. El borrado lógico marca registros y permite reutilizar huecos; la compactación física recupera espacio y mejora localidad, a costa de una operación más costosa.

13.4. Concurrencia y bloqueo

Cuando varios procesos acceden al mismo fichero deben coordinarse. Los bloqueos pueden aplicarse al fichero completo, a regiones o a registros y ser compartidos o exclusivos. Sin coordinación aparecen actualizaciones perdidas, lecturas parciales y condiciones de carrera. Los bloqueos asesores solo funcionan si todos los participantes respetan el protocolo; los obligatorios son menos habituales y dependen de plataforma.

13.5. Integridad, atomicidad y recuperación

Una actualización compuesta debe evitar estados intermedios visibles. Patrones como escribir en un fichero temporal, sincronizar y renombrar atómicamente reducen el riesgo para reemplazos completos. Los diarios o logs de escritura anticipada permiten rehacer o deshacer cambios. Las sumas de verificación detectan corrupción accidental, pero no sustituyen autenticación criptográfica frente a alteración intencionada.

13.6. Clasificación externa y fusión

Cuando los datos no caben en memoria se emplea clasificación externa: se generan tramos ordenados que sí caben en RAM y después se fusionan. La mezcla de k vías usa buffers y, con frecuencia, una cola de prioridad para seleccionar el menor elemento de los tramos. Es un ejemplo de cómo estructuras en memoria y organización de ficheros se combinan.

Perla de examen: abrir un fichero en modo append sitúa las escrituras al final; no equivale a lectura/escritura aleatoria. El modo concreto y sus garantías dependen de la API y del sistema operativo.

14. COMPLEJIDAD Y CRITERIOS DE SELECCIÓN

14.1. Notaciones O, Ω y Θ

La notación O expresa una cota superior asintótica; Ω una cota inferior y Θ una cota ajustada. En preguntas de estructuras suele usarse O de forma informal para describir el crecimiento dominante. Debe especificarse si se habla de caso peor, promedio, esperado o amortizado. Una tabla hash tiene acceso esperado O(1), pero peor caso O(n); un array dinámico tiene inserción al final amortizada O(1), aunque una ampliación individual es O(n).

14.2. Tiempo frente a espacio

Los índices, tablas auxiliares y caches consumen espacio para reducir tiempo. Una matriz de adyacencia sacrifica O(V²) para consultar aristas en O(1); una lista enlazada ahorra desplazamientos, pero añade punteros y pierde localidad; un fichero indexado añade almacenamiento y coste de mantenimiento para evitar exploraciones completas. No existe una estructura óptima para todas las operaciones.

14.3. Criterios de selección

Necesidad predominante Estructura u organización habitual Advertencia
Acceso por posición Array Insertar en medio desplaza elementos.
Inserciones locales con referencia Lista enlazada Buscar por índice sigue siendo lineal.
LIFO Pila La implementación puede ser array o lista.
FIFO Cola o buffer circular No desplazar el array en cada extracción.
Prioridad Heap No ofrece búsqueda arbitraria logarítmica.
Clave exacta Tabla hash No resuelve bien rangos y tiene colisiones.
Orden y rangos Árbol balanceado o B+ Actualización más compleja que hash.
Proceso completo en orden Fichero secuencial Búsqueda puntual puede ser lineal.
Consulta selectiva persistente Fichero indexado Índice ocupa espacio y debe mantenerse.

14.4. Medición y perfilado

La complejidad guía el diseño, pero debe complementarse con medición. Distribución de claves, tamaño de registros, caché, almacenamiento, concurrencia y lenguaje pueden cambiar el resultado. El perfilado identifica cuellos de botella reales y evita optimizar partes irrelevantes.

Trampa: O(1) no significa tiempo idéntico ni necesariamente más rápido para tamaños pequeños; significa que el crecimiento no depende de n bajo el modelo analizado.

15. APLICACIÓN EN SISTEMAS DE INFORMACIÓN DEL SAS

15.1. Uso en aplicaciones sanitarias y administrativas

Los sistemas del Servicio Andaluz de Salud procesan grandes volúmenes de información clínica, administrativa y técnica. Sin atribuir una implementación concreta a cada producto, los principios del tema aparecen en cualquier arquitectura: colecciones de profesionales o citas, colas de integración, índices por identificadores, árboles para clasificaciones jerárquicas, grafos para dependencias y ficheros para cargas, exportaciones, logs y documentos.

En un proceso de carga masiva, por ejemplo, el fichero de entrada debe definir codificación, delimitadores, esquema, obligatoriedad, claves y tratamiento de errores. Leerlo secuencialmente resulta natural; una tabla hash en memoria puede detectar duplicados; una cola puede desacoplar validación y persistencia; y un fichero de rechazos conserva las líneas no procesadas con su causa. La estructura elegida afecta a trazabilidad y capacidad de reanudación.

15.2. Identificadores y claves

Los identificadores sanitarios o administrativos no deben confundirse con posiciones físicas. Una clave estable identifica una entidad; el índice traduce esa clave a una ubicación cambiante. Guardar directamente offsets externos crea acoplamiento con la organización física y dificulta reorganización. La aplicación debe validar formato y dominio, pero no inferir significados no garantizados.

15.3. Intercambio y persistencia

Los formatos de intercambio textuales facilitan interoperabilidad y diagnóstico, pero requieren validación y pueden aumentar tamaño. Los binarios pueden ser compactos y rápidos, aunque exigen especificación y herramientas. En ambos casos deben establecerse versión, codificación, esquema y compatibilidad. Un fichero recibido no es confiable por provenir de un sistema interno: debe limitarse tamaño, validar estructura y evitar rutas, nombres o contenidos manipulados.

15.4. Seguridad y protección de datos

Los ficheros con datos personales requieren controles de acceso, cifrado cuando proceda, registro de operaciones, retención y borrado conforme a la política aplicable. Las copias temporales y exportaciones son especialmente sensibles porque pueden quedar fuera de los controles del sistema principal. El diseño debe minimizar datos, separar entornos y evitar incluir información clínica en nombres de fichero o logs no protegidos.

Aplicación práctica: en una importación de profesionales, un fichero secuencial puede alimentar un pipeline que normaliza campos, valida cada registro, consulta una tabla hash de claves ya vistas, agrupa errores y genera un resumen. La persistencia definitiva puede usar una base de datos, pero el razonamiento sobre registros, buffers y acceso sigue siendo el mismo.
Perla de examen: una clave lógica como un identificador de usuario no es el número relativo de registro. La clave permanece estable aunque el fichero se reorganice; la dirección física puede cambiar.

16. IDEAS CLAVE, TRAMPAS Y REPASO FINAL

16.1. Distinciones que deben memorizarse

  • Tipo frente a variable: el tipo define dominio y operaciones; la variable es una instancia o referencia.
  • Estructura abstracta frente a implementación: pila, cola o mapa describen comportamiento; array, lista, heap o árbol materializan ese comportamiento.
  • Tamaño lógico frente a capacidad: un array dinámico puede reservar más espacio del que contiene.
  • Acceso directo frente a indexado: el directo calcula o conoce la posición; el indexado consulta una estructura auxiliar.
  • Organización frente a formato: secuencial, indexada o hash describen disposición; CSV, JSON o binario describen representación.
  • Hash promedio frente a peor caso: O(1) esperado no elimina colisiones ni degradación.
  • Texto frente a Unicode: un fichero de texto necesita una codificación; los caracteres no son bytes sin contexto.

16.2. Errores frecuentes de diseño

Entre los errores más habituales están elegir una lista por su inserción O(1) ignorando la búsqueda previa, serializar estructuras de memoria sin especificar formato, comparar flotantes por igualdad exacta, usar un índice hash para rangos, recorrer repetidamente un fichero secuencial para consultas puntuales, o mantener demasiados índices sin considerar el coste de actualización.

También es frecuente confundir un sistema de ficheros con una organización de registros, asumir que el cierre de una API implica persistencia física inmediata, o tratar el valor nulo como equivalente a cero. En un examen, estas confusiones se explotan mediante opciones técnicamente próximas pero de distinto nivel de abstracción.

Regla de repaso: para cada estructura debes conocer su invariante, operaciones, complejidad, coste espacial, implementación típica y caso de uso. Para cada organización de fichero debes conocer disposición, método de acceso, ventajas, limitaciones y mantenimiento.

17. MAPA CONCEPTUAL

TIPOS, ESTRUCTURAS Y FICHEROS

├── TIPOS DE DATOS
│ ├── elementales: entero · real · carácter · booleano
│ ├── representación: complemento a dos · IEEE 754 · Unicode/UTF-8
│ ├── sistema de tipos: estático/dinámico · fuerte/débil
│ └── conversión: promoción · estrechamiento · nulidad

├── ESTRUCTURAS CONVENCIONALES
│ ├── array/matriz → homogénea · contigua · índice O(1)
│ ├── registro → campos heterogéneos por nombre
│ ├── cadena → secuencia de caracteres
│ └── enum · tupla · conjunto · mapa

├── ESTRUCTURAS DINÁMICAS
│ ├── listas → nodos y enlaces
│ ├── pila LIFO · cola FIFO · deque · prioridad
│ ├── árboles → BST · AVL/RB · heap · B/B+ · trie
│ ├── grafos → matriz/lista de adyacencia · BFS/DFS
│ └── hash → función · cubetas · colisiones · carga

├── FICHEROS
│ ├── contenido: texto / binario
│ ├── registros: fijo / variable / delimitado
│ ├── organización
│ │ ├── secuencial
│ │ ├── directa o relativa
│ │ ├── indexada / indexada-secuencial
│ │ └── hash
│ ├── acceso: secuencial · directo · indexado · hash
│ └── operación: abrir · leer · escribir · posicionar · cerrar

└── CRITERIO DE ELECCIÓN
├── patrón de operaciones
├── tiempo y espacio
├── localidad, concurrencia y persistencia
└── complejidad: peor · promedio · amortizada

18. REFERENCIAS NORMATIVAS Y BIBLIOGRÁFICAS

  • IEEE 754 — estándar de aritmética de coma flotante.
  • The Unicode Standard — repertorio universal de caracteres y modelo de codificación.
  • RFC 3629 — codificación UTF-8 de Unicode.
  • ISO/IEC 2382 — vocabulario de tecnologías de la información.
  • ISO/IEC 9899 — lenguaje de programación C y su modelo de tipos, arrays, estructuras y entrada/salida.
  • IEEE Std 1003.1, POSIX — interfaces de sistema, ficheros y entrada/salida.
  • RFC 8259 — formato de intercambio JSON.
  • RFC 4180 — formato común y tipo MIME de CSV.
  • Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest y Clifford Stein, Introduction to Algorithms — análisis de algoritmos y estructuras de datos.
  • Donald E. Knuth, The Art of Computer Programming — algoritmos, representación y organización de datos.
  • Robert Sedgewick y Kevin Wayne, Algorithms — estructuras, ordenación, búsqueda y grafos.
  • Alfred V. Aho, John E. Hopcroft y Jeffrey D. Ullman, Data Structures and Algorithms — fundamentos clásicos de estructuras de datos.
  • Documentación oficial de Python — tipos integrados, colecciones y operaciones con ficheros.
  • Examen TFA STI SAS 2025, turno libre, pregunta 59 — característica de los ficheros indexados; pregunta no anulada.
tipos de datos
arrays
listas enlazadas
pilas y colas
árboles
grafos
tablas hash
ficheros secuenciales
ficheros indexados
acceso directo
complejidad algorítmica
TFA STI SAS

Pon a prueba lo aprendido

Banco con 20 preguntas sobre este tema. Genera un quiz aleatorio cuando quieras.

Test completo →

Elaborado por Esteban Castro Palomo. Actualizado el agosto 5, 2026.