Apuntes DAM
Volver al inicio

Ordenación por selección

AlgoritmosOrdenaciónNivel básicoTambién: selection sort, método de selección directa

En cada pasada busca el menor de la parte sin ordenar y lo intercambia con el primero de esa parte. Siempre O(n²) comparaciones, pero como mucho n − 1 intercambios. No es estable.

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 selección

Escribe los números y mira cómo en cada pasada se busca el menor y se pone en su sitio.

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

Paso 1

Array inicial: [29, 10, 14, 37, 13, 5].

1static void seleccion(int[] a) {
2    for (int i = 0; i < a.length - 1; i++) {  // comparaciones = 0, intercambios = 0
3        int min = i;
4        for (int j = i + 1; j < a.length; j++) {
5            if (a[j] < a[min]) {
6                min = j;
7            }
8        }
9        int t = a[i]; a[i] = a[min]; a[min] = t;
10    }
11}

Variables

comparaciones
0
intercambios
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 selección hace lo que haría cualquiera a mano: busca el menor de todos y lo pone el primero; luego busca el menor de los que quedan y lo pone el segundo; y así hasta el final.

El array queda dividido en dos partes: a la izquierda, la parte ya ordenada (que crece una posición en cada pasada) y a la derecha, la que falta. Cada pasada recorre toda la parte de la derecha para encontrar su mínimo y lo intercambia con el primero de esa parte.

Su gran ventaja es que hace muy pocos movimientos: exactamente un intercambio por pasada, n − 1 en total, esté como esté el array. Su gran inconveniente es que no aprovecha nada: aunque el array ya esté ordenado, sigue recorriendo la parte derecha entera para comprobar cuál es el menor, así que siempre hace n(n − 1)/2 comparaciones.

No es estable: el intercambio puede mandar un elemento muy lejos y saltarse a otro igual que él. Por eso no sirve para ordenar por un segundo criterio respetando el orden anterior.

Cuándo usarlo

  • Cuando escribir o mover datos es mucho más caro que compararlos (memoria flash, elementos grandes que no se pueden mover por referencia): hace como mucho n − 1 intercambios.
  • Cuando solo hacen falta los k menores: bastan k pasadas, sin ordenar el resto.
  • Para aprender: es el más intuitivo y su número de comparaciones no depende de los datos, lo que facilita calcularlo.

Cuándo no

  • Con muchos datos: siempre es O(n²), incluso si el array ya estaba ordenado (la burbuja y la inserción, en ese caso, son O(n)).
  • Cuando se necesita estabilidad (ordenar por apellido respetando un orden previo por nombre).

Paso a paso

  1. Parte ordenada y sin ordenar. La posición i separa las dos partes: a[0..i-1] ya está ordenada y en su sitio definitivo; a[i..n-1] falta.
  2. Buscar el mínimo. Se recorre a[i..n-1] guardando la posición del menor visto (min): empieza en i y cambia cada vez que aparece uno más pequeño.
  3. Intercambiar. Se intercambia a[i] con a[min]: el menor de la parte derecha pasa a ser el último de la parte ordenada.
  4. Avanzar. Se repite con i + 1 hasta i = n − 2: el último elemento queda solo y, por fuerza, es el mayor.

El código

Selección pasada a pasada

Cada pasada fija una posición más por la izquierda.

Java
1import java.util.Arrays;
2
3public class Main {
4    /** Ordena a por selección: en cada pasada busca el menor de la parte sin ordenar
5        y lo intercambia con el primero de esa parte. */
6    static void seleccion(int[] a) {
7        for (int i = 0; i < a.length - 1; i++) {
8            int min = i;                                   // posición del menor visto hasta ahora
9            for (int j = i + 1; j < a.length; j++)
10                if (a[j] < a[min]) min = j;
11            int t = a[i]; a[i] = a[min]; a[min] = t;       // un solo intercambio por pasada
12            System.out.println("Pasada " + (i + 1) + ": el menor es " + a[i] + " → " + Arrays.toString(a));
13        }
14    }
15
16    public static void main(String[] args) {
17        int[] a = {29, 10, 14, 37, 13, 5};
18        seleccion(a);
19    }
20}

