QUEUES
DATA STRUCTURES
December 16, 2022
class queue
Overview
A queue is a collection of same-typed objects ordered by insertion
time.
The oldest object is called the front.
Access is restricted: only the front can be read, modied, or
removed at any time.
Though simple, queues have many applications.
2
Specication
1 template <class T>
2 class queue {
3 public:
4 // pre: none
5 // post: an empty queue has been created
6 queue();
7
8 // pre: none
9 // post: returns true iff this queue contains no elements
10 bool empty() const;
11
12 // pre: none
13 // post: returns number of elements in this queue
14 size_type size() const;
15
16 // pre: this queue is not empty
17 // post: returns the first-inserted element (front) in this queue
18
19 T & front() const;
20 T & front();
21 // pre: this queue is not empty
22 // post: the first-inserted element in this queue has been removed
23 void pop();
24
25 // pre: none
26 // post: a copy of the given element has been inserted into this q
27 void push(const T& entry);
28 }; 3
Polymorphism
1 template <class T>
2 class queue {
3 ...
4 };
5
6 int main()
7 {
8 queue<int> Qi;
9 queue<double> Qd;
10 ...
11 }
This template class denition takes a parameter T, which is the
type of elements held in queue.
4
const member functions
1 bool empty() const;
2
3 // implementation may NOT modify member variables
4 // and may NOT use non-const member functions
5
Design by contract
1 // pre: this queue is not empty
2 // post: returns the first-inserted element (front) in this queue
3
4 T & front(); // return reference to front, not copy
5 T & front() const; // this version is used when called by another const function
The pre-condition and post-condition preceding each member
function is a contract between the class designer and the class
implementer:
when the function is called, the pre-condition must hold;
when the function returns, the post-condition must hold.
6
Dynamic-array representation
1 private:
2 T *data;
3 size_type front, used, capacity;
4
5 /* Invariants:
6 1) the queue elements are stored in data[front], data[(front+1)% capacity], ...,
7 2) data[(front+used-1) % capacity] in increasing order of insertion time
8
9 3) used = number of elements in this queue
10
11 4) capacity = physical size of data
12 */
7
Implementation
26 assert(!empty());
1 queue(size_type init_capacity = INIT_CAPACITY) 27 return data[front];
2 { 28 }
3 front = used = 0; 29
4 capacity = init_capacity; 30 void pop()
5 data = new T[capacity]; 31 {
6 } 32 assert(!empty());
7 33 front = (front + 1) % capacity;
8 bool empty() const 34 --used;
9 { 35 }
10 return (used == 0); 36
11 } 37 void push(const T & entry)
12 38 {
13 size_type size() const 39 if (used == capacity)
14 { 40 {
15 return used; 41 capacity *= 2;
16 } 42 T *newdata = new T[capacity];
17 43 std::copy(data+front, data+used, newdata);
18 T & front() const 44 std::copy(data, data+front,
19 { 45 newdata+used - front);
20 assert(!empty()); 46 front = 0;
21 return data[front]; 47 delete [] data;
22 } 48 data = newdata;
23 49 }
24 T & front() 50 data[(front+used++) % capacity] = entry;
25 { 51 }
8
Linked-list representation
1 private:
2 std::list<T> _l;
3
4 /* invariants:
5 1) the items in this queue are stored in a doubly linked list _l in order of insertion
6 2) the oldest element is at the front of _l and the newest element is at the back of _l
7 */
9
Implementation
19 return _l.front();
1 queue() 20 }
2 { 21
3 _l = std::list<T>(); 22 T & front()
4 } 23 {
5 24 assert(!empty());
6 bool empty() const 25 return _l.front();
7 { 26 }
8 return (_l.size() == 0); 27
9 } 28 void push(const T & entry)
10 29 {
11 size_type size() const 30 _l.push_back(entry);
12 { 31 }
13 return _l.size(); 32
14 } 33 void pop()
15 34 {
16 T & front() const 35 assert(!empty());
17 { 36 _l.pop_front();
18 assert(!empty()); 37 }
10
Application: Queuing Theory
Single-server queues
Consider a queue with a single server
with an average customer arrival rate of α customers per
second;
and an average customer service rate of β customers per
second.
What is the average time spent in the queue by the customers ?
11
Technical details: Poisson process
events occur independently of each other
average rate in any time period is constant
at most one event at any time
1 double exp_rand(double rate) // time between events in a Poisson process
2 {
3 int r;
4 do
5 {
6 r = rand();
7 } while (r == 0 || r == RAND_MAX);
8
9 double u = (double) r / RAND_MAX; // rand() in [0, RAND_MAX]
10 return -log(u)/rate;
11 }
12
Simulation
1 typedef pair<double,double> customer;
2
3 double average_visit_time(double arrival_rate, double service_rate, std::size_t N)
4 {
5 double time(0.0);
6 queue<customer> q;
7 for (int i = 0; i < N; ++i)
8 {
9 time += exp_rand(arrival_rate);
10 q.push(customer(time, exp_rand(service_rate)));
11 }
12
13 double total_spent(0.0);
14 time= 0.0;
15
16 while (!q.empty())
17 {
18 customer c = q.front();
19 q.pop();
20 time = max(time, c.first) + c.second;
21
22 total_spent += (time- c.first);
23 }
24 return total_spent/N;
25 }
13
Find a formula based on arrival and service rates
1 int main()
2 {
3 srand(time(0));
4 const std::size_t N(1000000);
5
6 cout << average_visit_time(1.0, 5.0, N) << endl;
7 cout << average_visit_time(1.0, 3.0, N) << endl;
8 cout << average_visit_time(1.0, 2.0, N) << endl;
9 cout << average_visit_time(1.0, 1.5, N) << endl;
10 cout << average_visit_time(1.0, 1.1, N) << endl;
11 cout << average_visit_time(1.0, 1.05, N) << endl;
12 cout << average_visit_time(1.0, 1.025, N) << endl;
13 }
14