¿Existe una estructura de datos de cola prioritaria que admita las siguientes operaciones?
- Insertar (x, p) : agrega un nuevo registro x con prioridad p
- StableExtractMin () : devuelve y elimina el registro con prioridad mínima, rompiendo los lazos por orden de inserción .
Por lo tanto, después de Insertar (a, 1), Insertar (b, 2), Insertar (c, 1), Insertar (d, 2), una secuencia de StableExtractMin's devolvería a, luego c, luego b, luego d.
Obviamente, uno podría usar cualquier estructura de datos de cola prioritaria almacenando el par como la prioridad real, pero estoy interesado en estructuras de datos que no almacenen explícitamente los tiempos de inserción (u orden de inserción), por analogía con la ordenación estable .
Equivalentemente (?): ¿Existe una versión estable de heapsort que no requiera espacio adicional ?
ds.data-structures
Jeffε
fuente
fuente
Respuestas:
El método Bently-Saxe proporciona una cola de prioridad estable bastante natural.
Almacene sus datos en una secuencia de matrices ordenadas . tiene el tamaño . Cada matriz también mantiene un contador . Las entradas de matriz contienen datos.A0,…,Ak Ai 2i ci Ai[ci],…,Ai[2i−1]
Para cada , todos los elementos en se agregaron más recientemente que los de y dentro de cada elementos se ordenan por valor, y los lazos se rompen colocando los elementos más antiguos delante de los más nuevos. Tenga en cuenta que esto significa que podemos fusionar y y preservar este orden. (En el caso de empates durante la fusión, tome el elemento de ).A i A i + 1 A i A i A i + 1 A i + 1i Ai Ai+1 Ai Ai UNAi + 1 UNAi + 1
Para insertar un valor , encontrar la más pequeña tal que contiene 0 elementos, fusión y , almacenar esto en y conjunto adecuadamente.i A i A 0 , … , A i - 1 x A iX yo UNAyo UNA0 0, ... , Ai - 1 X UNAyo do0 0, ... , cyo
Para extraer el mínimo, encuentre el índice más grande tal que el primer elemento en sea mínimo sobre todo e incremente .A i [ c iyo i c iUNAyo[ cyo] yo doyo
Según el argumento estándar, esto le da a tiempo amortizado por operación y es estable debido al orden descrito anteriormente.O ( logn )
Para una secuencia de inserciones y extracciones, esto usa entradas de matriz (no mantenga matrices vacías) más palabras de datos de contabilidad. No responde la versión de Mihai de la pregunta, pero muestra que la restricción estable no requiere una gran cantidad de espacio. En particular, muestra que no hay un límite inferior en el espacio extra necesario.n O ( log nnorte norte Ω ( n )O ( logn ) Ω ( n )
Actualización: Rolf Fagerberg señala que si podemos almacenar valores nulos (sin datos), toda esta estructura de datos se puede empaquetar en una matriz de tamaño , donde es el número de inserciones hasta ahora.nnorte norte
Primero, observe que podemos empaquetar en una matriz en ese orden (con primero, seguido de si no está vacío, y así sucesivamente). La estructura de esto está completamente codificada por la representación binaria de , el número de elementos insertados hasta ahora. Si la representación binaria de tiene un 1 en la posición , entonces ocupará la ubicación de la matriz , de lo contrario no ocupará ninguna ubicación de la matriz.A k A k - 1 n n iUNAk, ... , A0 0 UNAk UNAk - 1 norte norte yo 2 iUNAyo 2yo
Al insertar, , y la longitud de nuestra matriz, aumentan en 1, y podemos fusionar más el nuevo elemento usando algoritmos de fusión estables existentes.Anorte UNA0 0, ... , Ayo
Ahora, donde usamos valores nulos es en deshacernos de los contadores . En , almacenamos el primer valor, seguido de los valores nulos de , seguidos de los valores restantes . Durante un extract-min, todavía podemos encontrar el valor para extraer en tiempo examinando . Cuando encontramos este valor en establecemos en nulo y luego hacemos una búsqueda binaria en para encontrar el primer valor no nulo e intercambiamos y .A i c i 2 i - c i - 1 O ( log n ) A 0 [ 0 ] , … , A k [ 0 ] A i [ 0 ] A i [ 0 ] A i A i [ c i ] A i [ 0 ] A i [ c i ]doyo UNAyo doyo 2yo- cyo- 1 O ( logn ) UNA0 0[ 0 ] , ... , Ak[ 0 ] UNAyo[ 0 ] UNAyo[ 0 ] UNAyo UNAyo[ cyo] UNAyo[ 0 ] UNAyo[ cyo]
El resultado final: toda la estructura se puede implementar con una matriz cuya longitud se incrementa con cada inserción y un contador, , que cuenta el número de inserciones.norte
fuente
No estoy seguro de cuáles son sus limitaciones; ¿califica lo siguiente? Almacene los datos en una matriz, que interpretamos como un árbol binario implícito (como un montón binario), pero con los elementos de datos en el nivel inferior del árbol en lugar de en sus nodos internos. Cada nodo interno del árbol almacena el menor de los valores copiados de sus dos hijos; en caso de empate, copie al niño izquierdo.
Para encontrar el mínimo, mira la raíz del árbol.
Para eliminar un elemento, márquelo como eliminado (eliminación diferida) y propague el árbol (cada nodo en la ruta a la raíz que contenía una copia del elemento eliminado debe reemplazarse con una copia de su otro elemento secundario). Mantenga un recuento de elementos eliminados y, si alguna vez llega a ser una fracción demasiado grande de todos los elementos, reconstruya la estructura conservando el orden de los elementos en el nivel inferior: la reconstrucción toma tiempo lineal, por lo que esta parte solo agrega tiempo amortizado constante complejidad de la operación
Para insertar un elemento, agréguelo a la siguiente posición libre en la fila inferior del árbol y actualice la ruta a la raíz. O, si la fila inferior se llena, duplique el tamaño del árbol (nuevamente con un argumento de amortización; tenga en cuenta que esta parte no es diferente de la necesidad de reconstruir cuando un montón binario estándar supera su matriz).
Sin embargo, no es una respuesta a la versión más estricta de la pregunta de Mihai, porque usa el doble de memoria que una verdadera estructura de datos implícita, incluso si ignoramos el costo espacial de manejar las eliminaciones de manera perezosa.
fuente
¿Es la siguiente una interpretación válida de su problema:
Debe almacenar N claves en una matriz de A [1..N] sin información auxiliar, de modo que pueda admitir: * insert key * delete min, que selecciona el primer elemento insertado si hay múltiples mínimos
Esto parece bastante difícil, dado que la mayoría de las estructuras de datos implícitas juegan el truco de codificar bits en el orden local de algunos elementos. Aquí, si varios tipos son iguales, se debe preservar su orden, por lo que no son posibles tales trucos.
Interesante.
fuente
Respuesta corta: no puedes.
Respuesta un poco más larga:
Necesitará espacio adicional para almacenar la "edad" de su entrada que le permitirá discriminar entre prioridades idénticas. Y necesitará espacio para información que permita inserciones y recuperaciones rápidas. Además de su carga útil (valor y prioridad).Ω ( n )Ω ( n ) Ω ( n )
Y, por cada carga útil que almacene, podrá "ocultar" cierta información en la dirección (por ejemplo, significa que Y es anterior a X). Pero en esa información "oculta", ocultará la "edad", O la información de "recuperación rápida". No ambos.a drer ( X) < a drer ( Y)
Respuesta muy larga con pseudo-matemáticas inexactas:
Nota: el final de la segunda parte es incompleto, como se mencionó. Si algún matemático pudiera proporcionar una versión mejor, estaría agradecido.
Pensemos en la cantidad de datos involucrados en una máquina de X bits (digamos 32 o 64 bits), con registros (valor y prioridad) de palabras de máquina ancho.PAGS
Tiene un conjunto de registros potenciales que está parcialmente ordenado: y pero no puede comparar y .( a , 1 ) = ( a , 1 ) ( a , 1 )( a , 1 ) < ( a , 2 ) ( a , 1 ) = ( a , 1 ) ( a , 1 ) (b,1)
Sin embargo, desea poder comparar dos valores no comparables de su conjunto de registros, en función de cuándo se insertaron. Por lo que tiene aquí otro conjunto de valores: los que se han insertado, y quiere mejorarla con un orden parcial: si y sólo si se insertan antes de .X<Y YX Y
En el peor de los casos, su memoria se llenará con registros del formulario (con Diferente para cada uno), por lo que tendrá que confiar completamente en el tiempo de inserción para decidir cuál va fuera primero.?(?,1) ?
Eso significa que de alguna manera debe almacenar bits adicionales de información para cada registro que almacene. Y eso es para registros.O ( n )X−log2(P) O(n) n
Ahora, ¿cuántos bits de información nos proporciona cada "célula" de memoria?
Ahora, supongamos (la carga útil tiene al menos una palabra de máquina de ancho (generalmente un octeto)). Esto significa que , por lo que podemos ajustar la información del orden de inserción en la dirección de la celda. Eso es lo que sucede en una pila: las celdas con la dirección más baja ingresaron primero a la pila (y saldrán al final).X - l o g 2 ( P ) < XP≥1 X−log2(P)<X
Entonces, para almacenar toda nuestra información, tenemos dos posibilidades:
Obviamente, para evitar el desperdicio, usaremos la primera solución.
Ahora para las operaciones. Supongo que deseas tener:
Veamos :StableExtractMin()
El algoritmo realmente muy general es así:
Por ejemplo, en el caso de un montón, se organizará de forma ligeramente diferente, pero el trabajo es el mismo: 1. Busque el registro mínimo en 2. Elimínelo de la estructura en 3. Arregle todo para que la próxima vez # 1 y # 2 sigan siendo es decir, "repare el montón". Esto debe hacerse en "O (log n)" 4. Devuelva el elemento.0(1) O(1) O(1)
Volviendo al algoritmo general, vemos que para encontrar el registro en el tiempo , necesitamos una forma rápida de elegir el correcto entre candidatos (en el peor de los casos, la memoria es lleno).O(logn) 2(X−log2(P))
Esto significa que necesitamos almacenar bits de información para recuperar ese elemento (cada bit divide el espacio candidato, por lo que tenemos bisecciones , lo que significa complejidad de tiempo).X−log2(P) O(logn) O(logn)
Estos bits de información pueden almacenarse como la dirección del elemento (en el montón, el mínimo está en una dirección fija) o, por ejemplo, con punteros (en un árbol de búsqueda binario (con punteros), debe seguir en promedio para llegar al mínimo).O(logn)
Ahora, al eliminar ese elemento, necesitaremos aumentar el siguiente registro mínimo para que tenga la cantidad correcta de información para permitir la recuperación de próxima vez, es decir, que tenga bits de información que lo discrimina de los otros candidatos.O(logn) X−log2(P)
Es decir, si no tiene suficiente información, deberá agregar algo. En un árbol de búsqueda binario (no equilibrado), la información ya está allí: tendrá que colocar un puntero NULO en algún lugar para eliminar el elemento, y sin ninguna otra operación, se puede buscar el BST en tiempo en promedio.O(logn)
Después de este punto, es un poco incompleto, no estoy seguro de cómo formular eso. Pero tengo la fuerte sensación de que cada uno de los elementos restantes en su conjunto necesitará tener bits de información que ayudarán a encontrar el próximo minuto y aumentarlo con suficiente información para que pueda encontrarse en hora la próxima vez.O ( l o g n )X−log2(P) O(logn)
El algoritmo de inserción generalmente solo necesita actualizar parte de esta información, no creo que cueste más (en cuanto a memoria) hacer que funcione rápidamente.
Ahora, eso significa que necesitaremos almacenar más bits de información para cada elemento. Entonces, para cada elemento, tenemos:X−log2(P)
Como ya usamos el contenido de la memoria para almacenar la carga útil y la dirección para almacenar el tiempo de inserción, no nos queda espacio para almacenar la información de "búsqueda rápida". Así que tendremos que asignar algo de espacio extra para cada elemento, y así "desperdiciar" espacio extra.Ω(n)
fuente
Si implementa su cola de prioridad como un árbol binario equilibrado (una opción popular), entonces solo debe asegurarse de que cuando agrega un elemento al árbol, se inserta a la izquierda de cualquier elemento con igual prioridad.
De esta manera, el orden de inserción se codifica en la estructura del árbol mismo.
fuente
No creo que sea posible
caso concreto:
montón mínimo con todo x> 1
heapifying eventualmente dará una opción a algo así
ahora cual 1 propagar a root?
fuente