Apuntes DAM
Volver al inicio

Ordenación por inserción

AlgoritmosOrdenaciónNivel básicoTambién: insertion sort, inserción directa

Toma cada elemento y lo inserta en su sitio dentro de la parte ya ordenada de su izquierda, desplazando los mayores. O(n²) en general, pero casi O(n) si los datos ya vienen casi ordenados.

Visualízalo paso a paso

Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.

Ordenación por inserción

Escribe los números y mira cómo cada uno se inserta en la parte ya ordenada de su izquierda.

De 2 a 16 enteros separados por espacios
  • en la zona de trabajo

Paso 1

Array inicial: [12, 11, 13, 5, 6, 2].

1static void insercion(int[] a) {
2    for (int i = 1; i < a.length; i++) {  // comparaciones = 0, escrituras = 0
3        int x = a[i];
4        int j = i - 1;
5        while (j >= 0 && a[j] > x) {
6            a[j + 1] = a[j];
7            j--;
8        }
9        a[j + 1] = x;
10    }
11}

Variables

comparaciones
0
escrituras
0

Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.

La idea

La ordenación por inserción es como ordenar las cartas de una mano según te las dan: cada carta nueva la colocas en su sitio entre las que ya tienes ordenadas, corriendo un poco las que son mayores.

El array se divide en una parte izquierda ya ordenada (al principio, solo el primer elemento) y el resto. En cada paso se coge el primer elemento del resto, x = a[i], y se compara con los de la parte ordenada empezando por el final: cada uno que sea mayor que x se desplaza una posición a la derecha. Cuando aparece uno menor o igual (o se llega al principio), el hueco que ha quedado es el sitio de x.

Su gran virtud es que se adapta a los datos: si el array ya está casi ordenado, cada elemento se desplaza muy poco y el algoritmo es prácticamente lineal. En el peor caso (al revés) cada elemento recorre toda la parte ordenada y es O(n²).

Es estable (se para en el primer elemento menor o igual, sin saltarlo) y funciona en línea: puede ordenar los datos a medida que llegan, sin conocerlos todos antes.

Cuándo usarlo

  • Con pocos datos: con menos de unos 20 elementos es más rápida que mergesort o quicksort, por eso las bibliotecas la usan para los trozos pequeños.
  • Cuando los datos están casi ordenados (por ejemplo, una lista ordenada a la que se han añadido unos pocos al final): es casi O(n).
  • Cuando los datos llegan de uno en uno y hay que mantenerlos ordenados en todo momento.
  • Cuando hace falta estabilidad con memoria O(1).

Cuándo no

  • Con muchos datos en orden aleatorio o al revés: O(n²) desplazamientos.
  • Si los datos están en una estructura donde desplazar es caro y no hay acceso directo (aunque en una lista enlazada se puede insertar sin desplazar, buscar el sitio sigue siendo O(n)).

Paso a paso

  1. Tomar el siguiente. Para cada i de 1 a n − 1, se guarda x = a[i]: la parte a[0..i-1] ya está ordenada.
  2. Desplazar los mayores. Con j = i − 1, mientras j >= 0 y a[j] > x, se copia a[j] en a[j + 1] y se retrocede j.
  3. Colocar. Cuando el bucle para, a[j + 1] es el hueco: ahí va x. La parte ordenada ya llega hasta i.
  4. Siguiente. Se repite con el siguiente elemento hasta el final del array.

El código

Inserción contando desplazamientos

El mismo algoritmo sobre un array casi ordenado (2 desplazamientos) y sobre uno al revés (28): se adapta a los datos.

Java
1import java.util.Arrays;
2
3public class Main {
4    /** Ordena a por inserción y devuelve cuántos elementos ha tenido que desplazar. */
5    static int insercion(int[] a) {
6        int desplazamientos = 0;
7        for (int i = 1; i < a.length; i++) {
8            int x = a[i];                         // el que se va a insertar
9            int j = i - 1;
10            while (j >= 0 && a[j] > x) {          // los mayores que x se corren una posición
11                a[j + 1] = a[j];
12                j--;
13                desplazamientos++;
14            }
15            a[j + 1] = x;                         // el hueco que queda es su sitio
16        }
17        return desplazamientos;
18    }
19
20    public static void main(String[] args) {
21        int[] casi = {1, 2, 4, 3, 5, 6, 8, 7};
22        int[] reves = {8, 7, 6, 5, 4, 3, 2, 1};
23        System.out.println("Casi ordenado: " + insercion(casi) + " desplazamientos → " + Arrays.toString(casi));
24        System.out.println("Al revés: " + insercion(reves) + " desplazamientos → " + Arrays.toString(reves));
25    }
26}

Salida al ejecutarlo (la misma en los 5 lenguajes)

Casi ordenado: 2 desplazamientos → [1, 2, 3, 4, 5, 6, 7, 8]
Al revés: 28 desplazamientos → [1, 2, 3, 4, 5, 6, 7, 8]

