258 lines
19 KiB
HTML
258 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="generator" content="pandoc" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||
<title>9.6 Queues</title>
|
||
<style>
|
||
code{white-space: pre-wrap;}
|
||
span.smallcaps{font-variant: small-caps;}
|
||
span.underline{text-decoration: underline;}
|
||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||
ul.task-list{list-style: none;}
|
||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||
div.sourceCode { margin: 1em 0; }
|
||
pre.sourceCode { margin: 0; }
|
||
@media screen {
|
||
div.sourceCode { overflow: auto; }
|
||
}
|
||
@media print {
|
||
pre > code.sourceCode { white-space: pre-wrap; }
|
||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||
}
|
||
pre.numberSource code
|
||
{ counter-reset: source-line 0; }
|
||
pre.numberSource code > span
|
||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||
pre.numberSource code > span > a:first-child::before
|
||
{ content: counter(source-line);
|
||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||
border: none; display: inline-block;
|
||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||
-khtml-user-select: none; -moz-user-select: none;
|
||
-ms-user-select: none; user-select: none;
|
||
padding: 0 4px; width: 4em;
|
||
color: #aaaaaa;
|
||
}
|
||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||
div.sourceCode
|
||
{ }
|
||
@media screen {
|
||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||
}
|
||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||
code span.at { color: #7d9029; } /* Attribute */
|
||
code span.bn { color: #40a070; } /* BaseN */
|
||
code span.bu { } /* BuiltIn */
|
||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||
code span.ch { color: #4070a0; } /* Char */
|
||
code span.cn { color: #880000; } /* Constant */
|
||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||
code span.dt { color: #902000; } /* DataType */
|
||
code span.dv { color: #40a070; } /* DecVal */
|
||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||
code span.ex { } /* Extension */
|
||
code span.fl { color: #40a070; } /* Float */
|
||
code span.fu { color: #06287e; } /* Function */
|
||
code span.im { } /* Import */
|
||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||
code span.op { color: #666666; } /* Operator */
|
||
code span.ot { color: #007020; } /* Other */
|
||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||
code span.ss { color: #bb6688; } /* SpecialString */
|
||
code span.st { color: #4070a0; } /* String */
|
||
code span.va { color: #19177c; } /* Variable */
|
||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||
</style>
|
||
<link rel="stylesheet" href="../tufte.css" />
|
||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" type="text/javascript"></script>
|
||
<!--[if lt IE 9]>
|
||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||
<![endif]-->
|
||
</head>
|
||
<body>
|
||
<div style="display:none">
|
||
\(
|
||
\newcommand{\NOT}{\neg}
|
||
\newcommand{\AND}{\wedge}
|
||
\newcommand{\OR}{\vee}
|
||
\newcommand{\XOR}{\oplus}
|
||
\newcommand{\IMP}{\Rightarrow}
|
||
\newcommand{\IFF}{\Leftrightarrow}
|
||
\newcommand{\TRUE}{\text{True}\xspace}
|
||
\newcommand{\FALSE}{\text{False}\xspace}
|
||
\newcommand{\IN}{\,{\in}\,}
|
||
\newcommand{\NOTIN}{\,{\notin}\,}
|
||
\newcommand{\TO}{\rightarrow}
|
||
\newcommand{\DIV}{\mid}
|
||
\newcommand{\NDIV}{\nmid}
|
||
\newcommand{\MOD}[1]{\pmod{#1}}
|
||
\newcommand{\MODS}[1]{\ (\text{mod}\ #1)}
|
||
\newcommand{\N}{\mathbb N}
|
||
\newcommand{\Z}{\mathbb Z}
|
||
\newcommand{\Q}{\mathbb Q}
|
||
\newcommand{\R}{\mathbb R}
|
||
\newcommand{\C}{\mathbb C}
|
||
\newcommand{\cA}{\mathcal A}
|
||
\newcommand{\cB}{\mathcal B}
|
||
\newcommand{\cC}{\mathcal C}
|
||
\newcommand{\cD}{\mathcal D}
|
||
\newcommand{\cE}{\mathcal E}
|
||
\newcommand{\cF}{\mathcal F}
|
||
\newcommand{\cG}{\mathcal G}
|
||
\newcommand{\cH}{\mathcal H}
|
||
\newcommand{\cI}{\mathcal I}
|
||
\newcommand{\cJ}{\mathcal J}
|
||
\newcommand{\cL}{\mathcal L}
|
||
\newcommand{\cK}{\mathcal K}
|
||
\newcommand{\cN}{\mathcal N}
|
||
\newcommand{\cO}{\mathcal O}
|
||
\newcommand{\cP}{\mathcal P}
|
||
\newcommand{\cQ}{\mathcal Q}
|
||
\newcommand{\cS}{\mathcal S}
|
||
\newcommand{\cT}{\mathcal T}
|
||
\newcommand{\cV}{\mathcal V}
|
||
\newcommand{\cW}{\mathcal W}
|
||
\newcommand{\cZ}{\mathcal Z}
|
||
\newcommand{\emp}{\emptyset}
|
||
\newcommand{\bs}{\backslash}
|
||
\newcommand{\floor}[1]{\left \lfloor #1 \right \rfloor}
|
||
\newcommand{\ceil}[1]{\left \lceil #1 \right \rceil}
|
||
\newcommand{\abs}[1]{\left | #1 \right |}
|
||
\newcommand{\xspace}{}
|
||
\newcommand{\proofheader}[1]{\underline{\textbf{#1}}}
|
||
\)
|
||
</div>
|
||
<header id="title-block-header">
|
||
<h1 class="title">9.6 Queues</h1>
|
||
</header>
|
||
<section>
|
||
<p>Picture a lineup at a fast food restaurant. The first person in line is the first one served, then the next person in line, and so forth. As new people join the line, they join at the back, so that everyone who joined before them are served before them. This is the exact opposite of a stack: in this lineup situation, people leave the line in the <em>same</em> order they joined it.</p>
|
||
<p>In this section, we’ll introduce a new abstract data type to represent this type of collection, see how to implement it in Python, and analyze our implementation’s take a close look at the operations a queue supports, how we might implement one in Python, and how efficient our implementation is.</p>
|
||
<h2 id="the-queue-adt">The Queue ADT</h2>
|
||
<p>A <strong>queue</strong> is another collection of data that, like a stack, adds and removes items in a fixed order. Unlike a stack, items come out of a queue in the order in which they entered. We call this behaviour <em>First-In-First-Out (FIFO)</em>.</p>
|
||
<ul>
|
||
<li><p><strong>Queue</strong></p>
|
||
<ul>
|
||
<li>Data: a collection of items</li>
|
||
<li>Operations: determine whether the queue is empty, add an item (<em>enqueue</em>), remove the least recently-added item (<em>dequeue</em>)</li>
|
||
</ul></li>
|
||
</ul>
|
||
<p>In code:</p>
|
||
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1"></a><span class="kw">class</span> Queue:</span>
|
||
<span id="cb1-2"><a href="#cb1-2"></a> <span class="co">"""A first-in-first-out (FIFO) queue of items.</span></span>
|
||
<span id="cb1-3"><a href="#cb1-3"></a></span>
|
||
<span id="cb1-4"><a href="#cb1-4"></a><span class="co"> Stores data in a first-in, first-out order. When removing an item from the</span></span>
|
||
<span id="cb1-5"><a href="#cb1-5"></a><span class="co"> queue, the most recently-added item is the one that is removed.</span></span>
|
||
<span id="cb1-6"><a href="#cb1-6"></a></span>
|
||
<span id="cb1-7"><a href="#cb1-7"></a><span class="co"> >>> q = Queue()</span></span>
|
||
<span id="cb1-8"><a href="#cb1-8"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb1-9"><a href="#cb1-9"></a><span class="co"> True</span></span>
|
||
<span id="cb1-10"><a href="#cb1-10"></a><span class="co"> >>> q.enqueue('hello')</span></span>
|
||
<span id="cb1-11"><a href="#cb1-11"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb1-12"><a href="#cb1-12"></a><span class="co"> False</span></span>
|
||
<span id="cb1-13"><a href="#cb1-13"></a><span class="co"> >>> q.enqueue('goodbye')</span></span>
|
||
<span id="cb1-14"><a href="#cb1-14"></a><span class="co"> >>> q.dequeue()</span></span>
|
||
<span id="cb1-15"><a href="#cb1-15"></a><span class="co"> 'hello'</span></span>
|
||
<span id="cb1-16"><a href="#cb1-16"></a><span class="co"> >>> q.dequeue()</span></span>
|
||
<span id="cb1-17"><a href="#cb1-17"></a><span class="co"> 'goodbye'</span></span>
|
||
<span id="cb1-18"><a href="#cb1-18"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb1-19"><a href="#cb1-19"></a><span class="co"> True</span></span>
|
||
<span id="cb1-20"><a href="#cb1-20"></a><span class="co"> """</span></span>
|
||
<span id="cb1-21"><a href="#cb1-21"></a></span>
|
||
<span id="cb1-22"><a href="#cb1-22"></a> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb1-23"><a href="#cb1-23"></a> <span class="co">"""Initialize a new empty queue."""</span></span>
|
||
<span id="cb1-24"><a href="#cb1-24"></a></span>
|
||
<span id="cb1-25"><a href="#cb1-25"></a> <span class="kw">def</span> is_empty(<span class="va">self</span>) <span class="op">-></span> <span class="bu">bool</span>:</span>
|
||
<span id="cb1-26"><a href="#cb1-26"></a> <span class="co">"""Return whether this queue contains no items.</span></span>
|
||
<span id="cb1-27"><a href="#cb1-27"></a><span class="co"> """</span></span>
|
||
<span id="cb1-28"><a href="#cb1-28"></a></span>
|
||
<span id="cb1-29"><a href="#cb1-29"></a> <span class="kw">def</span> enqueue(<span class="va">self</span>, item: Any) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb1-30"><a href="#cb1-30"></a> <span class="co">"""Add <item> to the back of this queue.</span></span>
|
||
<span id="cb1-31"><a href="#cb1-31"></a><span class="co"> """</span></span>
|
||
<span id="cb1-32"><a href="#cb1-32"></a></span>
|
||
<span id="cb1-33"><a href="#cb1-33"></a> <span class="kw">def</span> dequeue(<span class="va">self</span>) <span class="op">-></span> Any:</span>
|
||
<span id="cb1-34"><a href="#cb1-34"></a> <span class="co">"""Remove and return the item at the front of this queue.</span></span>
|
||
<span id="cb1-35"><a href="#cb1-35"></a></span>
|
||
<span id="cb1-36"><a href="#cb1-36"></a><span class="co"> Raise an EmptyQueueError if this queue is empty.</span></span>
|
||
<span id="cb1-37"><a href="#cb1-37"></a><span class="co"> """</span></span>
|
||
<span id="cb1-38"><a href="#cb1-38"></a></span>
|
||
<span id="cb1-39"><a href="#cb1-39"></a></span>
|
||
<span id="cb1-40"><a href="#cb1-40"></a><span class="kw">class</span> EmptyQueueError(<span class="pp">Exception</span>):</span>
|
||
<span id="cb1-41"><a href="#cb1-41"></a> <span class="co">"""Exception raised when calling dequeue on an empty queue."""</span></span>
|
||
<span id="cb1-42"><a href="#cb1-42"></a></span>
|
||
<span id="cb1-43"><a href="#cb1-43"></a> <span class="kw">def</span> <span class="fu">__str__</span>(<span class="va">self</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb1-44"><a href="#cb1-44"></a> <span class="co">"""Return a string representation of this error."""</span></span>
|
||
<span id="cb1-45"><a href="#cb1-45"></a> <span class="cf">return</span> <span class="st">'dequeue may not be called on an empty queue'</span></span></code></pre></div>
|
||
<p>Much like a stack, we can also picture implementing this with a Python list. And, once again, we need to decide which end of the list is considered the front. Unlike the stack, we will see that there is a trade-off in choosing which end of the list is considered a front. Before reading the rest of the section, try to informally reason with yourself why this might be, taking into account that a queue is a FIFO.</p>
|
||
<h2 id="list-based-implementation-of-the-queue-adt">List-based implementation of the Queue ADT</h2>
|
||
<p>In the following implementation, we use a Python list that is hidden from the client. We have decided that the beginning of the list (i.e., index 0) is the front of the queue. This means that new items that are enqueued will be added at the end of the list, and items that are dequeued are removed from the beginning of the list.</p>
|
||
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1"></a><span class="kw">class</span> Queue:</span>
|
||
<span id="cb2-2"><a href="#cb2-2"></a> <span class="co">"""A first-in-first-out (FIFO) queue of items.</span></span>
|
||
<span id="cb2-3"><a href="#cb2-3"></a></span>
|
||
<span id="cb2-4"><a href="#cb2-4"></a><span class="co"> Stores data in a first-in, first-out order. When removing an item from the</span></span>
|
||
<span id="cb2-5"><a href="#cb2-5"></a><span class="co"> queue, the most recently-added item is the one that is removed.</span></span>
|
||
<span id="cb2-6"><a href="#cb2-6"></a></span>
|
||
<span id="cb2-7"><a href="#cb2-7"></a><span class="co"> >>> q = Queue()</span></span>
|
||
<span id="cb2-8"><a href="#cb2-8"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb2-9"><a href="#cb2-9"></a><span class="co"> True</span></span>
|
||
<span id="cb2-10"><a href="#cb2-10"></a><span class="co"> >>> q.enqueue('hello')</span></span>
|
||
<span id="cb2-11"><a href="#cb2-11"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb2-12"><a href="#cb2-12"></a><span class="co"> False</span></span>
|
||
<span id="cb2-13"><a href="#cb2-13"></a><span class="co"> >>> q.enqueue('goodbye')</span></span>
|
||
<span id="cb2-14"><a href="#cb2-14"></a><span class="co"> >>> q.dequeue()</span></span>
|
||
<span id="cb2-15"><a href="#cb2-15"></a><span class="co"> 'hello'</span></span>
|
||
<span id="cb2-16"><a href="#cb2-16"></a><span class="co"> >>> q.dequeue()</span></span>
|
||
<span id="cb2-17"><a href="#cb2-17"></a><span class="co"> 'goodbye'</span></span>
|
||
<span id="cb2-18"><a href="#cb2-18"></a><span class="co"> >>> q.is_empty()</span></span>
|
||
<span id="cb2-19"><a href="#cb2-19"></a><span class="co"> True</span></span>
|
||
<span id="cb2-20"><a href="#cb2-20"></a><span class="co"> """</span></span>
|
||
<span id="cb2-21"><a href="#cb2-21"></a> <span class="co"># Private Instance Attributes:</span></span>
|
||
<span id="cb2-22"><a href="#cb2-22"></a> <span class="co"># - _items: The items stored in this queue. The front of the list represents</span></span>
|
||
<span id="cb2-23"><a href="#cb2-23"></a> <span class="co"># the front of the queue.</span></span>
|
||
<span id="cb2-24"><a href="#cb2-24"></a> _items: <span class="bu">list</span></span>
|
||
<span id="cb2-25"><a href="#cb2-25"></a></span>
|
||
<span id="cb2-26"><a href="#cb2-26"></a> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb2-27"><a href="#cb2-27"></a> <span class="co">"""Initialize a new empty queue."""</span></span>
|
||
<span id="cb2-28"><a href="#cb2-28"></a> <span class="va">self</span>._items <span class="op">=</span> []</span>
|
||
<span id="cb2-29"><a href="#cb2-29"></a></span>
|
||
<span id="cb2-30"><a href="#cb2-30"></a> <span class="kw">def</span> is_empty(<span class="va">self</span>) <span class="op">-></span> <span class="bu">bool</span>:</span>
|
||
<span id="cb2-31"><a href="#cb2-31"></a> <span class="co">"""Return whether this queue contains no items.</span></span>
|
||
<span id="cb2-32"><a href="#cb2-32"></a><span class="co"> """</span></span>
|
||
<span id="cb2-33"><a href="#cb2-33"></a> <span class="cf">return</span> <span class="va">self</span>._items <span class="op">==</span> []</span>
|
||
<span id="cb2-34"><a href="#cb2-34"></a></span>
|
||
<span id="cb2-35"><a href="#cb2-35"></a> <span class="kw">def</span> enqueue(<span class="va">self</span>, item: Any) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb2-36"><a href="#cb2-36"></a> <span class="co">"""Add <item> to the back of this queue.</span></span>
|
||
<span id="cb2-37"><a href="#cb2-37"></a><span class="co"> """</span></span>
|
||
<span id="cb2-38"><a href="#cb2-38"></a> <span class="va">self</span>._items.append(item)</span>
|
||
<span id="cb2-39"><a href="#cb2-39"></a></span>
|
||
<span id="cb2-40"><a href="#cb2-40"></a> <span class="kw">def</span> dequeue(<span class="va">self</span>) <span class="op">-></span> Optional[Any]:</span>
|
||
<span id="cb2-41"><a href="#cb2-41"></a> <span class="co">"""Remove and return the item at the front of this queue.</span></span>
|
||
<span id="cb2-42"><a href="#cb2-42"></a></span>
|
||
<span id="cb2-43"><a href="#cb2-43"></a><span class="co"> Raise an EmptyQueueError if this queue is empty.</span></span>
|
||
<span id="cb2-44"><a href="#cb2-44"></a><span class="co"> """</span></span>
|
||
<span id="cb2-45"><a href="#cb2-45"></a> <span class="cf">if</span> <span class="va">self</span>.is_empty():</span>
|
||
<span id="cb2-46"><a href="#cb2-46"></a> <span class="cf">raise</span> EmptyQueueError</span>
|
||
<span id="cb2-47"><a href="#cb2-47"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb2-48"><a href="#cb2-48"></a> <span class="cf">return</span> <span class="va">self</span>._items.pop(<span class="dv">0</span>)</span></code></pre></div>
|
||
<h2 id="implementation-efficiency">Implementation efficiency</h2>
|
||
<p>Our <code>Queue.enqueue</code> calls <code>list.append</code>, which we know takes constant (<span class="math inline">\(\Theta(1)\)</span>) time. However, the <code>Queue.dequeue</code> calls <code>self._items.pop(0)</code>, which takes <span class="math inline">\(\Theta(n)\)</span> time (where <span class="math inline">\(n\)</span> is the number of items stored in the queue). If we changed things around so that the front of the queue is the end of the list (rather than the beginning), we simply swap these running times. This presents a trade-off; using an array-based list, we can <em>either</em> have an efficient enqueue or an efficient dequeue operation.</p>
|
||
<p>Is there, perhaps, another data structure we can use instead of a list to improve efficiency? Unfortunately, both <code>dict</code> and <code>set</code> are unordered data structures, but queues need to maintain (and remember) a very specific order. One interesting programming challenge is to implement a queue using two stacks, which can be done correctly but is not always more efficient. Eventually you will learn about even more interesting data structures, and it may be a good idea to revisit the Queue ADT and see how to use your new arsenal of data structures instead of a Python list. And because of abstraction (i.e., <code>_items</code> is a private attribute), you can modify your <code>Queue</code> implementation however you like without having to change any client code that uses it!</p>
|
||
</section>
|
||
<footer>
|
||
<a href="https://www.teach.cs.toronto.edu/~csc110y/fall/notes/">CSC110 Course Notes Home</a>
|
||
</footer>
|
||
</body>
|
||
</html>
|