Cola de prioridad limitada
Consideremos el siguiente esquema. Usaremos la mitad de la memoria, , para mantener una cola de prioridad clásica . Todas las inserciones ocurrirán en , gratis. Mientras esta cola no se desborde, no usaremos el disco.
En el momento en que se inserte un nuevo elemento y esté llena, ésta se ordenará completamente en memoria (gratis) y se almacenará en un archivo en disco, , lo que requerirá escrituras. Inmediatamente crearemos un buffer de tamaño en memoria para , donde leeremos su primer bloque. quedará vacía de nuevo para aceptar nuevas inserciones.
De ahora en adelante, cada vez que extraigamos el mínimo, tendremos que elegir entre el mínimo de y el primer elemento del buffer de . Una vez que leímos todo el buffer de , lo volvemos a llenar leyendo el siguiente bloque de elementos.
Como sigue recibiendo inserciones, puede volverse a llenar. En este caso lo ordenamos nuevamente y lo escribimos en un nuevo archivo, . En general, tendremos archivos ordenados , y las extracciones de mínimo tendrán que considerar el mínimo entre el mínimo de y los mínimos de cada . Esto se hace fácilmente en tiempo de CPU con una pequeña cola de prioridad que mantiene los primeros elementos de cada y los reemplaza por el siguiente de su buffer cuando éstos son extraídos.
Note que en todo momento los archivos pueden estar a medio leer. Podríamos pensar en un mecanismo más sofisticado que eliminara los archivos leídos, o los uniera cuando se hicieran pequeños, pero aquí mantendremos la simplicidad: los archivos se crean y se van leyendo, y nunca se eliminan o unen.
Considerando que tenemos espacio de memoria para los buffers, tenemos un límite de . Esto significa que tenemos un límite de al total de elementos que pueden ser insertados en esta estructura (en el peor caso; en la práctica muchos podrían eliminarse antes de pasar a un archivo ). Con una memoria de GBs y un de KBs, esto equivale a PBs (petabytes).
Para analizar el costo de las operaciones, consideremos lo que nos puede costar un elemento desde que es insertado hasta que es extraído. La inserción es gratis, pero el elemento puede finalmente ser enviado a un archivo , donde es escrito junto con otros elementos, por lo que podemos cobrarle escrituras. Luego, puede ser leído de este archivo a su buffer en memoria, junto con otros elementos, por lo que podemos cobrarle lecturas. En total, cada operación cuesta I/Os. Esto, por supuesto, es en un sentido amortizado: muchas operaciones son gratis, y de repente una inserción provoca un costo de para escribir un archivo completo. En un esquema más sofisticado, podemos "deamortizar" el costo mediante escribir este archivo poco a poco, dividiendo en dos colas de tamaño , de manera que cuando una se llena empezamos a usar la otra y vamos escribiendo la que se llenó poco a poco a disco, a lo largo de las sucesivas inserciones que siguen. Debemos asegurar que, para cuando la segunda cola se llene, la primera ya se habrá vaciado y puedan intercambiar sus roles.
Aún en sentido amortizado, esta complejidad parece violar la cota inferior: podríamos ordenar en disco en tiempo mediante insertar los elementos y luego extraerlos de esta cola de prioridad. Esto es efectivamente cierto, pero dentro de la limitación de . Bajo este supuesto, la complejidad de ordenar es efectivamente .