stringtranslate.com

Cola de dos extremos

En informática , una cola de doble extremo (abreviada como deque , / dɛk / DEK [1] ) es un tipo de datos abstracto que generaliza una cola , a la que se pueden agregar o quitar elementos ya sea del frente (cabeza) o de la parte posterior (cola). [ 2] También se la suele llamar lista enlazada de cabeza-cola , aunque propiamente esto se refiere a una implementación de estructura de datos específica de una deque (ver más abajo).

Convenciones de nombres

Deque a veces se escribe dequeue , pero este uso generalmente está en desuso en la literatura técnica o en los escritos técnicos porque dequeue también es un verbo que significa "eliminar de una cola". Sin embargo, varias bibliotecas y algunos escritores, como Aho , Hopcroft y Ullman en su libro de texto Data Structures and Algorithms , lo escriben dequeue . John Mitchell , autor de Concepts in Programming Languages, también utiliza esta terminología.

Distinciones y subtipos

Esto difiere del tipo de datos abstractos de cola o lista FIFO ( primero en entrar, primero en salir ), donde los elementos solo se pueden agregar en un extremo y quitar en el otro. Esta clase de datos general tiene algunos subtipos posibles:

Tanto los tipos de listas más comunes como los más básicos en informática, las colas y las pilas , pueden considerarse especializaciones de deques y pueden implementarse mediante deques. Un deque es una estructura de datos que permite a los usuarios realizar operaciones de inserción y extracción en ambos extremos, lo que proporciona flexibilidad en la gestión del orden de los elementos.

Operaciones

Diagrama de clases UML de una cola de doble extremo
Diagrama de clases UML de una cola de doble extremo

Las operaciones básicas en una cola de doble cola son poner y quitar de la cola en cada extremo. También se implementan generalmente operaciones de inspección , que devuelven el valor en ese extremo sin quitarlo de la cola.

Los nombres varían según el idioma; las principales implementaciones incluyen:

Implementaciones

Hay al menos dos formas comunes de implementar eficientemente un deque: con una matriz dinámica modificada o con una lista doblemente enlazada .

El enfoque de matriz dinámica utiliza una variante de una matriz dinámica que puede crecer desde ambos extremos, a veces llamada deques de matriz . Estos deques de matriz tienen todas las propiedades de una matriz dinámica, como acceso aleatorio de tiempo constante , buena localidad de referencia e inserción/eliminación ineficiente en el medio, con la adición de inserción/eliminación de tiempo constante amortizada en ambos extremos, en lugar de solo en un extremo. Tres implementaciones comunes incluyen:

Implementación puramente funcional

Las colas de doble extremo también se pueden implementar como una estructura de datos puramente funcional . [3] : 115  Existen dos versiones de la implementación. La primera, llamada ' deque en tiempo real , se presenta a continuación. Permite que la cola sea persistente con operaciones en O (1) tiempo de peor caso, pero requiere listas perezosas con memorización . La segunda, sin listas perezosas ni memorización, se presenta al final de las secciones. Su tiempo amortizado es O (1) si no se usa la persistencia; pero la complejidad en el peor tiempo de una operación es O ( n ) donde n es el número de elementos en la cola de doble extremo.

Recordemos que, para una lista l, , |l|denota su longitud, que NILrepresenta una lista vacía y CONS(h, t)representa la lista cuya cabeza es hy cuya cola es t. Las funciones drop(i, l)y take(i, l)devuelven la lista lsin sus primeros ielementos, y los primeros ielementos de l, respectivamente. O, si |l| < i, devuelven la lista vacía y lrespectivamente.

Deques en tiempo real mediante reconstrucción y programación diferidas