Insertar en una lista ya ordenada

Un solo paso de la inserción: buscar dónde va y hacer sitio. La búsqueda del sitio puede ser binaria, pero desplazar sigue costando O(n).

Java
1/** Inserta x en la lista ordenada l sin desordenarla: un solo paso de la inserción. */
2static void insertarOrdenado(List<Integer> l, int x) {
3    int pos = Collections.binarySearch(l, x);
4    if (pos < 0) pos = -pos - 1;          // no estaba: binarySearch dice dónde iría
5    l.add(pos, x);                        // add desplaza los de la derecha, como la inserción
6}

Traza: inserción de {12, 11, 13, 5, 6}

ixDesplazadosVa en la posiciónOrdenado | resto
1111011 12 | 13 5 6
2130211 12 13 | 5 6
35305 11 12 13 | 6
46315 6 11 12 13 |

El 13 no desplaza a nadie (ya estaba en su sitio); el 5 tiene que pasar por delante de todos.

Complejidad

CasoComparacionesDesplazamientosCoste
Mejor (ya ordenado)n − 10O(n)
Medio≈ n²/4≈ n²/4O(n²)
Peor (al revés)n(n − 1)/2n(n − 1)/2O(n²)

Los desplazamientos son exactamente el número de parejas desordenadas (inversiones) del array: pocas inversiones, poco trabajo. Memoria extra: O(1).

0102030405015101520tamaño de la entrada (n)operacionesO(n!)O(2ⁿ)O(n²)O(n log n)O(1)O(log n)O(n)
  • Mejor caso: O(n)
  • Caso medio: O(n²)
  • Peor caso: O(n²)

Casi ordenado es casi n: por eso los algoritmos de las bibliotecas la usan para los trozos pequeños. Las curvas grises son las demás clases, para comparar.

En la práctica

  • TimSort (el Arrays.sort de objetos en Java, sorted en Python) usa inserción binaria para los tramos de menos de 32-64 elementos.
  • El Arrays.sort de tipos primitivos en Java también pasa a inserción cuando el trozo a ordenar es pequeño.
  • Mantener ordenado un ranking que recibe puntuaciones una a una, o una lista de tareas por prioridad que se va llenando.
  • Ordenar datos casi ordenados: registros que llegan casi en orden de fecha, una lista ordenada con unos pocos cambios.

Errores típicos

  • No guardar a[i] en una variable antes de desplazar: el primer desplazamiento lo sobrescribe.
  • Escribir la condición al revés (a[j] > x && j >= 0): cuando j llega a −1 se evalúa a[-1] antes de comprobar j y salta la excepción.
  • Colocar x en a[j] en vez de en a[j + 1]: al salir del bucle, j apunta al primer elemento que NO es mayor.
  • Usar >= en la condición: los iguales se desplazan, deja de ser estable y hace trabajo de más.
  • Empezar el bucle exterior en 0: no está mal, pero el primer paso no hace nada.

Ejercicios

Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.

1. Números ordenados según llegan

Los números llegan de uno en uno, uno por línea. Mantén una lista siempre ordenada: inserta cada número en su sitio como en la ordenación por inserción (desde el final, corriendo los mayores) y muestra la lista y cuántos números has tenido que desplazar. Al final, muestra la mediana. Completa insertar y mediana.

  • Entrada: un número entero por línea.
  • Por cada número: 5 → [2, 5, 8] (desplazados: 1); al final, Mediana: 5 o, si hay un número par, Mediana: entre 5 y 8.
  • Errores: Número no válido: «texto» (se salta) y No hay números si no llega ninguno.
☕JavaNúmeros ordenados según lleganMedio

Ejemplo

Entrada (lo que se escribe por teclado)
5
2
8
1
9
Salida esperada
5 → [5] (desplazados: 0)
2 → [2, 5] (desplazados: 1)
8 → [2, 5, 8] (desplazados: 0)
1 → [1, 2, 5, 8] (desplazados: 3)
9 → [1, 2, 5, 8, 9] (desplazados: 0)
Mediana: 5
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    /** Inserta x en la lista l, que ya está ordenada, como en la ordenación por inserción: se compara
5        desde el final y cada mayor que x se corre un sitio. Devuelve cuántos se han desplazado. */
6    static int insertar(List<Integer> l, int x) {
7        l.add(x);                               // se hace sitio al final
8        int j = l.size() - 2;
9        int desplazados = 0;
10        while (j >= 0 && l.get(j) > x) {
11            l.set(j + 1, l.get(j));
12            j--;
13            desplazados++;
14        }
15        l.set(j + 1, x);
16        return desplazados;
17    }
18
19    /** La mediana de una lista ordenada: el del medio, o los dos del medio si hay un número par. */
20    static String mediana(List<Integer> l) {
21        int n = l.size();
22        if (n % 2 == 1) return String.valueOf(l.get(n / 2));
23        return "entre " + l.get(n / 2 - 1) + " y " + l.get(n / 2);
24    }
25
26    public static void main(String[] args) {
27        Scanner sc = new Scanner(System.in);
28        List<Integer> l = new ArrayList<>();
29        while (sc.hasNextLine()) {
30            String linea = sc.nextLine().trim();
31            if (linea.isEmpty()) continue;
32            int x;
33            try {
34                x = Integer.parseInt(linea);
35            } catch (NumberFormatException e) {
36                System.out.println("Número no válido: «" + linea + "»");
37                continue;
38            }
39            int d = insertar(l, x);
40            System.out.println(x + " → " + l + " (desplazados: " + d + ")");
41        }
42        if (l.isEmpty()) {
43            System.out.println("No hay números");
44            return;
45        }
46        System.out.println("Mediana: " + mediana(l));
47    }
48}