Salida al ejecutarlo (la misma en los 5 lenguajes)

Pasada 1: el menor es 5 → [5, 10, 14, 37, 13, 29]
Pasada 2: el menor es 10 → [5, 10, 14, 37, 13, 29]
Pasada 3: el menor es 13 → [5, 10, 13, 37, 14, 29]
Pasada 4: el menor es 14 → [5, 10, 13, 14, 37, 29]
Pasada 5: el menor es 29 → [5, 10, 13, 14, 29, 37]

Por qué no es estable

Dos cartas con el mismo valor cambian de orden: el intercambio de la primera pasada manda el 5♥ al final, por detrás del 5♠.

Java
1import java.util.Arrays;
2
3public class Main {
4    record Carta(int valor, String palo) {
5        @Override public String toString() { return valor + palo; }
6    }
7
8    public static void main(String[] args) {
9        Carta[] c = { new Carta(5, "♥"), new Carta(5, "♠"), new Carta(2, "♦") };
10        // Selección por valor: el 2♦ se intercambia con el primer 5 y lo manda detrás del otro 5
11        for (int i = 0; i < c.length - 1; i++) {
12            int min = i;
13            for (int j = i + 1; j < c.length; j++)
14                if (c[j].valor() < c[min].valor()) min = j;
15            Carta t = c[i]; c[i] = c[min]; c[min] = t;
16        }
17        System.out.println(Arrays.toString(c) + ": el 5♥ iba antes que el 5♠ y ahora va después");
18    }
19}

Salida al ejecutarlo (la misma en los 5 lenguajes)

[2♦, 5♠, 5♥]: el 5♥ iba antes que el 5♠ y ahora va después

Traza: selección de {29, 10, 14, 37, 13, 5}

PasadaMenor de la parte derechaIntercambioArray
15 (posición 5)a[0] ↔ a[5]5 10 14 37 13 29
210 (posición 1)ninguno5 10 14 37 13 29
313 (posición 4)a[2] ↔ a[4]5 10 13 37 14 29
414 (posición 4)a[3] ↔ a[4]5 10 13 14 37 29
529 (posición 5)a[4] ↔ a[5]5 10 13 14 29 37

Cinco pasadas para seis elementos: siempre n − 1, aunque en la última el menor ya estuviera en su sitio.

Complejidad

CasoComparacionesIntercambiosCoste
Mejor (ya ordenado)n(n − 1)/2n − 1 (cada uno consigo mismo)O(n²)
Medion(n − 1)/2≈ n − 1O(n²)
Peorn(n − 1)/2n − 1O(n²)

Las comparaciones no dependen de los datos. Memoria extra: O(1). Comparada con la burbuja hace los mismos ≈ n²/2 de comparaciones, pero muchísimos menos intercambios.

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²)

Siempre hace las mismas n(n − 1)/2 comparaciones, esté como esté el array; a cambio, como mucho n − 1 intercambios. Las curvas grises son las demás clases, para comparar.

En la práctica

  • La idea de «buscar el mínimo de lo que queda» es la base del heapsort, que hace lo mismo pero encuentra cada mínimo (o máximo) en O(log n) gracias a un montículo.
  • Elegir los k mejores (un podio, los 10 productos más vendidos) con k pasadas de selección es O(k·n): para k pequeño es más rápido que ordenarlo todo.
  • Se usa en sistemas donde escribir cuesta mucho más que leer, como algunas memorias EEPROM o flash con ciclos de escritura limitados.

Errores típicos

  • Guardar el valor mínimo en vez de su posición: al final no se sabe con quién intercambiar.
  • Empezar el bucle interno en 0 en lugar de en i + 1: vuelve a mirar la parte ya ordenada y estropea el resultado.
  • Intercambiar dentro del bucle interno cada vez que aparece uno menor: sigue ordenando, pero hace muchos más intercambios y pierde su única ventaja.
  • Esperar que sea estable: el intercambio puede saltarse elementos iguales.
  • Hacer la última pasada (i = n − 1): no hace daño, pero sobra, porque un solo elemento ya está ordenado.

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. El podio sin ordenarlo todo

