MergeSort es un ordenamiento eficiente basado en división y conquista. Divide el array en mitades, ordena cada parte de forma recursiva y luego las combina manteniendo el orden. Con complejidad O(n log n) y estabilidad, es la opción preferida en escenarios donde la consistencia del orden importa.
MergeSort crea un árbol binario de subproblemas:
La clave está en partir el problema a la mitad hasta llegar al caso base:
void mergesort(int *valores, int inicio, int fin) {
if (inicio >= fin) {
return;
}
int medio = (inicio + fin) / 2;
mergesort(valores, inicio, medio);
mergesort(valores, medio + 1, fin);
merge(valores, inicio, medio, fin);
}
Cada llamada recursiva trabaja sobre un rango definido por índices inclusivos.
La función de mezcla toma dos mitades ordenadas y produce una salida ordenada en un buffer auxiliar, luego copia el resultado al array original:
#include <stdlib.h>
void merge(int *valores, int inicio, int medio, int fin) {
int total = fin - inicio + 1;
int *buffer = malloc(sizeof(int) * total);
if (!buffer) {
return; /* degradar sin crashear si la reserva falla */
}
int i = inicio;
int j = medio + 1;
int k = 0;
while (i <= medio && j <= fin) {
if (valores[i] <= valores[j]) {
buffer[k++] = valores[i++];
} else {
buffer[k++] = valores[j++];
}
}
while (i <= medio) {
buffer[k++] = valores[i++];
}
while (j <= fin) {
buffer[k++] = valores[j++];
}
for (int t = 0; t < total; t++) {
valores[inicio + t] = buffer[t];
}
free(buffer);
}
El tamaño del buffer siempre coincide con el segmento a mezclar, evitando desbordes y manteniendo la estabilidad.
MergeSort preserva el orden relativo de elementos equivalentes porque al mezclar toma primero el elemento de la izquierda cuando son iguales (<= en la comparación). Esta propiedad es clave en reportes, agrupaciones y operaciones de deduplicación.
La mezcla necesita espacio auxiliar proporcional al tamaño del segmento a combinar. En entornos críticos de memoria es común reutilizar un buffer global o alternar entre dos arrays para reducir copias.
Para evitar reservar y liberar memoria en cada mezcla, usamos un buffer auxiliar reutilizable:
#include <stdlib.h>
static void merge_with_buffer(int *arr, int *buffer, int inicio, int medio, int fin) {
int i = inicio;
int j = medio + 1;
int k = inicio;
while (i <= medio && j <= fin) {
if (arr[i] <= arr[j]) {
buffer[k++] = arr[i++];
} else {
buffer[k++] = arr[j++];
}
}
while (i <= medio) {
buffer[k++] = arr[i++];
}
while (j <= fin) {
buffer[k++] = arr[j++];
}
for (int t = inicio; t <= fin; t++) {
arr[t] = buffer[t];
}
}
static void mergesort_rec(int *arr, int *buffer, int inicio, int fin) {
if (inicio >= fin) {
return;
}
int medio = (inicio + fin) / 2;
mergesort_rec(arr, buffer, inicio, medio);
mergesort_rec(arr, buffer, medio + 1, fin);
merge_with_buffer(arr, buffer, inicio, medio, fin);
}
El buffer se pasa a todas las llamadas y se escribe el resultado ordenado directamente en el array original.
La versión recursiva clásica reserva el buffer una sola vez y lo comparte:
void mergesort_topdown(int *arr, int n) {
int *buffer = malloc(sizeof(int) * n);
if (!buffer) {
return;
}
mergesort_rec(arr, buffer, 0, n - 1);
free(buffer);
}
Esta estrategia reduce la sobrecarga de reservas y mantiene la estabilidad del algoritmo.
La variante iterativa evita recursión. Recorre el array en runs de tamaño creciente y mezcla pares adyacentes:
void mergesort_bottomup(int *arr, int n) {
for (int size = 1; size < n; size *= 2) {
for (int inicio = 0; inicio < n - size; inicio += 2 * size) {
int medio = inicio + size - 1;
int fin = (inicio + 2 * size - 1 < n) ? inicio + 2 * size - 1 : n - 1;
merge(arr, inicio, medio, fin);
}
}
}
Resulta práctico cuando se desea controlar el consumo de pila o instrumentar el algoritmo paso a paso. Para minimizar reservas, se puede usar un buffer compartido igual que en la versión top-down.
O(n log n) en peor, promedio y mejor caso.O(n) espacio adicional para el buffer de mezcla.O(n log n) incluso en el peor caso; estabilidad; buen comportamiento en datos parcialmente ordenados; estructura clara para paralelizar la fase de división.MergeSort es un punto de partida excelente para comprender ordenamientos eficientes: muestra cómo dividir y combinar de forma sistemática y establece un rendimiento predecible.
Estructura mínima para compilar y depurar MergeSort en CLion o desde consola. Incluye el código de cada archivo base:
/* sort.h */
void merge(int *valores, int inicio, int medio, int fin);
void mergesort_topdown(int *arr, int n);
void printArray(const int *arr, int n);
/* sort.c */
#include <stdio.h>
#include <stdlib.h>
#include "sort.h"
void printArray(const int *arr, int n) {
printf("[");
for (int i = 0; i < n; i++) {
printf("%d", arr[i]);
if (i + 1 < n) {
printf(", ");
}
}
printf("]\n");
}
void merge(int *valores, int inicio, int medio, int fin) {
int total = fin - inicio + 1;
int *buffer = malloc(sizeof(int) * total);
if (!buffer) {
return;
}
int i = inicio;
int j = medio + 1;
int k = 0;
while (i <= medio && j <= fin) {
if (valores[i] <= valores[j]) {
buffer[k++] = valores[i++];
} else {
buffer[k++] = valores[j++];
}
}
while (i <= medio) {
buffer[k++] = valores[i++];
}
while (j <= fin) {
buffer[k++] = valores[j++];
}
for (int t = 0; t < total; t++) {
valores[inicio + t] = buffer[t];
}
free(buffer);
}
/* mergesort.c */
#include <stdlib.h>
#include "sort.h"
static void merge_with_buffer(int *arr, int *buffer, int inicio, int medio, int fin) {
int i = inicio;
int j = medio + 1;
int k = inicio;
while (i <= medio && j <= fin) {
if (arr[i] <= arr[j]) {
buffer[k++] = arr[i++];
} else {
buffer[k++] = arr[j++];
}
}
while (i <= medio) {
buffer[k++] = arr[i++];
}
while (j <= fin) {
buffer[k++] = arr[j++];
}
for (int t = inicio; t <= fin; t++) {
arr[t] = buffer[t];
}
}
static void mergesort_rec(int *arr, int *buffer, int inicio, int fin) {
if (inicio >= fin) {
return;
}
int medio = (inicio + fin) / 2;
mergesort_rec(arr, buffer, inicio, medio);
mergesort_rec(arr, buffer, medio + 1, fin);
merge_with_buffer(arr, buffer, inicio, medio, fin);
}
void mergesort_topdown(int *arr, int n) {
int *buffer = malloc(sizeof(int) * n);
if (!buffer) {
return;
}
mergesort_rec(arr, buffer, 0, n - 1);
free(buffer);
}
/* main.c */
#include <stdio.h>
#include "sort.h"
int main(void) {
int datos[] = {42, 7, 18, 3, 25};
int n = sizeof(datos) / sizeof(datos[0]);
printf("Antes: ");
printArray(datos, n);
mergesort_topdown(datos, n);
printf("Despues: ");
printArray(datos, n);
return 0;
}
Con esta separación, CLion recompila solo los archivos modificados y puedes hacer step-by-step en cada fase del algoritmo.