Una cola de doble extremo se representa como un séxtuple (len_front, front, tail_front, len_rear, rear, tail_rear)donde frontes una lista enlazada que contiene el frente de la cola de longitud len_front. De manera similar, reares una lista enlazada que representa el reverso de la parte posterior de la cola, de longitud len_rear. Además, se asegura que |front| ≤ 2|rear|+1y |rear| ≤ 2|front|+1- intuitivamente, significa que tanto el frente como la parte posterior contienen entre un tercio menos uno y dos tercios más uno de los elementos. Finalmente, tail_fronty tail_rearson colas de fronty de rear, permiten programar el momento en el que se fuerzan algunas operaciones perezosas. Tenga en cuenta que, cuando una cola de doble extremo contiene nelementos en la lista frontal y nelementos en la lista posterior, entonces el invariante de desigualdad permanece satisfecho después de ilas inserciones y deliminaciones cuando (i+d) ≤ n/2. Es decir, como máximo n/2pueden ocurrir operaciones entre cada reequilibrio.

Primero, vamos a dar una implementación de las diversas operaciones que afectan al frente de la deque - cons, head y tail. Esas implementaciones no necesariamente respetan el invariante. En una segunda ocasión, explicaremos cómo modificar una deque que no satisface el invariante para convertirla en una que lo satisfaga. Sin embargo, utilizan el invariante, ya que si el frente está vacío, entonces la parte posterior tiene como máximo un elemento. Las operaciones que afectan la parte posterior de la lista se definen de manera similar por simetría.

vacío  =  ( 0 ,  NIL ,  NIL ,  0 ,  NIL ,  NIL ) divertido  insertar' ( x ,  ( len_delante ,  delantero ,  cola_delantera ,  len_trasero ,  trasero ,  cola_trasera ))  =  ( len_delante + 1 ,  CONS ( x ,  delantero ),  soltar ( 2 ,  cola_delantera ),  len_trasero ,  trasero ,  soltar ( 2 ,  cola_trasera )) divertido  cabeza ((_,  CONS ( h ,  _),  _,  _,  _,  _))  =  h divertido  cabeza ((_,  NIL ,  _,  _,  CONS ( h ,  NIL ),  _))  =  h divertido  cola' (( len_delante ,  CONS ( cabeza_delantera ,  delantero ),  cola_delantera ,  len_trasero ,  trasero ,  cola_trasera ))  =  ( len_delante  -  1 ,  delantero ,  soltar ( 2 ,  cola_delantera ),  len_trasero ,  trasero ,  soltar ( 2 ,  cola_trasera )) diversión  cola' ((_,  NIL ,  _,  _,  CONS ( h ,  NIL ),  _))  =  vacío

Queda por explicar cómo definir un método balanceque reequilibre la deque si insert'o tailrompe la invariante. El método inserty tailse puede definir aplicando primero insert'y tail'y luego aplicando balance.

 equilibrio divertido ( q  como  ( len_delante ,  delantero ,  cola_delantera ,  len_trasera ,  trasera ,  cola_trasera ))  =  let  suelo_mitad_len  =  ( len_delante  +  len_trasera )  /  2  en  let  techo_mitad_len  =  len_delante  +  len_trasera  -  suelo_mitad_len  en  si  len_delante  >  2 * len_trasera+ 1  entonces  let  val  delantero'  =  tomar ( techo_mitad_len ,  delantero )  val  trasero'  =  rotateDrop ( trasero ,  suelo_mitad_len ,  delantero )  en  ( techo_mitad_len ,  delantero' ,  delantero' ,  suelo_mitad_len ,  trasera' ,  trasera' )  de lo contrario  si  len_delante  >  2 * len_trasera+ 1  entonces  let  val  trasero'  =  tomar ( suelo_mitad_len ,  trasera )  val  delantero'  =  rotateDrop ( delantero ,  techo_mitad_len ,  trasero )  en  ( longitud_mitad_techo ,  delantero' ,  delantero' ,  longitud_mitad_piso ,  trasero' ,  trasero' )  de lo contrario  q

donde rotateDrop(front, i, rear))devuelve la concatenación de fronty de drop(i, rear). Que se front' = rotateDrop(front, ceil_half_len, rear)coloca en front'el contenido de fronty el contenido de rearque no está ya en rear'. Como eliminar nelementos lleva tiempo, usamos la pereza para asegurarnos de que los elementos se eliminen de dos en dos, con dos eliminaciones realizadas durante cada operación . tail'insert'

