public class Alumno { // 1. clase publica
private String nombre; // 2. atributos SIEMPRE private
private double nota; // (nadie los toca desde fuera)
public Alumno(String nombre, double nota) { // 3. constructor = nombre de la clase, sin tipo de retorno
this.nombre = nombre; // this.atributo = parametro (mismo nombre)
setNota(nota); // reuso el setter: tambien valida al construir
}
public String getNombre() { return nombre; } // 4. getters: lectura controlada
public double getNota() { return nota; }
public void setNota(double nota) { // 5. setter CON validacion (esto da puntos)
if (nota >= 0 && nota <= 10) { // solo acepta valores legales
this.nota = nota; // si no, se ignora (o lanzar excepcion)
}
}
@Override // 6. redefine el toString de Object
public String toString() {
return nombre + " (" + nota + ")"; // lo que imprime System.out.println(alumno)
}
}
private — todos, sin excepción.this.if que rechaza valores ilegales).toString() con @Override devolviendo un String.public abstract class Figura { // abstract: NO se puede hacer new Figura()
protected String nombre; // protected: las hijas SI lo ven
public Figura(String nombre) { // las hijas lo llaman con super(...)
this.nombre = nombre;
}
public abstract double area(); // SIN cuerpo: cada hija DEBE implementarlo
public String describir() { // metodo normal: se hereda tal cual
return nombre + ": area = " + area(); // llama al area() de la hija real
}
}
class Circulo extends Figura {
private double radio;
public Circulo(double radio) {
super("Circulo"); // PRIMERA linea: constructor del padre
this.radio = radio;
}
@Override public double area() { return Math.PI * radio * radio; }
}
class Rectangulo extends Figura {
private double base, altura;
public Rectangulo(double base, double altura) {
super("Rectangulo");
this.base = base; this.altura = altura;
}
@Override public double area() { return base * altura; }
}
Uso polimórfico (esto es lo que demuestra que entiendes el tema):
Figura[] figuras = { new Circulo(2), new Rectangulo(3, 4) }; // array del tipo PADRE
for (Figura f : figuras) {
System.out.println(f.describir()); // ejecuta el area() de CADA hija (enlace dinamico)
}
| Clase abstracta | Interfaz | |
|---|---|---|
| ¿Qué aporta? | Atributos + código común + métodos abstractos | Solo el contrato: métodos que hay que cumplir |
| ¿Cómo se usa? | extends — solo UNA | implements — VARIAS a la vez |
| ¿Estado? | Sí: atributos y constructor | No tiene atributos de instancia ni constructor |
| ¿Cuándo? | Jerarquía "ES-UN" con código compartido (Figura→Círculo) | Capacidad "SABE-HACER" (Comparable, Serializable) |
@Override): misma firma en la hija, cambia el comportamiento heredado.i*=2, i/=2, mitades) → O(log n). Bucle de n con interno logarítmico → O(n log n).| Patrón de código | Orden | Por qué |
|---|---|---|
for (int i = 0; i < n; i++)
x++; | O(n) | n vueltas de trabajo constante |
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) x++; | O(n²) | anidados independientes: n·n |
for (int i = 0; i < n; i++) x++;
for (int j = 0; j < n; j++) x++; | O(n) | seguidos se suman: 2n → O(n) |
for (int i = 1; i < n; i *= 2)
x++; | O(log n) | i = 1,2,4,8... llega a n en log₂n pasos |
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j *= 2) x++; | O(n log n) | n vueltas × log n internas |
for (int i = 0; i < n; i++)
for (int j = 0; j < i; j++) x++; | O(n²) | dependiente: 0+1+…+(n−1) = n(n−1)/2 |
int v = a[0] + a[n - 1];
x = v * 2; | O(1) | nº fijo de operaciones, no depende de n |
static void f(int ini, int fin) {
if (ini >= fin) return;
int m = (ini + fin) / 2;
f(ini, m); f(m + 1, fin); // + mezcla O(n)
} | O(n log n) | T(n) = 2T(n/2) + n → mergesort |
for (int i = 0; i < n; i++)
for (int j = 0; j <= i; j++) x++;
Suma aritmética: 1+2+…+n = n(n+1)/2 → O(n²), NO O(n log n).for (int tam = 1; tam <= n; tam *= 2)
for (int j = 0; j < tam; j++) x++;
Suma geométrica: 1+2+4+…+n ≈ 2n → O(n), NO O(n log n).| Orden (de MEJOR a PEOR) | Nombre | Ejemplo | n = 1000 |
|---|---|---|---|
| O(1) | constante | acceso a[i], push/pop, get de HashMap | 1 |
| O(log n) | logarítmico | búsqueda binaria, ABB equilibrado | ~10 |
| O(n) | lineal | recorrer un array, búsqueda secuencial | 1 000 |
| O(n log n) | casilineal | mergesort, heapsort, quicksort (medio) | ~10 000 |
| O(n²) | cuadrático | burbuja, selección, inserción | 1 000 000 |
| O(2ⁿ) | exponencial | fuerza bruta, Fibonacci recursivo puro | astronómico |
static void burbujaMejorada(int[] a) {
boolean cambio = true; // bandera: optimizacion 1
for (int i = 0; i < a.length - 1 && cambio; i++) {
cambio = false; // supongo que ya esta ordenado
for (int j = 0; j < a.length - 1 - i; j++) { // -i: el final ya esta ordenado (opt. 2)
if (a[j] > a[j + 1]) { // vecinos desordenados...
int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; // ...se intercambian
cambio = true;
}
}
}
}
Compara vecinos y los intercambia si están al revés: en cada pasada el mayor "flota" hasta el final. Si una pasada entera no cambia nada, la bandera corta el algoritmo (mejor caso O(n)).
Pasada 1 de [5,2,4,1]: 5↔2→[2,5,4,1] · 5↔4→[2,4,5,1] · 5↔1→[2,4,1,5] (el 5 ya está colocado) Mejor O(n) (ya ordenado, 1 pasada) · Medio/Peor O(n²) · Estable SÍ · In situstatic void seleccion(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
int min = i; // posicion del minimo de lo no ordenado
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[min]) min = j; // solo APUNTO donde esta (no intercambio aun)
}
int t = a[i]; a[i] = a[min]; a[min] = t; // UN solo intercambio por pasada
}
}
Busca el mínimo de la zona sin ordenar y lo coloca al principio con un único intercambio por pasada. Hace siempre las mismas comparaciones, esté como esté el array (no tiene mejor caso).
Pasada 1 de [5,2,4,1]: mínimo = 1 (pos 3) → intercambio 5↔1 → [1,2,4,5] (el 1 ya está colocado) Mejor/Medio/Peor O(n²) siempre · Estable NO · Mínimo nº de intercambios (n−1)static void insercion(int[] a) {
for (int i = 1; i < a.length; i++) { // a[0] ya es una "mano" ordenada
int v = a[i]; // carta que voy a colocar
int j = i - 1;
while (j >= 0 && a[j] > v) { // mientras haya mayores a la izquierda...
a[j + 1] = a[j]; // ...los desplazo un hueco a la derecha
j--;
}
a[j + 1] = v; // inserto la carta en su hueco
}
}
Toma cada elemento y lo desliza hacia la izquierda hasta su hueco, como ordenar cartas en la mano. La zona izquierda del array está siempre ordenada.
i=1 en [5,2,4,1]: v=2, el 5 se desplaza → [2,5,4,1] · (i=2: el 4 entra entre 2 y 5 → [2,4,5,1]) Mejor O(n) (casi ordenado: el mejor de los simples) · Medio/Peor O(n²) · Estable SÍstatic void mergeSort(int[] a, int ini, int fin) {
if (ini >= fin) return; // CASO BASE: 0 o 1 elementos
int mid = (ini + fin) / 2;
mergeSort(a, ini, mid); // 1) ordeno mitad izquierda
mergeSort(a, mid + 1, fin); // 2) ordeno mitad derecha
merge(a, ini, mid, fin); // 3) mezclo las dos mitades ordenadas
}
static void merge(int[] a, int ini, int mid, int fin) {
int[] aux = new int[fin - ini + 1]; // array auxiliar (memoria extra O(n))
int i = ini, j = mid + 1, k = 0;
while (i <= mid && j <= fin) { // comparo cabezas y copio la menor
aux[k++] = (a[i] <= a[j]) ? a[i++] : a[j++]; // <= mantiene la estabilidad
}
while (i <= mid) aux[k++] = a[i++]; // restos de la izquierda
while (j <= fin) aux[k++] = a[j++]; // restos de la derecha
for (k = 0; k < aux.length; k++) a[ini + k] = aux[k]; // vuelco aux al original
}
Divide el array en mitades hasta trozos de 1 elemento (ya ordenados) y luego los mezcla por parejas comparando cabezas. Rendimiento garantizado, pero gasta un array auxiliar.
[5,2 | 4,1] → ordena mitades: [2,5] y [1,4] → mezcla cabezas: 1,2,4,5 → [1,2,4,5] Mejor/Medio/Peor O(n log n) SIEMPRE · Estable SÍ · Memoria extra O(n)static void quickSort(int[] a, int ini, int fin) {
if (ini >= fin) return; // CASO BASE: 0 o 1 elementos
int p = particionar(a, ini, fin); // coloca el pivote en su sitio definitivo
quickSort(a, ini, p - 1); // ordeno los menores
quickSort(a, p + 1, fin); // ordeno los mayores
}
static int particionar(int[] a, int ini, int fin) {
int pivote = a[ini]; // pivote = primer elemento
int i = ini; // frontera de los menores
for (int j = ini + 1; j <= fin; j++) {
if (a[j] < pivote) { // menor que el pivote:
i++; // amplio la zona de menores...
int t = a[i]; a[i] = a[j]; a[j] = t; // ...y lo meto en ella
}
}
int t = a[ini]; a[ini] = a[i]; a[i] = t; // pivote a la frontera (su sitio final)
return i; // posicion definitiva del pivote
}
Elige un pivote y particiona: menores a su izquierda, mayores a su derecha; el pivote queda ya en su posición definitiva. Repite recursivamente en cada lado.
[5,2,4,1], pivote=5: 2,4,1 son menores → swap final 5↔1 → [1,2,4,5], pivote fijo en pos 3 Mejor/Medio O(n log n) · Peor O(n²) (array YA ordenado con pivote = primero) · Estable NO · In situstatic void heapSort(int[] a) {
int n = a.length;
for (int i = n / 2 - 1; i >= 0; i--) { // FASE 1: construir MAX-HEAP
hundir(a, n, i); // desde el ultimo padre hacia la raiz
}
for (int fin = n - 1; fin > 0; fin--) { // FASE 2: extraer el maximo n-1 veces
int t = a[0]; a[0] = a[fin]; a[fin] = t; // raiz (maximo) ↔ ultimo del heap
hundir(a, fin, 0); // re-hundir la nueva raiz (heap mas corto)
}
}
static void hundir(int[] a, int n, int i) { // baja a[i] hasta cumplir padre >= hijos
int mayor = i, izq = 2 * i + 1, der = 2 * i + 2; // hijos de i en el array
if (izq < n && a[izq] > a[mayor]) mayor = izq;
if (der < n && a[der] > a[mayor]) mayor = der;
if (mayor != i) { // algun hijo es mayor que el padre:
int t = a[i]; a[i] = a[mayor]; a[mayor] = t;
hundir(a, n, mayor); // sigo hundiendo por esa rama
}
}
Construye un max-heap sobre el propio array (todo padre ≥ sus hijos, máximo en a[0]). Luego, n−1 veces: intercambia raíz↔último, acorta el heap y "hunde" la nueva raíz.
[5,2,4,1] ya es max-heap → 5↔1 → [1,2,4 | 5] → hundir raíz → [4,2,1 | 5] Mejor/Medio/Peor O(n log n) garantizado · Estable NO · In situ (memoria O(1))| Algoritmo | Mejor | Medio | Peor | Estable | Memoria | Apunte clave |
|---|---|---|---|---|---|---|
| Burbuja mejorada | O(n) | O(n²) | O(n²) | Sí | O(1) | bandera → para si no hay cambios |
| Selección | O(n²) | O(n²) | O(n²) | No | O(1) | siempre igual; solo n−1 intercambios |
| Inserción | O(n) | O(n²) | O(n²) | Sí | O(1) | el mejor con arrays casi ordenados |
| Mergesort | O(n log n) | O(n log n) | O(n log n) | Sí | O(n) | garantizado, pero array auxiliar |
| Quicksort | O(n log n) | O(n log n) | O(n²) | No | O(log n) | peor caso: ya ordenado, pivote 1º |
| Heapsort | O(n log n) | O(n log n) | O(n log n) | No | O(1) | garantizado e in situ |
static int busquedaBinaria(int[] a, int x) {
int low = 0, high = a.length - 1;
while (low <= high) { // OJO: <= (con < falla, ver abajo)
int mid = (low + high) / 2; // miro el centro
if (a[mid] == x) return mid; // encontrado
if (a[mid] < x) low = mid + 1; // esta a la derecha: descarto mitad izq.
else high = mid - 1; // esta a la izquierda: descarto mitad der.
}
return -1; // no esta
}
while (low < high). Cuando el intervalo se reduce a UN elemento (low == high) el bucle no entra y ese elemento no se comprueba. Siempre low <= high.Cada vuelta descarta la mitad → O(log n) (peor caso). Mejor caso O(1): acierta al centro a la primera.
| Estructura | Idea en 5 palabras | Operaciones y coste | Clase Java | Elígela si piden… |
|---|---|---|---|---|
| Array | casillas contiguas con índice directo | acceso O(1) · insertar/borrar en medio O(n) | int[], ArrayList | acceso por posición i |
| Lista enlazada | nodos encadenados por referencias | insertar/borrar en extremos O(1) · acceso O(n) | LinkedList | muchas altas/bajas, sin índices |
| Pila (LIFO) | el último en entrar sale | push / pop / peek O(1) | ArrayDeque (push/pop) | deshacer, paréntesis, llamadas recursivas |
| Cola (FIFO) | el primero en entrar sale | offer / poll / peek O(1) | ArrayDeque (offer/poll) | turnos, cola de impresión, BFS |
| ABB (árbol bin. búsqueda) | izquierda < raíz < derecha | buscar/insertar/borrar O(log n) equilibrado (O(n) degenerado) | TreeSet, TreeMap | datos SIEMPRE ordenados, rangos, mín/máx |
| Tabla hash | la clave calcula su posición | put / get / remove O(1) medio | HashMap, HashSet | acceso rápido por clave, contar, duplicados |
Tree* · piden velocidad por clave → Hash* ·
piden FIFO (turnos) → cola · piden LIFO (deshacer) → pila · piden posición i → array/ArrayList ·
piden insertar/borrar constante → LinkedList.
Deque<Integer> pila = new ArrayDeque<>(); // pila LIFO pila.push(1); pila.push(2); pila.push(3); // apilar: la cima es 3 System.out.println(pila.pop()); // 3 (sale el ULTIMO que entro) System.out.println(pila.peek()); // 2 (mira la cima SIN sacarla) System.out.println(pila.isEmpty()); // false
Map<String, Integer> notas = new HashMap<>(); // clave -> valor
notas.put("Ana", 7); notas.put("Luis", 5); // insertar O(1)
notas.put("Ana", 9); // clave repetida: SOBRESCRIBE
System.out.println(notas.get("Ana")); // 9 (acceso O(1) por clave)
System.out.println(notas.containsKey("Eva")); // false
Imports: import java.util.*; cubre Deque, ArrayDeque, Map, HashMap, List, ArrayList…
if que devuelve un valor directo SIN llamarse a sí mismo (para la recursión).
② CASO RECURSIVO: se llama a sí misma con un problema más pequeño que se acerca SIEMPRE a la base.
static long factorial(int n) {
if (n <= 1) return 1; // CASO BASE: 0! = 1! = 1
return n * factorial(n - 1); // CASO RECURSIVO: n! = n * (n-1)!
}
Traza: factorial(4) = 4·factorial(3) = 4·3·factorial(2) = 4·3·2·factorial(1) = 4·3·2·1 = 24. Sin caso base → StackOverflowError.
private en los atributos → adiós encapsulación (medio ejercicio).==: compara referencias. Usa equals(): s1.equals(s2).a[i]=a[j]; a[j]=a[i]; pierde el valor. Siempre: int t=a[i]; a[i]=a[j]; a[j]=t;StackOverflowError.low < high en vez de low <= high → se salta el último candidato.a[j+1], el bucle debe llegar solo hasta j < n-1 (en burbuja, n-1-i).@Override / extends → no compila.new de una clase abstracta o saltarse super(...) como primera línea del constructor de la hija.Repaso Exprés EDA · UAX 1º Ing. Informática · Si sabes reproducir a mano las plantillas 1, 2 y los 6 códigos del punto 4, el examen está hecho.