Es la ordenación por inserción hecha «en línea»: cada número nuevo es el a[i] de la inserción y la lista que ya hay es la parte ordenada.

Los desplazamientos de cada número son cuántos de los anteriores eran mayores que él: si llegan casi en orden, casi no hay trabajo.

2. Alumnos por nota y apellido

Cada línea es un alumno: apellido, nombre y nota (entero de 0 a 10). Ordénalos con el método de inserción de mayor a menor nota; con la misma nota, por apellido de la A a la Z; y con el mismo apellido, por nombre. Completa antes, que dice si un alumno va antes que otro, y ordenar, que hace la inserción usando antes.

  • Entrada: líneas como García Ana 8.
  • Salida: 8 García Ana, una línea por alumno, ya ordenados.
  • Errores: Línea no válida: «texto» (se salta) y No hay alumnos.
☕JavaAlumnos por nota y apellidoMedio

Ejemplo

Entrada (lo que se escribe por teclado)
García Ana 8
López Luis 9
Alonso Eva 8
García Pablo 8
Ruiz Marta 5
Salida esperada
9 López Luis
8 Alonso Eva
8 García Ana
8 García Pablo
5 Ruiz Marta
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
0/5 tests pasados · pulsa un test para ver su entrada y su salida esperada
Ver la solución explicada
java
1import java.util.*;
2
3public class Main {
4    record Alumno(String apellido, String nombre, int nota) { }
5
6    /** ¿Va a antes que b? Primero la nota más alta; con la misma nota, por apellido (A-Z)
7        y, con el mismo apellido, por nombre. */
8    static boolean antes(Alumno a, Alumno b) {
9        if (a.nota() != b.nota()) return a.nota() > b.nota();
10        int c = a.apellido().compareTo(b.apellido());
11        if (c != 0) return c < 0;
12        return a.nombre().compareTo(b.nombre()) < 0;
13    }
14
15    /** Ordenación por inserción usando antes() para comparar. */
16    static void ordenar(List<Alumno> l) {
17        for (int i = 1; i < l.size(); i++) {
18            Alumno x = l.get(i);
19            int j = i - 1;
20            while (j >= 0 && antes(x, l.get(j))) {
21                l.set(j + 1, l.get(j));
22                j--;
23            }
24            l.set(j + 1, x);
25        }
26    }
27
28    public static void main(String[] args) {
29        Scanner sc = new Scanner(System.in);
30        List<Alumno> l = new ArrayList<>();
31        while (sc.hasNextLine()) {
32            String linea = sc.nextLine().trim();
33            if (linea.isEmpty()) continue;
34            String[] p = linea.split("\\s+");
35            if (p.length != 3 || !p[2].matches("10|\\d")) {
36                System.out.println("Línea no válida: «" + linea + "»");
37                continue;
38            }
39            l.add(new Alumno(p[0], p[1], Integer.parseInt(p[2])));
40        }
41        if (l.isEmpty()) {
42            System.out.println("No hay alumnos");
43            return;
44        }
45        ordenar(l);
46        for (Alumno a : l) System.out.println(a.nota() + " " + a.apellido() + " " + a.nombre());
47    }
48}

Toda la lógica de «qué va antes» está en un solo método: el algoritmo de ordenación no cambia, solo la comparación. Es exactamente lo que hace un Comparator en Java.

Comparar varios criterios en cascada (nota, después apellido, después nombre) da el mismo resultado que ordenar primero por el último criterio y luego, con un algoritmo estable, por los anteriores.

Test

Test: Ordenación por inserción

0/5 respondidas · 0 aciertos

Elige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.

  1. 1.¿Cuántos desplazamientos hace la inserción sobre un array ya ordenado?

  2. 2.¿Por qué las bibliotecas usan la inserción para los trozos pequeños?

  3. 3.En el bucle while (j >= 0 && a[j] > x), ¿qué pasa si se cambia el orden de las condiciones?

  4. 4.Al salir del bucle interno, ¿dónde se coloca x?

  5. 5.¿Qué cuenta exactamente el número de desplazamientos de la inserción?

Relacionado