La primera línea es k, cuántos corredores hay que premiar. Cada línea siguiente es un corredor: su nombre y su tiempo mm:ss. Muestra los k más rápidos, en orden, usando solo k pasadas de selección (el resto no hace falta ordenarlo), y cuántas comparaciones has hecho frente a las que haría la selección completa. Completa seleccionParcial.

  • Entrada: 3, luego líneas como Ana 12:31 (minutos de 1 a 3 cifras y segundos de 00 a 59).
  • Salida: 1. Marta 11:40, 2. … y al final Comparaciones: C (ordenarlo todo con selección: T).
  • Errores: k no válido: «texto» (k es un entero de 1 a 999), Tiempo no válido: «línea» (se salta) y No hay corredores.
☕JavaEl podio sin ordenarlo todoMedio

Ejemplo

Entrada (lo que se escribe por teclado)
3
Ana 12:31
Luis 11:58
Eva 13:02
Marta 11:40
Pablo 12:05
Sara 14:10
Salida esperada
1. Marta 11:40
2. Luis 11:58
3. Pablo 12:05
Comparaciones: 12 (ordenarlo todo con selección: 15)
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
⏳
Test oculto #6
0/6 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 Corredor(String nombre, int segundos) { }
5
6    /** Deja en las k primeras posiciones los k menores tiempos, ya ordenados, haciendo solo
7        k pasadas de selección (no hace falta ordenar el resto). Devuelve las comparaciones. */
8    static long seleccionParcial(List<Corredor> l, int k) {
9        long comparaciones = 0;
10        for (int i = 0; i < Math.min(k, l.size() - 1); i++) {
11            int min = i;
12            for (int j = i + 1; j < l.size(); j++) {
13                comparaciones++;
14                if (l.get(j).segundos() < l.get(min).segundos()) min = j;
15            }
16            Corredor t = l.get(i);
17            l.set(i, l.get(min));
18            l.set(min, t);
19        }
20        return comparaciones;
21    }
22
23    public static void main(String[] args) {
24        Scanner sc = new Scanner(System.in);
25        String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
26        if (!primera.matches("[1-9]\\d{0,2}")) {
27            System.out.println("k no válido: «" + primera + "»");
28            return;
29        }
30        int k = Integer.parseInt(primera);
31        List<Corredor> l = new ArrayList<>();
32        while (sc.hasNextLine()) {
33            String linea = sc.nextLine().trim();
34            if (linea.isEmpty()) continue;
35            String[] p = linea.split("\\s+");
36            if (p.length != 2 || !p[1].matches("\\d{1,3}:[0-5]\\d")) {
37                System.out.println("Tiempo no válido: «" + linea + "»");
38                continue;
39            }
40            String[] t = p[1].split(":");
41            l.add(new Corredor(p[0], Integer.parseInt(t[0]) * 60 + Integer.parseInt(t[1])));
42        }
43        if (l.isEmpty()) {
44            System.out.println("No hay corredores");
45            return;
46        }
47        long c = seleccionParcial(l, k);
48        for (int i = 0; i < Math.min(k, l.size()); i++)
49            System.out.printf("%d. %s %d:%02d%n", i + 1, l.get(i).nombre(), l.get(i).segundos() / 60, l.get(i).segundos() % 60);
50        long todo = (long) l.size() * (l.size() - 1) / 2;
51        System.out.println("Comparaciones: " + c + " (ordenarlo todo con selección: " + todo + ")");
52    }
53}

Cada pasada de la selección fija definitivamente una posición por la izquierda, así que tras k pasadas las k primeras ya contienen los k menores, en orden, aunque el resto siga desordenado.

Con n corredores y k pequeño son unas k·n comparaciones en vez de n²/2: para un podio de 3 entre 10.000, unas 30.000 en lugar de 50 millones.

