Lernziele
Die Klasse b_priority_queue |
Bei manchen Anwendungen von Prioritäts-Warteschlangen ist
von vorneherein bekannt, dass alle jemals darin auftretenden
Prioritäten p aus einem relativ kleinen Intervall [a..b] ganzer
Zahlen stammen werden. Für diesen speziellen Fall einer Prioritäts-Warteschlange mit
beschränkten, ganzzahligen Prioritäten (bounded integral priority
queue) bietet LEDA über die Klasse b_priority_queue
eine besonders effiziente Implementierung von
Prioritäts-Warteschlangen an.
Im Konstruktor der Klasse müssen die Endpunkte a und b des
Intervalls angegeben werden, aus dem die Prioritäten
stammen. Diese Klasse hat nur einen Template-Parameter, nämlich
den Typ der Information. Eine solche Prioritäts-Warteschlange
BPQ mit Informationstyp I
legt man also etwa durch
b_priority_queue<I> BPQ(a,b);
mit a und b vom Typ int an.
Diese Klasse folgt genauso wie die Klasse p_queue
dem Item-Container-Konzept. Die Schnittstelle bietet im
Wesentlichen dieselben Operationen an, unterscheidet sich jedoch in der Nomenklatur:
Als Anwendungsbeispiel wollen wir unser durch eine
p_queue beschleunigtes
Programm der Warteschlangensimulation nochmals für
folgendes Szenario verbessern: Angenommen, es wurde festgestellt,
dass sich nie mehr als 5 Reisende in einer Schlange vor einem
Schalter befinden, weil kein Reisender Lust hat, sich an eine
bereits derart lange Schlange anzustellen. Vielmehr verlässt ein
Reisender sofort frustriert die Schalterhalle, wenn er feststellt,
dass in jeder Schlange schon 5 Personen stehen. In diesem Szenario
stammen alle auftretenden Prioritäten (d. h. alle Längen der
Warteschlangen) aus dem Intervall [0..5]. Wir können daher statt einer p_queue auch eine b_priority_queue verwenden:
#include <LEDA/queue.h>
#include <LEDA/b_prio.h>
#include <LEDA/list.h>
#include <LEDA/array.h>
#include <LEDA/random_variate.h>
#include <iostream>
#include "Traveller.h"
using leda::queue;
using leda::b_priority_queue;
using leda::b_pq_item;
using leda::array;
using leda::random_variate;
using leda::list;
using std::cout;
const int max_travellers_in_queue = 5;
array< queue<Traveller*> > Queues(1,8);
// Every queue knows the number of its travellers
// There are now at most max_travellers_in_queue travellers in a queue
b_priority_queue<int> BPQ(0,max_travellers_in_queue);
array<b_pq_item> BPQITEM(1,8); // Every queue knows its item in BPQ
list<int> Service_times; // needed for computation of variance
const int mins_to_simulate = 60*24*7*52; // 1 full year
int total_travellers = 0;
int travellers_served = 0;
int total_service_time = 0;
int num_frustrated_travellers = 0;
void output_statistics();
int main()
{
// initialize bounded priority queue BPQ:
// there are 0 travellers in all 8 queues
for(int i = 1; i <= 8; i++)
BPQITEM[i] = BPQ.insert(i, 0); // WARNING! order of args reversed!
// random variate of 'travellers per minute'
array<int> weight(0,4);
weight[0] = 1;
weight[1] = 4;
weight[2] = 6;
weight[3] = 4;
weight[4] = 1;
// this gives 2 travellers per minute on the average
random_variate R(weight);
// for every minute of the time interval do the following
for(int time = 0; time < mins_to_simulate; time++) {
// some new travellers arrive
int num_new_travellers = R.generate();
total_travellers += num_new_travellers;
// queing the new travellers
for(int i = 0; i < num_new_travellers; i++ ) {
// find out queue with minimal length
b_pq_item min_it = BPQ.find_min();
int min_queue = BPQ.key(min_it); // WARNING! 'inf' in p_queue
int min_length = BPQ.inf(min_it); // WARNING! 'prio' in p_queue
// has this queue already max_travellers_in_queue travellers?
if(min_length == max_travellers_in_queue) {
// the remaining new travellers
// leave the system immediately in frustration
num_frustrated_travellers += (num_new_travellers - i);
break;
}
Traveller* T = new Traveller(time);
// traveller T goes to the queue with minimal length
Queues[min_queue].append(T);
// this queue has now one more traveller
// BUT: one cannot increase priority!
// use the following trick: delete and insert with new priority
BPQ.del_min();
// WARNING! order of args reversed!
BPQITEM[min_queue] = BPQ.insert(min_queue, min_length + 1);
}
// travellers at the front of the queues get served
for(int i = 1; i <= 8; i++)
if(!Queues[i].empty()) {
Traveller* T = Queues[i].top();
if(T->dec_time_to_serve() == 0) {
// T leaves the system
Queues[i].pop();
// queue i has now one less traveller
int old_p = BPQ.inf(BPQITEM[i]); // WARNING: 'prio' in p_queue
BPQ.decrease_inf(BPQITEM[i], old_p - 1 );
travellers_served++;
int time_used = time - T->get_arrival_time();
total_service_time += time_used;
Service_times.append(time_used);
delete T;
}
}
}
output_statistics();
}
void output_statistics() {
double mean = total_service_time / (double)travellers_served;
// compute standard deviation
int time;
double sum;
forall(time, Service_times)
sum += (time - mean) * (time - mean);
double variance = sum / (Service_times.size()-1);
double stddev = sqrt(variance);
cout << "Total number of travellers arrived = "
<< total_travellers << "\n";
cout << "Total number of travellers served = "
<< travellers_served << "\n";
cout << "Total number of frustrated travellers not served = "
<< num_frustrated_travellers << "\n";
cout << "Total service time = " << total_service_time << "\n";
cout << "Service time per traveller = " << mean << "\n";
cout << "Standard deviation = " << stddev << "\n";
}
Die Ausgabe eines Beispiellaufes war:
Total number of travellers arrived = 1048537 Total number of travellers served = 1048286 Total number of frustrated travellers not served = 241 Total service time = 5637477 Service time per traveller = 5.3778 Standard deviation = 3.42378
Wir sehen, dass bei dieser Wahl von max_travellers_in_queue nur sehr wenige Reisende frustriert wieder von dannen ziehen.
Dieses Programm benötigt auf der Maschine des Autors nur 4,2
Sekunden, holt also gegenüber den 5,5 Sekunden der Version mit
einer gewöhnlichen p_queue nochmals ca.
24% an Kaufzeitgewinn heraus! Das liegt daran, dass die Methoden
insert(), find_min(),
del_item(),
decrease_inf(), key()
und inf() alle Zeit O(1) benötigen; die
Methode del_min() benötigt Zeit O(d), wobei d
die Differenz der bisherigen kleinsten Priorität zur
nächstkleineren Priorität ist.
Zur Implementierung von
b_priority_queue s. Übung 44.
Die Verwendung einer
b_priority_queue lohnt sich gegenüber
einer p_queue vor allem dann, wenn das
Intervall [a..b] der auftretenden Prioritäten klein ist. Ist
dieses sehr groß, so sollte
b_priority_queue nicht verwendet werden,
weil mit der Länge b-a des Intervalls auch der von der
Datenstruktur benötigte Speicherplatz wächst: Dieser ist O(b-a+n), wobei n die Anzahl der in der Struktur gespeicherten Paare (p,i) ist.
Mehr über die Klasse b_priority_queue findet
sich auf der entsprechenden Manualseite.