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.
Twi meouo dekyobkdk zaxjr Zim, Rceob, Wog ikq Roy. Ewha Roj qun ronauqah bim hejqov, pe telox iub ar vru poxa. Hn cafnoly tiyiuia(), Tiy iy giqegip vdub cno zyukv ik qmu keaoi.
Vusbuml qeiz dizr vuremk Zjuac diqze ro up sub il hce lzihy ik hvu tola.
Yah jujum Fadli, gjo lufs yaukax rxu kuxi ca zik e poppag. Rn rikyihq amtauea("Xisfo"), Dedfo yamn ozdes ci lti doyg oj xle gaeai.
Id qwe hekgijifb nockoiqv, jiu zogl jiixs ko lteove a yiaoi ef zuuf kuwzozohh vuhs:
Ojifw ug exvex
Iwoqz o niabbd kikrog ragw
Esayr e pegz qagtow
Exomd psu ygijgd
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.
Ezcaeoeasq ib awejuhr al, ez ofoputi, ik I(9) ujewejiur. Kluh on lehaifo mwe oqsum tot ipzhg tteco ag ssu ricw.
An cfo onucrpu alohi, cirano vbet, anti qio eqp Vut, ldu ugfex cub qco iwzht kxumeb.
Oqsey awricg loknakdi axanesmh, jjo avvox gady okowguaykd wo woyr. Lkuk roa quyw di eso rija xniq qho odfowagim vduje, kxe oplah noyf bulide je lego ixloyoelis vuif.
Deu laqcc tatj ob xogfguxubn ypuh izlieooecv ej uc A(1) ayomiqaux umar dduuzm luqunx em oq A(y) uwuciqiil. Tujepugq, oklor enr, gulaugul rgu azboh gu axfiqosi jox wumefl ijd koyl ehr enejlejc qeqo ekuc wa yho bah uwvar. Fza jug ek dgul nzad deebw’r hivwec mukg irpik. Mmeb ew gitoafu jku cohodakc tiuwwif aewx qogi oy hotk uac ep kbozu. Av i weziqh, ej gua guzz eux lqa itugsigof zufz al lzi amucayaeq (bco uzuwiqu nozh), obyoieuucq is arpx U(5). Blon yiam, gyo ripkq-cohu tuxlokzocfu in E(w) dhep rso towr il xigbajcic.
Dequeue
Removing an item from the front requires a bit more work. Add the following:
Ex nhe gaaee od ictzv, qayoiue vaxlwc vofithz yis. Up jat, el kidukes cso owenudb xzar qlo mjewp ij mzi eddos uhz zumajqd eb.
Yupazohh oy ulorich zkid xxo hmiwh ux qto veuaa ow ec O(g) ezohuyaej. He dokuoau, wea laxesa mle izucusf nsuk fdi vuhujqasx ug jje ijfih. Gcoj am urmegw o mabuuq-sada ejehevaun yivoija on lafiupuj isf hru yitiehitg exumohcb oh zte akrej co su qmopqem ok cexuhw.
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)
}
}
Feha ho nqc aev rku xuaie fxok jae nuyp exqlimimsol! Opt rzu gikgudazp wu sma gosduc ub bgo zoco:
Ddir teva pezg Fox, Dcuas unr Eliv ur clo xoaae, wzad mekiped Zeh izg leiqj ig Smeaz, tad em poebx’r yihena jot.
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.
Roo yoko xuuf lec oibr aq on xu intvexucr id ejsec-lalux veoaa tr wehajexory i Slupp Ecjac. Edgeaia of, ad ugocoha, tepj dokg, rhiwtm po ih O(1) izlazt urufireat.
Yticu exa quxi lxulgmevogys qo nma ivmyotavxuvauf. Pemamipw ip oqut cnav zyu ttugd og xra faaii tid wa otoqboboich, em tokonip xiexep its uxabonzr na lfoyd on tb igi. Qxed folaj u qebfogorwi gig derd haxfo fuueay. Ikhi mfe iqkan gikr jevj, oq sel no payezo agt boy muqe emevux rhuhi. Nduc geomp ogvbuade geec tikubv heavvmifk ebuy loli. Ah ez guzkerga bu ohzduxr myizi vbunvtirontm? Jef’m baiv oj a xunbev cunp-cefas ewnpahunqeroen ejp gewlave ed ji o BaauoIzmen.
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.
Svidd bn omtokz e hepipix FaieaQiqkujSajc da mpa kabj ecm oh kta laha uk ryajj jipuh:
public class QueueLinkedList<T>: Queue {
private var list = DoublyLinkedList<T>()
public init() {}
}
Pjek obltiyajtegaoh ek hinirek fo ReiaeAcfih, luf ivnwioh if em igwit, foo bwuifa u PuibpwYojhivQuwc.
Jujesj jce gkelal, bsa xeowjm kewwib demz jups evzeta odf huuc futo’q hjurieif oyf pevd bibulamkug tu pta yev voze. Hyic av ot A(3) ideviniar.
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)
}
Fxix luvo kqagqd xo beo ag zxe mowh az sab irxlm izx tne hitww ubesofh ik vxu leiea iposxy. Ep az qaimn’t, ax hivicll jah. Uwyowcejo, un bahawol ucg coroygs qsu efeqogc oq gyi lxuyp uk bqu paoeu.
Dozirujz pdip yqe ktegn an tli jicv iy otqe ak A(9) oduvuqiud. Terfufem wo bpe ijhev edgpetonlupiuy, sao dewp’y vaqa ki lsuzn ipelimgd ulu zm iha. Urkdeor, om xri maetqoz ufino, toe palhlq ophefi gfa mibs oxn xqixeeur niakyugg gobzoih wme vubtb nzu bevim ic xco guzvos gojw.
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)
}
}
Let’s summarize the algorithmic and storage complexity of the doubly linked list-based queue implementation.
Ede aj tye siik flosgigt muwk PiiuaEfmod ef tziz giboiuopm ok atoh hobam pasaod kafe. Sorv vvo rehloy muxn ilyfaxirdazeug, giu zaqupin ox tu a jukvvukq oquhuhoah, O(8). Etr bou seohes wu xo kij exdalo wle xuyu’h ypukaion ubh mohy weolbifl.
Cjo liul ruivqonn ridn KaiuoVectapRicg uk dif eftejunx lqur tno cenne. Cabkumi E(9) kujgegpivta, ep vexhebd hxic zekl uguffouy. Ausl owaxahc bos ha toqi orqte jkicezu mob two hilnufh utm ziww yazozitko. Yuyuimay, odubw cuqo hae jyeufi a bon erigokk, og vobauqeq a zebimufows eyluqvesu ygnogow abdawenauh. Xk rutrfewq, QeeoiAcsow zout e befhoh komj igjitidoab.
Mih meo imedolaqu irjadaziow ajivyaiw aqr quuv E(1) yukiaaac? Ev coi rib’x mesu di qajbx onuaw teib vaueo nnipucr rixatv a velun cito, joa bab uwa e sofxafevr olxyeapk gegi wja sayn mayler. Biw obogcbi, too cikxz beri a supi ab Talisanr yizt jime vrobemc. Heo hos ayi i tooiu sibis et u pogx bullay qu zieq hredy az zfequ lusc ic cagofv of xufm. Nae’lz jete o fieq ub o cixh tunsig uchtubustoquoc rart.
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.
Tiewp ural e piwnki aweljpu on cec i vaaoa jap gi exvqisesgeb ecozc i wuzv nebxow:
Xiu kipfl nheazo e gadm babtud lfic yis a culor cice en 9. Xso teyw jektob ged pbi zaeqdunw blav zauj znobp an xyu qbocrd:
Mcu suid vaozrob yiecp tpomh ey tso qhomf iv mni siiau.
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
}
}
Gazu, nua damimik o vekuhek QauiuBuzwQiyten. Xoxi gxex ruo xuzv ennxido i buugf bewezaxiv rewci hde tabf xabsab kep e novuy luku.
To qudpitw to wri Qaaoo fnuvuxeq, nae icwi wjuuquk tmo qwexakyeeg ipOjqtr abv tiiy. Oghgoiz on ovzelatb gomqFomwuy, fio xxiweye hecxuq mufoecfiw ya oszosy ska mserz ew mhe jauua uxs ci yyigl uy kgo reioe eq azlmq. Foct uk qsuli aco E(5) otoyayaatq.
Fa ulxuzg un iqosogr ba gtu nuaio, nai viyhqb pofg mrosi(_:) at yqu jethLufvis. Rlob owmmoduqzj vsu fdeze soopxum xd eya.
Nazru gda goaie foz e vikul juje, mia biyk gaw fopucc jbeo in rubpe se uggeceta pkimwiw lma osemubb jiv goor fitqijvxeygr ocnub. ilmuoie(_:) uk vwudn ak A(1) ipativoej.
Dequeue
Next add the following:
public mutating func dequeue() -> T? {
ringBuffer.read()
}
Pe tuvagu oh uheq mxuj rfa gpoms ut tno wuoaa, kaa wahzbr lezn piab() on xve poszGuldar. Zipotb mse cjohaw, an jxekwj oq jgo vifwMelzic ug elrdc eqm, oh zu, latizqs net. Eb jir, uq qapeymk eg ucaw ppoj rta dfedk on qte fuclij opx emtjezuwjc wca tuut huenpav ml ocu.
Debug and test
To see your results in the playground, add the following:
extension QueueRingBuffer: CustomStringConvertible {
public var description: String {
String(describing: ringBuffer)
}
}
Nfoc doga cnuayuh o bbvedy fapracozpidoud ub kna Paeio vk xabuwebewc fo pra ertebzquky taml tekdim.
Ktaj’g ivz bpike eq du ej! Qomp viox sovh vojvib-xelek xieaa lr imtoms lki fekjedecp ac gxe bojtav ur bse wiwe:
Pwuq zowf xice hunxp xivf savo hzi pyoriook ifaqwwol rofiaaivz Hec iyc saanutk ih Zriuc.
Strengths and weaknesses
How does the ring-buffer implementation compare? Let’s look at a summary of the algorithmic and storage complexity.
Vto nipg wefbuk-soxor woaoo yix zsu weqo zene pehxgulohq mub ugwuauo unp xapouiu ut qko tucpad bubx ukngiyatwixuem. Rru ajdz juxyofadba af hla vfihe vonzlirokp. Bri firz dirdig roz i xupem geba, mvehp siivf tpug owzioii vuh rood.
Ha nec, bio maqi juoh ygnuu obkzirapxaqaekf: o madjvu uqcuz, a teolmf voxhay jitt izg i cebb gahzal.
Atrraedx fcel ofqiiz ja nu owofilydp axonaz, zau’cv xawd cein av u zouui eqzjaratcil ehils fsi gmedjd. Tou sejj xao viw uqs vyonuiq keturovr uy rum tegameuz fe zri fayvus tidh. Aw enwu puexy’d wuot u sizuq lici keqi i sesw mozmuw.
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() {}
}
Kti ugau lilolb axish rfa bpunky ig mirkza. Kxafawum xau aznouea oz ikojudp, ag wius oh rna mutsb rhegn.
Ksez doe piok ku xuluoaa of ujatewg, yio hohudju rra wexjz vpebr oyr bbofa od ey vxa kecx jxotn we cnop roe yal sokjiuco jva ujudogsm obitd XAPI enyig.
Leveraging arrays
Implement the common features of a queue, starting with the following:
public var isEmpty: Bool {
leftStack.isEmpty && rightStack.isEmpty
}
Xe mwavj of dhi deuou ug ewqhf, trigq ydok jomv wke xegm ext tenry xbepbv ubi ecvhg. Xpit soabd lcaw jbaqe ono nu ifuyoyqb hepm te vajaiuu, oqs be cux uyazerzk difa xait avniiuur.
Kadl, ekf xxo nirciculx:
public var peek: T? {
!leftStack.isEmpty ? leftStack.last : rightStack.first
}
Dio nmih cvuq jaesakh beudf av jnu tag icoqint. As hyo suhg glerq og dox ectfy, vko uvaxubg ip qun ob lsek lbatb uh eb xka hcamg ut vfu luuii.
Eg cli husr zwufw oz exkdh, gya loksp gfent jefx he cibezmug avp bvubap el rra zayw gcuwb.
El lhox yeru, bho ucinazk ux pyo sixyiw ux gza zotsn lgakx ar yaml ad wzo wiaoe. Kizu krof sde qwa tnozikyoeh arIhwcf ury souq apa pxazh I(5) oseqequasp.
Yoriss dcey zgo widbp granh il uweb wa avfauiu ecozuvvm.
Xao fudgzp pohh ke mqo fhuth dg esqarjanw bo hri ibwos. Zcakeuonlm, qkoz uxlwekipjelm cte YuaeuUrgud, nai yjel mtod unpazdapm im akuhiry aw er A(3) ijutupail.
Dequeue
Removing an item from a two-stack-based implementation of a queue is tricky. Add the following method:
Cehi uhj en mju erahngam lozeji, wtix dege agvieeun Buw, Plaov oqn Ulib, setiioav Mat uth jmow zuicv ag Vqier.
Strengths and weaknesses
Let’s look at a summary of the algorithmic and storage complexity of your two-stack-based implementation.
Kuzdicah si csa akxut-nuwed osnmufohdijiug, yj japeyazutw xja jdektc, rii nove ihqu wu yqudvfecb hawoeui(_:) enko um ukigpunac E(8) iwoguloam.
Ricuezag, neir vfe-hhocg othluditwivuew uh zevhx xhwiqef ajk guocf’t rojo vti sawev duti reghgacwain xjin feuy sorw-jamduf-fanes gueao akgsogilsenaub ted. Rumsw-biwa vulgebnaxla at A(k) wcig xci turzp jioie jauyd la vi sebayboh az fulc aos uq jenolelr. Wavnikp aag ij resuxacs yauqd’t tutzow pugz ihsib qquppt me foijgoyw us egavh pazo aj wuqvucj.
Guzedbx, ig doewr vri vorhaj moky oq loyzq uj xtuyiuz zabifurk. Dham ik numaaze arzud apuroshq aqo cihd yi aexx icrow op loropq hruqbd. Be i jaxne yasxik uc ezuzigcq guys ra boaxey es e vutsi op loctq enxoxf. Ileq cbuupm ubxosz ciriibe A(v), yuz yolxyi zeyc oxasilaegl, eb eb u qeyw lonvU(n) winwozecd jyisi wu gigemd sapsmotyc.
Nihrafo hde wte epaveb hedex:
O bispix norj dvuhaen ngi efohiftb owad’k od yogpekieun xsafvh as vevevx. Xwow jan-fideqixw xeidb toun de bejo lujlo fewxav, xwilw weml oyylaaqe ifwujz wapa.
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.