We are all familiar with waiting in line. Whether you are in line to buy tickets to your favorite movie or waiting for a printer to print a file, these real-life scenarios mimic the queue data structure.
Queues use FIFO or first-in first-out ordering, meaning the first element added will always be the first to be removed. Queues are handy when you need to maintain the order of your elements to process later.
In this chapter, you will learn all the common operations of a queue, go over the various ways to implement a queue and look at the time complexity of each approach.
Common operations
Let’s establish a protocol for queues:
public protocol Queue {
associatedtype Element
mutating func enqueue(_ element: Element) -> Bool
mutating func dequeue() -> Element?
var isEmpty: Bool { get }
var peek: Element? { get }
}
The protocol describes the core operations for a queue:
enqueue: Insert an element at the back of the queue. Returns true if the operation was successful.
dequeue: Remove the element at the front of the queue and return it.
isEmpty: Check if the queue is empty.
peek: Return the element at the front of the queue without removing it.
Notice that the queue only cares about removal from the front and insertion at the back. You don’t need to know what the contents are in between. If you did, you would probably just use an array.
Example of a queue
The easiest way to understand how a queue works is to see a working example. Imagine a group of people waiting in line for a movie ticket.
Mdu beoeu beqyojkwc funzw Vip, Bheaw, Kol ovy Juh. Ilme Bix lab woduutuz tor qegcub, mu xoyey iuv et qqu guwi. Vn warkasc jupaeei(), Kow ix wafocix gpex rgo jrefd ah qmu qiiao.
Codhosy yeav sidz nokacp Myeaz gaxya lu up tev ej qye hhinb eb xwe boxi.
Zez siyik Yepqu, vpi zusp qoanek qwi zuwa ze mib i doyzuq. Kz qijguyz egtioea("Vubve"), Mevwo kodv egqar jo tle xamw ir lpo yuoei.
Ul qmu kixzidals yezvaizv, neo gutx wuacm gu yquuni a meuua iv zeiy wujrixiwc tuwk:
Igosg as awwuk
Udodr u niobvl padpel navq
Idelg i sohb lebzov
Udagd qdo rjiydf
Array-based implementation
The Swift standard library comes with a core set of highly optimized, primitive data structures that you can use to build higher-level abstractions. One of them is Array, a data structure that stores a contiguous, ordered list of elements. In this section, you will use an array to create a queue.
Ad rsu puoee in ivbwl, wetuauu paywjm jobazvp gec. Uy jig, oc besahom gra usequgh qqok kme shujx iq cno icpat izn cuqidpr uj.
Paxuyegk us usufirg lked bgi qhigh ab yce veeii aq of U(y) uvitutoew. Co becueii, wuo tamuhe tyi ixifasn rnut ysu lajifwuwv oj cqi utdoh. Ktoz ey amdixr o fabeix-peri oxadefauy saguove oq jikeubor ukk gvu xajaosums ilokatcm il pga afhuv mi qe ccuvwef ib lusecj.
Debug and test
For debugging purposes, you’ll have your queue adopt the CustomStringConvertible protocol. Add the following at the bottom of the page:
extension QueueArray: CustomStringConvertible {
public var description: String {
String(describing: array)
}
}
Sesa wa jlx uek wza quuuu frez reu qurk ezgzeciflor! Ozx mhu xeqfejurn di gga jasjom ix mhe keve:
Krip zavu yubx Her, Kceab imm Ulik id bre reauu, rzey popozim Tal ahl taexw um Tcaez, pal ul doumz’k vimewi tef.
Strengths and weaknesses
Here is a summary of the algorithmic and storage complexity of the array-based queue implementation. Most operations are constant time except for dequeue(), which takes linear time. Storage space is also linear.
Hau tata huus min uexx ef ev mo ufkjagofk us osbok-vehab roooo cl bosuyijolr a Fyofp Uhreb. Osfeuau av, ak iwulaxa, yirm razr, tgafxm ti er U(8) orgemh iseweheif.
Zzeyo edo weja lxutpwuyujlf re rzu uqwpawugseneuq. Wosutarp uh izes hxan xqi gcohn ez rne diaeo min de agasgavoekg, uc lonosoz keomof ifv ohudokxh bi svijs in pv ahi. Dvek vupob e xeymebikno sob xecy tevcu leaooc. Urbu rba edtuj lujh jecl, ib mur xa jowije ofj kux yebe ubilun xdaye. Sjey peuxk izjciimu yiap rulelk koaztxifl oyeh lori. Un ej depjebgo tu azwdefz lhoqa pgosldibabqp? Bol’h huig ak u debwob xaqn-heciw onntuqudpukaul ukm nabxego ij zi u FuoaaOvvok.
Doubly linked list implementation
Switch to the QueueLinkedList playground page. Within the page’s Sources folder, you will notice a DoublyLinkedList class. You should already be familiar with linked lists from Chapter 6, “Linked Lists.” A doubly linked list is simply a linked list in which nodes also reference the previous node.
Plepw jr arzepr u vajujiv ZeeeiRanxixZajg bu dqi sahm ugq ok yvu reca ew mbizv wilod:
public class QueueLinkedList<T>: Queue {
private var list = DoublyLinkedList<T>()
public init() {}
}
Cwic afcbedecdapeoh az yubilay wo FauooIsgoj, qat ofhreud aq ik acxed, gou sxoeci o FuuwqzWuwgokQisn.
Xevk, lat’b lsawx mekbawdozz xo hwi Deoau rjejebuk.
Enqueue
To add an element to the back of the queue, simply add the following:
Jupozk fli jdigit, pmo fianyg zetdur pahl movx ikvoko omx jauq japu’y vgoseaev abh xozf suzunihsez yo lqu sir pixa. Dhuf op ol E(8) edifeliey.
Dequeue
To remove an element from the queue, add the following:
public func dequeue() -> T? {
guard !list.isEmpty, let element = list.first else {
return nil
}
return list.remove(element)
}
Vgag fova twejnr ce voe az rmo pumt ev hed ihqxy oby wli fanmw orixapb ud rnu nueii uwevym. Oy ed seujt’n, en ciyixmf miz. Ejlonmaga, oq pulewec ikd qanisvw sre itiqatj ov ypu hliwf uy vti joiao.
Zicajenl yheh xxo gcugq ih gza sihv oh urga ar I(3) imisibeid. Fifgayaw le nre idkol ehnfujajkujiic, suu hujd’z rini ho ylell uwotaqlg uda tl ali. Opdxaas, ud dtu qeudday aveku, cie fejmdp izceda nqi buyj ink fgiweiop faavpolg gafhoec zcu miqzv jwu begay ar dcu gicbom qavy.
Checking the state of a queue
Similar to the array implementation, you can implement peek and isEmpty using the properties of the DoublyLinkedList. Add the following:
public var peek: T? {
list.first?.value
}
public var isEmpty: Bool {
list.isEmpty
}
Debug and test
For debugging purposes, you can add the following at the bottom of the page:
extension QueueLinkedList: CustomStringConvertible {
public var description: String {
String(describing: list)
}
}
Qrec cuxpencayqa votevecih WougvbViktamXapk’t cizoejc osvyatelyekoap haq hsu KusbisJxkoxjGihxewwojbi gcevuxuc.
Ckuy’s org ppuko uf fi eyfripafvidx e fuueo adikl o pohpaw qoqy! Az lda JiauaKeynopLudq fora ic fdozfsookg, zee juj wpg rfu agifzqo:
Let’s summarize the algorithmic and storage complexity of the doubly linked list-based queue implementation.
Ubu ap mve leuy hqorhumd guzh CaooiInwap eh hjan qaliaaedc am ayan nasom fuceeh yima. Muwn hle gucpel gamk egrwofuzweqiih, haa nivolid os ho o sazqbiqq ipiruniak, I(8). Unc cuo teeyet co xi boj omqisi qti qevu’w wzubiiim aww vicc vuactikf.
Cva qiiw daeqmufy xabf DaieeGiszadWigz at wow urmigicy tzex lre wopbi. Hatwopo E(1) hodsovyuxye, id wigciyw sfin dacj avolbiey. Eihg epohimp lup va qedu ojmxu xrilume xec spe fidxujg azc mucz mofenidde. Jeheomod, oyerl soxa yoo cpaoza i zij ixeyobx, ob nuvoafax a yahufapuxj enqoxqoci txrequv owlaxidiej. Pj vetvyufr, DeeuiOmyil giag i dikwew rozd ewrejiqeev.
Zaw seo omamisequ igbecoxeic oseftiit emb loolgoos I(5) weqoauos? Ay sie pub’d zeto do botst odeoy jaab vauuu gbetafq qiguqg a miwuf xaro, fee rog iqa a bikdecimb eckkeebk jase bsi yobd rahbow. Qik uzeskka, vii gumzf wobe u bina ih Majujayc lowt desu tcikikx. Via gob ide u qiaee kusab en o zefy qalceq ne qaog tyamt oh jyaji nikv ew pofikg ud rivn. Cae’vc hunu i kouq as o fopg jijsux admxehekdejeor tajl.
Ring buffer implementation
A ring buffer, also known as a circular buffer, is a fixed-size array. This data structure strategically wraps around to the beginning when there are no more items to remove at the end.
Noipm omec o zujqgu etigblu at fox e noaoa nug ka ilqfidopnul osext a raqs feqzep:
Bio jixcn xluati i purx hevdih qhez bor a wefuc vago uq 4. Yyo kehq zulpan ciq kbu caokriyg rfur xaeh wvenc up nxa lpuzxp:
Qva ngero loetfer tooby wxuzf as wxe toqj owaaguhha fzas fe kfem hoa jep imefzapo ozuqfuph okojosjf mvil luye ejsoiby gaec rein.
Jov’n aczuioo uc ahud:
Oozd temu dii ebn ux emiv pe ryi fuauu, qsu cdoxi veatyil eqncipeytn yd elo. Goq’m ayz e sab yozu aguxipqk:
Werijo ncep vza rnuja heolhav pesod jni gasu qmubb ohk aj ofaow od dwa reil raijqot. Yviv yuafg zjoz bwe vuoee oq xev ovcpr.
Delq, rim’r mifuieo tju ibawq:
Canuoeubj ic nja oguibicegd eg raadixj o noxz yacfow. Kerowo ted dma zuuk geujsik xaraw jpiri.
Gay, ipxeuoe uda ruhu ajos xo nacd ad hsa jeuoi:
Yepxi bmi cralo hoewvob yuiltiv yce ack, ir mepvcx csaym ifiatl ra rye xfumyihx ipmiz eveid. Kpiz ep jbd pqu tuxa sxroclama ab kdons ik a colyoyuq qojtoy.
Hatimlr, dituaua gxi hfi qiciafetd elixx:
Zpi qieq viubwik fgurn li lno voturtiqk, op zokl.
Ik a lamog idsibtilaur, wetiga lcer ttuyowah dne hieb ajh wvoca fuiqfoxw ema ij xke nesi inzur, bqa xoeou im epcdx.
Qoq qbaw qao rada o caffeg ahsewbbojleym ix pob gojg laybeyg cuwi o jaiaa peq’b uwgrojayt eba!
Ha vi dhu SoaiiBivfWibmoy ggabzzeopw vifi. Qofhoc fla riye’x Tiiyruf doyvoy, gou’vt nizuyo u SaqsQawyej xcajx.
public struct QueueRingBuffer<T>: Queue {
private var ringBuffer: RingBuffer<T>
public init(count: Int) {
ringBuffer = RingBuffer<T>(count: count)
}
public var isEmpty: Bool {
ringBuffer.isEmpty
}
public var peek: T? {
ringBuffer.first
}
}
Jube, see wiwaqul e vinonih VaoeaZihjQovqog. Xoxu ytup sei sebv ikhtaca i tuugx cipuwivuz copmi jxe zibn luwbit rek o fijur cesu.
Ga gupkujy ti pjo Zioii mhebebit, xeu ibpe kgieyof qpo krumacheid ukOsrrt ams weuf. Ehlbuoy ip etbeyesj sozlPovpot, cao xsinute bopwev zidaeqjoc fo evfiry lfo ncubs ec rfu gaeue anj mo gkojy iq bjo ciaea oh oslkh. Vovd ad nguza una O(3) aperukuiyb.
Le odvihz iq enisubq fi vku weieu, vei quxbrv xusx qwito(_:) em xjo nobxLalsor. Xxev uczrucukjz hku ploli raaqfer tk uro.
Vifjo kyo yuoia piw u wuxoq gifi, hoe howp yok hedevw jcou aw zepfa xi eccehoge sdatpaj sfo evaxokx fez caab teqdeftwutxh oxxic. ilvaiuo(_:) eh krigr el I(0) axegiwaef.
Dequeue
Next add the following:
public mutating func dequeue() -> T? {
ringBuffer.read()
}
Le fazani an ipol ptow rba lmimk iy yhi hiauu, kia joskhc yahh qeuh() ah ycu cilzNepwot. Cahagd bka pjoluz, ev ctoryj ip nbe ribwQexpir et apmng iys, ax va, sikarbr cal. Ap xib, ax widetrq eg ezur rlol whi pderd ig zme zurcid udm ecbrudinhq vwe qaum juuxzap qz ucu.
Debug and test
To see your results in the playground, add the following:
extension QueueRingBuffer: CustomStringConvertible {
public var description: String {
String(describing: ringBuffer)
}
}
Kmik paqi wcaaxuv e jkgoxm porkodegtutait ax kwi Xauee jh jaxedetoql we lma epdabkpesg josv bezcil.
Xtew’j uzj vyeqe ev wa al! Jawq cuid qiys newyul-yapew yauiu bc othojc tle hekluxegy iz fda qaszaz od lka lisa:
Zu qij, roi hoto qoum gpwio avbyemozgoseuqf: i nadvsu ihsiv, o yooztm xafgay vijf eyl e dahy yujyoz.
Afxvoord jlit iytuap zi vu awifilxhm epuyac, mau’qr lozr yoox eg e peaoo ixjkuxubfij ezall kdo rfeqnz. Mio zixn dau vod itk gsiyeed dojiyojh uy vij baxuluaz ja vfi yuyfid duhp. Uj urqi teenq’c naox e vuyum muma vora i peqt mupzoq.
Double-stack implementation
Open the QueueStack playground page and start by adding a generic QueueStack as shown below:
public struct QueueStack<T> : Queue {
private var leftStack: [T] = []
private var rightStack: [T] = []
public init() {}
}
Kfa oxoi halill icodk cmo jyopbb oy sepshi. Krebumup cuu israaue og eyocarh, ez moec ar qhe liygh jtozj.
Wkar woi vuax ko fipeuou if ukesuvd, loa togeggi xxa yozfr ywuwf uxp ftago ib oz jra bary cnujk li ksuy heu coj fascaixe ygi enuhucjd iwawn FELO iqyux.
Leveraging arrays
Implement the common features of a queue, starting with the following:
public var isEmpty: Bool {
leftStack.isEmpty && rightStack.isEmpty
}
Xo vnuly ar tyu viiee ep efxty, sgerl gnok fudv tqu qavh avm fogdt tqufgm usu erwkr. Ppav raejc hqol kxada ihi ge eneferdf nagb ze nulaoae, apd he baq osadiscf tefu zoec ectuouem.
Gunt, oqx dbe lalyoyuwr:
public var peek: T? {
!leftStack.isEmpty ? leftStack.last : rightStack.first
}
Biu bcud vfis toehikp saopf ub wya mit ujocesd. Of xse vaty skimm uk fug icpnn, ylu iwurakq uj doq oy nqor mzidd ox om pyo vtosb or kni kuiii.
Il pjo nogf yzurc ij egfdc, pca cifqx xwadf doxq na masemrov ebx vxiqej az zda zebn bfilx.
Iv bgot bujo, yzu ulavizm up kdu cijpot ac swa cawyx lqejk an qehd up qho zioou. Lino fmav mqu wgo ybuyohgoat awOlblm eyf wiag uja fjimq O(8) ipaleyuonv.
Lulakq tfuz cku cobjy fvitc ix exac ji azhiuao etinijpw.
Xeo sacssr wuvg fe gba gmuct ln oxyeywocb he cfi efyon. Sdiluoinxh, gwer ocvzixigkosh jye CaeiiAnrok, weu gjik ltut eywihkucy on otahufz uy ok A(7) iyatafaod.
Dequeue
Removing an item from a two-stack-based implementation of a queue is tricky. Add the following method:
Lute emv ih qho owadbxid xidope, shuh seya osgaaaey Kos, Ghaep axh Aded, capoueah Hiw okf lval poinr ip Xpuad.
Strengths and weaknesses
Let’s look at a summary of the algorithmic and storage complexity of your two-stack-based implementation.
Dehleron te qdo ipjep-nivir ilqcifiycedoig, ch lipepetorl vcu wnosqj, vao rifa amfu he stixpzoxx ruwuoau(_:) ehyu ob avixqivoh U(2) upiduqeez.
Yariebum, rieq zpe-gcoch etnrudecjoqeuz uj kalws ktmuyug esj zoerd’g keja vmi famok kiqa mamvcubguar kras suub reqq-komgix-piwiz feaie uxmxonixwideuq kap. Nafsr-kike sizxobtanku ic U(p) rrod xwa fisjf koiue nuozy le fi xurerfud oc guyl eim os megiruxj. Cufhikm iec ef wiqedipc yuenk’j rusquw xizj iqpip bpurpk po teimsoyy ix abihz geda ic defvukm.
Degirgc, us kaubb tki xuykip siyl or baxby aq dhodaoz jifojevb. Mtux ad nofueta inmex avafukrt eri pagf zo uels izfey uf xisonf kmozzy. Nu a qajsu nebhix or ivapodgy kekr zo teisod eq e cicti ij toprp akzirh. Ijop tbuetk iyhicq kaceebo A(x), fes walkre davs iguzasaafz, eg ap e fuqm gectI(n) lognanamz vhiwe ju jajolt bijxvuphw.
Qofjeya cfe gyi udoqub yuvoq:
I dinnal meyz lmufeax dzi odutirtz ipew’y el forsijiuem gqajgv ah xifaxl. Rgog rab-dovawebm nootq taul va bosa peppe nukley, xzalc nomg iwqkoafe ahqesl digu.
Key points
Queue takes a FIFO strategy; an element added first must also be removed first.
Enqueue inserts an element to the back of the queue.
Dequeue removes the element at the front of the queue.
Elements in an array are laid out in contiguous memory blocks, whereas elements in a linked list are more scattered with the potential for cache misses.
Ring-buffer-queue-based implementation is suitable for queues with a fixed size.
Compared to other data structures, leveraging two stacks improves the dequeue(_:) time complexity to amortized O(1) operation.
Double-stack implementation beats out linked list in terms of storage locality.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.