fun  rotateDrop ( delantero ,  i ,  trasero )  =  si  i  <  2  entonces  rotateRev ( delantero ,  soltar ( i ,  trasero ),  NIL )  de lo contrario,  dejar  CONS ( x ,  delantero' )  =  delantero  en  CONS ( x ,  rotateDrop ( delantero' ,  j- 2 ,  soltar ( 2 ,  trasero )))

donde rotateRev(front, middle, rear)es una función que devuelve el frente, seguido por el medio invertido, seguido por la parte posterior. Esta función también se define utilizando la pereza para garantizar que se pueda calcular paso a paso, con un paso ejecutado durante cada insert'y tail'y tomando un tiempo constante. Esta función utiliza el invariante que |rear|-2|front|es 2 o 3.

fun  rotateRev ( NIL ,  trasero ,  a )  =  revertir ( trasero ) ++a fun  rotateRev ( CONS ( x ,  delantero ),  trasero ,  a )  =  CONS ( x ,  rotateRev ( delante ,  soltar ( 2 ,  trasero ),  revertir ( tomar ( 2 ,  trasero )) ++a ))

¿Dónde ++está la función que concatena dos listas?

Implementación sin pereza

Tenga en cuenta que, sin la parte perezosa de la implementación, esta sería una implementación no persistente de la cola en tiempo amortizado O (1) . En este caso, las listas y podrían eliminarse de la representación de la cola de doble extremo.tail_fronttail_rear

Soporte de idiomas

Los contenedores de AdaAda.Containers.Vectors proporcionan los paquetes genéricos y Ada.Containers.Doubly_Linked_Lists, para las implementaciones de matrices dinámicas y listas enlazadas, respectivamente.

La biblioteca de plantillas estándar de C++ proporciona las plantillas de clase std::dequey std::list, para las implementaciones de matrices múltiples y listas enlazadas, respectivamente.

A partir de Java 6, el marco de colecciones de Java proporciona una nueva Dequeinterfaz que proporciona la funcionalidad de inserción y eliminación en ambos extremos. Se implementa mediante clases como ArrayDeque(también nuevas en Java 6) y LinkedList, que proporcionan las implementaciones de matriz dinámica y lista enlazada, respectivamente. Sin embargo, ArrayDeque, al contrario de lo que sugiere su nombre, no admite el acceso aleatorio.

El prototipo de matriz de Javascript y las matrices de Perl tienen soporte nativo tanto para eliminar (shift y pop) como para agregar (unshift y push) elementos en ambos extremos.

Python 2.4 introdujo el collectionsmódulo con soporte para objetos deque. Se implementa mediante una lista doblemente enlazada de submatrices de longitud fija.

A partir de PHP 5.3, la extensión SPL de PHP contiene la clase 'SplDoublyLinkedList' que se puede utilizar para implementar estructuras de datos Deque. Antes, para crear una estructura Deque, se debían utilizar las funciones de matriz array_shift/unshift/pop/push.

El módulo Data.Sequence de GHC implementa una estructura deque funcional y eficiente en Haskell . La implementación utiliza árboles de 2 a 3 dedos anotados con tamaños. Hay otras posibilidades (rápidas) para implementar colas dobles puramente funcionales (por lo tanto también persistentes ) (la mayoría utilizando una evaluación muy diferida ). [3] [4] Kaplan y Tarjan fueron los primeros en implementar colas dobles catenables persistentes y confluentes óptimas. [5] Su implementación fue estrictamente puramente funcional en el sentido de que no utilizó una evaluación diferida. Okasaki simplificó la estructura de datos utilizando una evaluación diferida con una estructura de datos con arranque y degradando los límites de rendimiento del peor de los casos a amortizado. [6] Kaplan, Okasaki y Tarjan produjeron una versión amortizada, sin arranque y más simple que se puede implementar utilizando una evaluación diferida o de manera más eficiente utilizando la mutación de una manera más amplia pero aún restringida. [7] Mihaescu y Tarjan crearon una implementación estrictamente puramente funcional más simple (pero aún altamente compleja) de deques catenables, y también una implementación mucho más simple de deques no catenables estrictamente puramente funcionales, ambas con límites óptimos en el peor de los casos. [8]