2. Selección por los dos extremos

Mejora la selección buscando en cada pasada el menor y el mayor a la vez: el menor va al principio de la zona y el mayor al final, así que cada pasada fija dos posiciones y hacen falta la mitad de pasadas. Cuidado con un caso: si el mayor estaba justo en la posición donde se pone el menor, el primer intercambio lo ha movido. Completa pasada.

  • Entrada: una línea con números enteros separados por espacios.
  • Por cada pasada: Pasada 1: 1 al principio y 9 al final → 1 5 3 7 2 9; al final, Ordenado en P pasadas: ….
  • Errores: No hay números y Número no válido: «x».
☕JavaSelección por los dos extremosDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
3 9 5 1 7 2
Salida esperada
Pasada 1: 1 al principio y 9 al final → 1 2 5 3 7 9
Pasada 2: 2 al principio y 7 al final → 1 2 5 3 7 9
Pasada 3: 3 al principio y 5 al final → 1 2 3 5 7 9
Ordenado en 3 pasadas: 1 2 3 5 7 9
⏳
Test oculto #3
⏳
Test oculto #4
⏳
Test oculto #5
⏳
Test oculto #6
0/6 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    static void cambiar(int[] a, int i, int j) {
5        int t = a[i]; a[i] = a[j]; a[j] = t;
6    }
7
8    /** Una pasada de selección por los dos extremos sobre a[ini..fin]: busca a la vez el menor y el
9        mayor, pone el menor en ini y el mayor en fin. */
10    static void pasada(int[] a, int ini, int fin) {
11        int min = ini, max = ini;
12        for (int j = ini + 1; j <= fin; j++) {
13            if (a[j] < a[min]) min = j;
14            if (a[j] > a[max]) max = j;
15        }
16        cambiar(a, ini, min);
17        if (max == ini) max = min;      // el mayor estaba en ini y el intercambio lo ha llevado a min
18        cambiar(a, fin, max);
19    }
20
21    static String texto(int[] a) {
22        StringJoiner sj = new StringJoiner(" ");
23        for (int x : a) sj.add(String.valueOf(x));
24        return sj.toString();
25    }
26
27    public static void main(String[] args) {
28        Scanner sc = new Scanner(System.in);
29        String linea = sc.hasNextLine() ? sc.nextLine().trim() : "";
30        if (linea.isEmpty()) {
31            System.out.println("No hay números");
32            return;
33        }
34        String[] partes = linea.split("\\s+");
35        int[] a = new int[partes.length];
36        for (int i = 0; i < partes.length; i++) {
37            try {
38                a[i] = Integer.parseInt(partes[i]);
39            } catch (NumberFormatException e) {
40                System.out.println("Número no válido: «" + partes[i] + "»");
41                return;
42            }
43        }
44        int pasadas = 0;
45        for (int ini = 0, fin = a.length - 1; ini < fin; ini++, fin--) {
46            pasada(a, ini, fin);
47            pasadas++;
48            System.out.println("Pasada " + pasadas + ": " + a[ini] + " al principio y " + a[fin] + " al final → " + texto(a));
49        }
50        System.out.println("Ordenado en " + pasadas + (pasadas == 1 ? " pasada: " : " pasadas: ") + texto(a));
51    }
52}

Cada pasada hace casi las mismas comparaciones que antes (dos por elemento), pero fija dos posiciones: el número total de comparaciones es parecido, el de pasadas se reduce a la mitad.

El caso del mayor en ini es el típico error de esta variante: sin la corrección, el segundo intercambio mueve el elemento que acaba de llegar a min en vez del mayor.

Test

Test: Ordenación por selecció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 intercambios hace como mucho la ordenación por selección con n elementos?

  2. 2.¿Cuántas comparaciones hace la selección sobre un array ya ordenado de 100 elementos?

  3. 3.Tras la pasada i de la selección, ¿qué parte del array está en su sitio definitivo?

  4. 4.¿Es estable la ordenación por selección?

  5. 5.¿En qué situación tiene ventaja sobre la burbuja?

Relacionado