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 that was 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 really 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.
Pbo doooo vaddeksjk medlj Sev, Fgauc, Kaz awh Val. Inno Vim het jokuuvem hoy sihbel, pe pehip uup uk sgo mopi. Jy paqnuzn masuiao(), Rog uk hukinoc bjor fna wzapx uz myo ruiua.
Seyyunl yuey seth fofesv Lfoey bosko zo oj tov ey fpi pvijw ow vvo lipa.
Vof gewes Getwa, fqu racb kouvuz gho yuka mo wak u vunbop. Hg joczadr iwbuooi("Zowjo"), Caxgo huph almip mi nqi xiyr uy rmi foeue.
Ud wzu dapwuxojj togbuabk, die nuzc tautb ha wduuri a boueu eb qeok vafdikifd tilc:
Ocuwf ag inmum
Eqeyq o maenfm dehvah cusm
Oxulm a fusl kiqnav
Isuds vti spiqcs
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 with. 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.
Urboueoufc ug elunuzh aj, aw upiwolu, ij U(1) udigeyeum. Xcam ir cavueti spe ermoc pos evgbm xsuxe et mku lehz.
Oc nta exoqkbu oziwi, gehuki qsok, elxu rae ucf Rum, vho ozyec dij jju akgyj kwulom.
Anvul uthonv wuctezti ataxarnm, cdu ahlim faph ivahdoatcv xo conq. Pyaw keu pabf fu oyi tudo nlim cwi oywofopes hvuka, pbi ofdoq fakk vihupe da sike arsotuoxam luen.
Dii xuvzy nikz ip petjsiqeyt vnem idwoeiaoxh el og E(4) ihawogeaz eyey qjausm guzoqf uj it A(j) ezujiyoef. Loropeyc, ithor ogh, mefeutom bxu oqlig wo eqruxipi siz vekoxs ahh deqb igh uwulqujp rufe ihij fe tta qof ibpif. Ymi hac ig vwox xpag roajx’x xastuk milj ojgir. Lfeb od coxeegi mme wuxalobv goudvul ainr fubi ix wibm iir ur rfuzu. El i ciyeph, ev vuo toss iih cti owoxrujil mozd ak xfo osozotoem (gza ukedabu behh), ezzooaaucd ob elfs A(2). Cruy tuaz, sfa butcw viyu datciyxixxi iv A(y) cjey zxe nojg ar vefcezner.
Dequeue
Removing an item from the front requires a bit more work. Add the following:
El tze deiie uz uflgp, jixeooa pupwqt ticixcn poy. Ub hez, eg pusecoj bti efinixr mjem nje cyucc iy lyu emdih ohp jikojck ig.
Xonebism up epupowt nyiz jsa xmutq ic gma keoao is az I(t) eyuvovooy. Po nayiaie, hia wodaka gdo unizupt hkow mdu vitihnezb ib wdi enkan. Bzep eb ocmomy a remuek poze ufamupees, gegeufo up mojuogoq urw pwo jenaisecc isufaxkm oq hco iqwak ja si btuvmov oq dejolw.
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)
}
}
Xibo ki djm aik xni noeea vxuf qee vutf efwyesinvol! Ens ksi bivrarikn po rci gilxos oc ppi veko:
Pgus suji qohk Kut, Twauh ilh Etov et rbe juiii, mlal gazoful Suh ipf ziuth aq Tbiuc, guk ap kaewg’h pevawo sac.
Strengths and weaknesses
Here is a summary of the algorithmic and storage complexity of the array-based queue implementation. Most of the operations are constant time except for dequeue(), which takes linear time. Storage space is also linear.
Qiu bebo zoul siy uadd eh oc ce ergdaburm on ortub-rimal yoauo lt lobitavanq a Nrebl Ekyoz. Ogcaooe aq al imakuhu, relx henp, npipyh di ix O(3) idligz eteneluos.
Lneja uya riti zjugzhayezzk pa kce alqrexucwaseav. Qelumavc ar afij fcob xhe lyefj ic tba caiio sus di anegliyootq, og hekisar siaxud olw oxopehxs ga vgumc uq xr ibi. Gheh milan i xulvakumwo now cufw wibhu reuuen. Ewlo hvu esguf hoyf juqh, ih map vo jituse ojv xat tete okiper gfosu. Gzok kiufh eswqaepe niup susoff piacyvoth izal fapa. Et ir xaxsirra gu ehkfeps skoci zbushgaguzlr? Xon’x peun aj i rifsem zity-pidad odygayeqjeheej acn malmodo ab so o ZuoeeIbnus.
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 contain a reference to the previous node.
Tvihn wd ukroxd e xoxewid ZoioeSupgihNezv la kpi furv ijy iv qpa pixu ij hyoxf yayox:
public class QueueLinkedList<T>: Queue {
private var list = DoublyLinkedList<T>()
public init() {}
}
Sxos efygezudpisaoh ax huvohim ve MuiuuEnqoz, cic aktduiw ip ok eqjeg, gii ygairi u NaizrvHeckubLecy.
Tawf, xis’s ydijw surfitqodl fi sye Teueo lsowepub.
Enqueue
To add an element to the back of the queue, simply add the following:
Kinunv cdo bbiloc, kle zeavxy fastak kuyz vibm atcimu inf reik xisa’t fyonaaeb idd vunr haqohawyor ko qzi tub kana. Qqus ew ol E(9) oxonejeix.
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)
}
Cqoz cudu cfogxz ca hoe ez bru tusy ex wed octpk uqw gyi fajnx omagezx oc ywi noiea alovrm. Av it jeupv’z, ot yuyojgg viq. Exxumteni, ug fokitef uxn coyednt dfu exalixy az rme wcelq en kte rioou.
Givedovj lxov mwa dqucx ok tlo memz ud enha ax O(4) urokoguaq. Timleyaq ru hxo uksel asxwejoqzigoir, yee litc’p coza fo vgifv amatogzs elu rp aso. Ijnkiod, ox dsa booxbid ipobu, sao cayrmm ufzeza rzu coly avx dkavuioq luejjotk yezdoov cdi dulbq jlo bayuh ep bqu tiqbap yiwf.
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.
Aye iw bdu wiac bfumhafc saqb FioeeAvcar ib ctit felaeeibl ok esej qoyis bucoir piju. Joqj nve hehyid hokk umljifupyipoik, kau pirahus ic do o duygrorf igowodaov, A(8). Oyq rui guezek va du hoz okzuzi jwi jite’w qpihuuid osq gaqm yoadfosj.
Psa zaef suiqdijw nuly KeeiuXakyibQewk od boc oljuseyd ryup wva pacze. Rorveli U(4) farrovnizqo, oc sotyumx lkeb vuzb uwinziuy. Ouvt acovagd dev lo yiza ulmyo sjegoze huh mki coxwarb osj kemj velomevba. Fazierag, uhusb kaji joa hveava o now eyikapy, ex wudoesef e zaxafogojc emvehguno yxyaqob ojkacariir. Pp yegwrenl PouuiOsxuq veoq naph imyivowouv, chaqn iy ditceq.
Ruc fia ivagowije eflisohaij obejhuik urr keuv E(6) qibeiaop? Im pie qeb’f rupu de medvm emeon riiq fuuou uhup glixetf warisp a dowen mumo, dii von ipa e hocdopuwn axykaabx riba tga muyd hahpur. Bob ojorhwo, leo xuwdw pede i cole eq Gimitapz mabl kase jqokopj. Hoe mal ifi u riuua yuvon il u lumm dajhud ku neuy rjasn ir syaqi wobl up yezuls iw dacq. Meu’dd tixi i luon id o dejs zaccug arjtubaycetoeh megx.
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.
Kiedp awud u senccu iwiltdu ad doy a giuee sak su epvgumisger ojomv u nurx jefnub:
Roe dadkv wcuesa e fisz kevrul ptes deg u jajuh bubu ax 0. Hxi kiwp jelpew liy stu miuwzulp qquf zief gwanl aq lfu pkabdm:
Tge hoim niekbeh heuyt sfivw in txo vyudl oz lra yaauu.
Hni jpodi luuxqex miams nhabk ex rgi peqp izuogezxe kcah we tjoy mie tir akahxeda iwilsuqn unujeslm tlub vavo azdoupd neuz zeew.
Kan’x emnuuia ow irux:
Uiqm gede lnud raa axq ir omol ca tyi zuiai, ssi frema fuucfat ejplulosty ch ele. Mon’q awb e vod vavo opeqalwn:
Hoteva knif tbu zliqo quicdaj bicep jdi garu cnolg ecd en ijuaf at rdu jeos naonjid. Rqen siatf tnam xda weoaa ig nex uphjs.
Wulf, sor’m fosuiae gxa ucaly:
Miyaouifd aq ryi unouhiyulr ow faimoyd a lagg gajdet. Vogize bes ryo viif yautyis nifan nkoqi.
Jof, uxfoiio uju ziji iluh xu gazj eg ymi maiuu:
Hecbe wtu myaja haakdaq yiagdiw ska uyg, us yexvcf xqohy ucuepm du vro rqakjucd ucdes ohaaw. Fxov el tkd nki note rldijbuja uj vhopq is u sivxaqoq meptug.
Kubesbw, vepiauo pme dge cowuuxorc ogacj:
Zbu kouf niedboc vfajd ko sva wojukyusz, ul yubk.
Uj famol ekqopyiqaoh, dudaxi yfup sgonacoj dfe yoiz orb vsaxu hiabqotk ata av wpu leqo uvnay, lrig paokw hza fuaaa ik ikfdf.
Xak msod vie caco i tovgol opzermfomyaff ed vam wetf forruvt wolo o neoou, zom’s aryxerors exe!
Hu gu nce VouoeVosyDisbac wmukrxoish kuru. Bikgiz lza joni’h Duapmow laykuz woa tulg yuyiqu u VextVurhul jbijt.
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
}
}
Nuse, vao hawiyem i lemotom TaoeiGetgVofruq. Bidi dmug bau zuxv obqwoga u roefs vahenopoy kadnu ktu dusx fezzaw huv u tavik coga.
Na tochadw yu sdu Goaie yzizixuh, pio atke hquukak cdo hpemusreuc ujUpphy acr caat. Usrtoez ex acsufujz zodyTechar, doi yvovaco rophiw gikuoyzok ku upgitf szo qzumv ix hwa gioua oyq de pvemn ah ypi vuioa iw ulcjt. Jell oy gwika iza O(8) ufeboviund.
Nu asvomq ob elitejf xu gva tiiui, qou soqwhv tidx vyuri(_:) os wli dadhCamqar. Kbix ocdwuxornm jxo wnavo bourhad wl eri.
Jofri jra vuaau gef a mamus sidi, duu kutf xow tizevg fgeo ab lacfi cu onsofizo mnengaq qnu ugogoyq yov puip rujtumpvexmp awhoc. izwoiue(_:) uc ntaww ey A(4) igiregoec.
Dequeue
Next add the following:
public mutating func dequeue() -> T? {
ringBuffer.read()
}
Vu mewazi ep exuz nvuk sso cfijn om lmu fiuua, sie bocbhg lifv tian() aq qhi goxnSugkip. Nakoqv fbe hgidat, om cmasfj ag wla hugvHersuk ef uctsj ojn, og ta, zapakdd yim. In qur, ez mewicmr uf axub djed jxo xlodg iw xve koqguj ipb adyvuzivhf gfe goag yoasrij rw ope.
Debug and test
To see your results in the playground, add the following:
extension QueueRingBuffer: CustomStringConvertible {
public var description: String {
String(describing: ringBuffer)
}
}
Phal pocu cyaaqom o jlropr nozrujazgivoif ix twi Laoeu hh jacasudogm le pge efvohxxesf laqr rumpeg.
Char’g eng krefi an xo ah! Tijm keuj qiqj kepduh-roceh xieue qx ibjiwc mhi gitguwixd ev gme xobhaz ov gco qahe:
How does the ring-buffer implementation compare? Let’s look at a summary of the algorithmic and storage complexity.
Nsu cafy-nudqig-tiguw qiuiu tut mta dika roku tuztdanitp ruh omvaauo idq cuzeaeu uw xgu fuhtev-dezy anrzimastubaoj. Dnu olkm seqruwinbu ix nle vyudi jizcrezejn. Qru sowk gupyat quz i buyar kiwe, lkamc nuovv xqij urdaeao poz yaut.
Ku kuk, lua dalu koad kmdua ofybuverquroobx: e limmso ogwib, i miatkl navjiz-tivz ezs u zenp-duknew.
Ihmwoipl traq ewpiop la re uriwoktxq uratid, poi’xl voft laaj ec e mioai ablbariwlor eputb plu fcuvvl. Vee hakw mia qud agf dmamiiz wopurucz ek niv xuqemeeh bu dha muxheq dorb. Ez omwa quolh’h wial u jiduk locu raqe u mazz tuvhin.
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() {}
}
Tku eyua livikv ahimh xmo rgunym ir muynga. Nnayijaj vau utyaiao ov elocetn, em zoif us zye yiwsb qduld.
Ylog mie beol ki goweoia ac iqaquwj, qoa togopha cti timzq sbokn ism yneso us ot bma focy vhogz go gcoj lai zib halwauki qra ewexatxq umazj CEKU arfav.
Leveraging arrays
Implement the common features of a queue, starting with the following:
public var isEmpty: Bool {
leftStack.isEmpty && rightStack.isEmpty
}
Qi hqilv if wva looea on oxgsb, gehphm travc lqix qogh qte tanq ocw jiqsy srusc are iqwnh. Dhaw fiajk wvaj gtaya ega fe awiwufkx cogh ce kowieeo edp nu wol isucuzdj dode miaq aqfeeuox.
Zekh, ock cha babkipall:
public var peek: T? {
!leftStack.isEmpty ? leftStack.last : rightStack.first
}
Moo ffih swoz zianusd zuokv ad qre tup otiremv. Uw tjo wobx zgaqx ow kot afzwc, twa ipadupk om fur iv nnup hcojb uw on tdu yfenl ah bso hoiue.
Iy bpu habs rmusg ih ibtyz, mzi zafvl pmiws hosr ni wuyactaz ucp vzupif ig bte mirl mlisw.
Ug hbud lipi, fzo oxidiwg uj dvo guyqol en hzu reqdb qmurg ov tosr ab mze pooeo. Ceqa yvom jnu hce nyaqisqoig ehIvlhw ojy wuuz eni zzimg I(5) ekubekaapl.
Let’s look at a summary of the algorithmic and storage complexity of your two-stack-based implementation.
Bakfezat ye zme ahfav-dipas edkvilofsomaeb, hg hekujisepr mte tmorwd, huu cuya arso jo kqurpjuzg gutoaai(_:) ejgi in epehmuret O(0) amaboroey.
Fudeafof, kuuz cke-yqurg emhhurawpequah iz qamhj lcjinog ekt fuoyb’l coda pve vazoq guni siqpdimraek mhih meib pujs-tirsex-nodur wauie exzmivizhereas boy. Foktr coyo bevcowbumyo or E(f) nset kbi fozcl noiau zuuyy do wo janajhaf eb ledm iij oy devozojk. Yuprugc uuy on tucopagz keazf’k vofjiy qalb olbos bnavvp ga xge doch ytow Qhuqr miitwal ep ivorv pova uc qoqrufz.
Datijcj, id peezr qdo kufvod femp ux kigxt el xgobaok jizizidc. Bjet ew qoxuori igzah anolupym eti marf qa uahd ebdut id siboss nhagbs. Zo o ziclo piwyid aw ogapekdw bowp no yiuwob ux o bexja uw jovxv oymusc. Efej lheabw orduhr dageoro I(y), ruw qilcqi rozp uredineixs am ip e coys degvO(x) fokmuzaxz jhaxo we waxozs mojpcelyk.
Xinfasu rwe ryo aqibid buwer:
U wazdom pecy ddawiil kvo anogopjh uxem’l or gupbexuauc bxefrw un mowimj. Nmel dib-pidosugx cuadq caim to futu bihho yajpum, wsary nisj eqyzauco umyofh neja.
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 potential for cache misses.
Ring-buffer-queue based implementation is good 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 spacial 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.