Rust std::collectionsincluye VecDeque, que implementa una cola de doble extremo utilizando un búfer de anillo ampliable.

Complejidad

Aplicaciones

Se puede utilizar una cola de doble extremo para almacenar el historial de navegación : los sitios web nuevos se agregan al final de la cola, mientras que las entradas más antiguas se eliminan cuando el historial es demasiado extenso. Cuando un usuario solicita borrar el historial de navegación de la última hora, se eliminan las entradas agregadas más recientemente.

Un ejemplo en el que se puede utilizar una deque es el algoritmo de robo de trabajo . [9] Este algoritmo implementa la programación de tareas para varios procesadores. Se mantiene una deque separada con subprocesos a ejecutar para cada procesador. Para ejecutar el siguiente subproceso, el procesador obtiene el primer elemento de la deque (utilizando la operación de deque "eliminar el primer elemento"). Si el subproceso actual se bifurca, se vuelve a poner al frente de la deque ("insertar elemento al frente") y se ejecuta un nuevo subproceso. Cuando uno de los procesadores termina la ejecución de sus propios subprocesos (es decir, su deque está vacía), puede "robar" un subproceso de otro procesador: obtiene el último elemento de la deque de otro procesador ("eliminar el último elemento") y lo ejecuta. El algoritmo de robo de trabajo es utilizado por la biblioteca Threading Building Blocks (TBB) de Intel para la programación paralela.

Véase también

Referencias

  1. ^ Jesse Liberty; Siddhartha Rao; Bradley Jones. C++ en una hora al día, Sams Teach Yourself , sexta edición. Sams Publishing, 2009. ISBN  0-672-32941-7 . Lección 18: Clases de matriz dinámica STL, págs. 486.
  2. ^ Donald Knuth . El arte de la programación informática , volumen 1: Algoritmos fundamentales , tercera edición. Addison-Wesley, 1997. ISBN 0-201-89683-4 . Sección 2.2.1: Pilas, colas y deques, págs. 238-243. 
  3. ^ ab Okasaki, Chris (septiembre de 1996). Estructuras de datos puramente funcionales (PDF) (tesis doctoral). Carnegie Mellon University. CMU-CS-96-177.
  4. ^ Adam L. Buchsbaum y Robert E. Tarjan. Deques persistentes confluentes mediante el arranque estructural de datos. Journal of Algorithms, 18(3):513–547, mayo de 1995. (pp. 58, 101, 125)
  5. ^ Haim Kaplan y Robert E. Tarjan. Representaciones puramente funcionales de listas ordenadas que se pueden clasificar. En el Simposio ACM sobre teoría de la computación, páginas 202-211, mayo de 1996. (pp. 4, 82, 84, 124)
  6. ^ Chris Okasaki (agosto de 1997), Colas de doble extremo que permiten la conexión, ACM SIGPLAN Notices Volumen 32 Número 8
  7. ^ Haim Kaplan, Chris Okasaki y Robert E. Tarjan (2000), Listas catenables simples y persistentes confluentes, SIAM Journal on Computing, vol. 30, núm. 3
  8. ^ Radu Mihaescu y Robert Tarjan (agosto de 2003), Notas sobre deques catenables en Pure Lisp, Princetown University, COS 528, otoño de 2003
  9. ^ Blumofe, Robert D.; Leiserson, Charles E. (1999). "Programación de cálculos multiproceso mediante robo de trabajo" (PDF) . J ACM . 46 (5): 720–748. doi :10.1145/324133.324234. S2CID  5428476.

Enlaces externos