PowerPoint

Werbung
Prioritätswarteschlangen
In vielen Anwendungen müssen Datensätze mit Schlüsseln
der Reihe nach verarbeitet werden, doch nicht unbedingt in
einer vollständig sortierten Reihenfolge und nicht unbedingt
auf einmal.
Eine geeignete Datenstruktur muss unter solchen
Bedingungen die Eigenschaft haben, dass sie die Operationen
des Einfügens eines neuen Elementes und des Löschen des
größten Elementes unterstützt.
Eine derartige Datenstruktur, die Schlangen (Löschen des
ältesten Elements) und Stapeln(Löschen des neuesten
Elements) gegenüber gestellt werden kann, wird
Prioritätswarteschlange (priority queue) genannt.
Diese kann man sich als Verallgemeinerung des Stapels und
der Schlange vorstellen.
G.Heyer
1
Algorithmen und Datenstrukturen II
Anwendungsbeispiele für Prioritätswarteschlangen:
- Simulationssysteme, wo die Schlüssel den „Ereigniszeiten“
entsprechen könnten, die der Reihe nach verarbeitet werden
müssen
- das Job-Scheduling in Computersystemen, wo die Schlüssel
den „Prioritäten“ entsprechen könnten, die angeben, welche
Benutzer zuerst bedient werden und
- numerische Berechnungen, wo die Schlüssel
Berechnungsfehler sein können, so dass der größte zuerst
bearbeitet werden kann.
Prioritätswarteschlangen können als Basis für verschiedene
fundamentale Algorithmen zum Durchsuchen von Graphen
dienen.
Der Hauptgrund der Nützlichkeit von Prioritätswarteschlangen besteht in ihrer Flexibilität, da sie die Möglichkeit
geben, eine Vielzahl verschiedenartiger Operationen mit
Mengen von Datensätzen mit Schlüsseln effizient
auszuführen.
G.Heyer
2
Algorithmen und Datenstrukturen II
Aufbau und Unterhaltung einer Datenstruktur,
die Datensätze mit numerischen Schlüsseln (Prioritäten)
enthält und einige der folgenden Operationen unterstützt:
- Aufbauen (construct) einer Prioritätswarteschlange aus
N gegebenen Elementen.
- Einfügen (insert) eines neuen Elements.
- Entfernen (remove) des größten Elements.
- Ersetzen (replace) des größten Elements durch ein neues
Element (außer, wenn das neue Element größer ist).
- Verändern (change) der Priorität eines Elements.
- Löschen (delete) eines beliebigen angegebenen Elements.
- Zusammenfügen (join) von zwei Prioritätswarteschlangen
zu einer neuen, größeren.
G.Heyer
3
Algorithmen und Datenstrukturen II
Die Operation replace entspricht fast einem insert,
gefolgt von remove, (wobei der Unterschied darin besteht, dass
die Prioritätswarteschlange sich vorübergehend um ein
Element verlängert); das ist etwas anderes, als wenn einem
remove ein insert folgt.
In ähnlicher Weise könnte die Operation change als ein delete
implementiert werden, dem insert folgt, und construct könnte
mit wiederholter Benutzung der Operation insert
implementiert werden.
Die Operation join erfordert für eine effiziente
Implementierung höherentwickelte Datenstrukturen.
Hier wird eine klassische Datenstruktur, die Heap genannt
wird, behandelt und die effiziente Implementationen der
ersten fünf Operationen ermöglicht.
G.Heyer
4
Algorithmen und Datenstrukturen II
Die Prioritätswarteschlange ist ein ausgezeichnetes Beispiel
einer abstrakten Datenstruktur.
Sie ist mittels der Operationen, die auf ihr ausgeführt
werden , eindeutig definiert, unabhängig davon, wie die
Daten in irgendeiner speziellen Implementation organisiert
und verarbeitet werden.
Elementare Implementationen
Eine Methode der Organisation einer Prioritätswarteschlange ist eine ungeordnete Liste, wobei die Elemente
einfach in einem Feld a [1] , ..., a [N] aufbewahrt werden,
ohne die Schlüssel zu beachten. Hiermit lassen sich construct,
insert und remove leicht implementieren.
(Reservierung von a[0] und evtl. a[N+1] für Markenwerte).
G.Heyer
5
Algorithmen und Datenstrukturen II
static int a [maxN + 1], N;
construct (int b[], int M)
{ for ( N = 1; N <= M; N++ ) a [N] = b[N]; }
insert (int v)
{ a[++ N] = v ;
}
int remove ( )
{
int j , max, v ;
max = 1 ;
for ( j = 2 ; j <= N ; j++ )
if ( a [j] > a [max]) max = j ;
v = a [max]; a [max] = a[N --];
return v ;
}
G.Heyer
6
Algorithmen und Datenstrukturen II
Die Operation construct ist lediglich eine Feldkopie
und für insert inkrementiert man einfach N und legt das neue
Element in a [N] ab, wobei diese Operation eine konstante
Zeit benötigt.
Remove erfordert dagegen das Durchsuchen des Feldes, um
das Element mit dem größten Schlüssel zu finden, wofür eine
lineare Zeit benötigt wird ( alle Elemente im Feld müssen
betrachtet werden), danach das Vertauschen von a[N] mit
dem Element mit dem größten Schlüssel und das
Dekrementieren von N.
Die Implementation von replace ist ähnlich.
Um die Operation change zu implementieren (Verändern
der Priorität des Elements in a [k]), kann man einfach den
neuen Wert speichern, und um das Element in a[k] zu
löschen (delete), kann man es, wie in der letzten Zeile von
remove, mit a [N] vertauschen und N dekrementieren.
G.Heyer
7
Algorithmen und Datenstrukturen II
Eine andere elementare Form der Organisation,
die benutzt werden kann, ist eine geordnete Liste, wobei
wiederum ein Feld a [1] , ... , a [N] verwendet wird, die
Elemente jedoch entsprechend der wachsenden Reihenfolge
ihrer Schlüssel aufbewahrt werden.
Jeder Algorithmus für Prioritätswarteschlangen kann in
einen Sortieralgorithmus umgewandelt werden, in dem
wiederholt insert benutzt wird, um eine Prioritätswarteschlange zu erzeugen, die alle zu sortierenden Elemente
enthält, und indem danach wiederholt remove benutzt wird,
um die Prioritätswarteschlange zu leeren, wobei die
Elemente in umgekehrter Reihenfolge erhalten werden.
Die Benutzung einer als ungeordnete Liste dargestellten
Prioritätswarteschlange entspricht somit Selection Sort, und
die Verwendung einer geordneten Liste entspricht Insertion
Sort.
G.Heyer
8
Algorithmen und Datenstrukturen II
Anstelle der oben angegebenen Implementation mit Hilfe
eines Feldes können auch verkettete Listen für die
ungeordnete Liste oder die geordnete Liste verwendet
werden.
Die ändert nichts an den grundlegenden Merkmalen der
Leistungsfähigkeit für insert, remove oder replace, gibt jedoch
die Möglichkeit, delete und join in konstanter Zeit
auszuführen.
Es lohnt sich diese einfachen Implementationen zu merken,
da sie in der Praxis kompliziertere Methoden überlegen sein
können.
Beispiel: ungeordnete Liste günstig, wenn nur wenig removeOperationen, jedoch sehr viele Einfügungen ausgeführt
werden, geordnete Liste ist geeignet, wenn bei den
einzufügenden Elementen immer zu erwarten ist, dass sie
dem größten Element in der Prioritätswarteschlange nahe
kommen.
G.Heyer
9
Algorithmen und Datenstrukturen II
Die Datenstruktur des Heaps
Die Datenstruktur, die man verwendet, um die Operationen
mit Prioritätswarteschlangen zu ermöglichen, beinhaltet das
Speichern der Datensätze in einem Feld so, dass jeder
Schlüssel garantiert größer ist als die Schlüssel auf zwei
bestimmten anderen Positionen.
Jeder dieser Schlüssel muss wiederum größer sein als zwei
weitere Schlüssel usw. Diese Ordnung lässt sich sehr leicht
veranschaulichen, in dem man das Feld in Form einer
zweidimensionalen Baumstruktur zeichnet, wobei von jedem
Knoten Linien nach unten zu den beiden Knoten führen, von
denen bekannt ist, dass sie kleiner sind.
Diese Struktur wird „vollständiger binärer Baum“ genannt.
G.Heyer
10
Algorithmen und Datenstrukturen II
Diese Struktur kann erzeugt werden, in dem ein Knoten
(Wurzel genannt) gezeichnet wird und dann nach unten und
von links nach rechts fortgefahren wird, in dem jeweils zwei
Knoten unter einem Knoten der vorangehenden Ebene
gezeichnet und mit ihm verbunden werden, bis N Knoten
gezeichnet worden sind.
Die beiden Knoten unter jedem Knoten werden dessen
(direkte) Nachfolger (children) genannt, der Knoten über
jedem Knoten heißt dessen (direkter) Vorgänger (parent).
Forderung: Die Schlüssel im Baum müssen der
Heap-Bedingung genügen:
Der Schlüssel in jedem Knoten soll größer sein (oder gleich) als
die Schlüssel in seinen Nachfolgern (falls er Nachfolger
besitzt).
Daraus folgt, dass sich der größte Schlüssel in der Wurzel
befindet.
G.Heyer
11
Algorithmen und Datenstrukturen II
Darstellung eines Heaps als vollständiger Baum
1 X
2 T
4 G
8 A
9 E
3 O
5 S
10 R
6 M
7 N
11 A 12 I
Darstellung: vollständiger binärer Bäume sequentiell
innerhalb eines Feldes:
- die Wurzel auf Position 1 setzen,
- ihre Nachfolger auf die Positionen 2 und 3,
- die Knoten der nachfolgenden Ebene auf die Positionen
4, 5, 6 und 7 usw.
G.Heyer
12
Algorithmen und Datenstrukturen II
Darstellung eines Heaps als Feld
k
1 2 3 4 5 6 7 8 9 10 11 12
a [k] X T O G S M N A E R A I
Diese Darstellungsform ist sehr nützlich, da es sehr leicht ist,
von einem Knoten zu seinem Vorgänger und zu seinen
Nachfolgern zu gelangen.
- Der Vorgänger des Knotens auf Position j befindet sich
auf Position j/2 ( abgerundet auf die nächste ganze
Zahl, falls j ungerade ist ), und
- die beiden Nachfolger des Knotens auf Position j
befinden sich auf den Positionen 2j und 2j + 1.
Dadurch ist die Traversierung des Baumes sehr einfach.
Ein Heap ist ein als ein Feld dargestellter vollständiger
binärer Baum, in dem jeder Knoten der Heap-Bedingung
genügt. Insbesondere befindet sich der größte Schlüssel stets
auf der ersten Position im Feld.
G.Heyer
13
Algorithmen und Datenstrukturen II
Alle Algorithmen operieren entlang eines Pfades (path)
von der Wurzel zum unteren Ende des Heaps (indem sie sich
einfach vom Vorgänger zum Nachfolger oder umgekehrt
bewegen).
In einem Heap mit N Knoten befinden sich auf allen Pfaden
etwa lgN Knoten.
Es gibt etwa
N/2 Knoten auf der untersten Ebene
N/4 Knoten mit direkten Nachfolgern auf der untersten Ebene
N/8 Knoten mit „Enkeln“ auf der untersten Ebene usw.
Jede „Generation“ besitzt halb so viele Knoten wie die
nächste, woraus folgt, dass höchstens lg N Generationen
existieren können. Folglich können alle Operationen mit
Prioritätswarteschlangen (mit Ausnahme von join ) bei
Verwendung von Heaps in logarithmischer Zeit ausgeführt
werden.
G.Heyer
14
Algorithmen und Datenstrukturen II
Algorithmen mit Heaps
Die Algorithmen für Prioritätswarteschlangen unter
Verwendung von Heaps laufen alle in der Weise ab, dass sie
zuerst eine einfache Strukturänderung vornehmen, welche die
Heap-Bedingung verletzen könnte, und anschließend den
Heap durchlaufen und ihn so modifizieren, dass die Erfüllung
der Heap-Bedingung überall gewährleistet ist.
Einige Algorithmen durchlaufen den Heap von der untersten
Ebene zur Spitze, andere von der Spitze nach unten.
Bei allen Algorithmen wird vorausgesetzt, dass die Datensätze
aus einem Wort bestehende ganzzahlige Schlüssel sind, die in
einem Feld a von einer maximalen Größe gespeichert sind,
wobei die aktuelle Größe des Heaps in einer ganzen Zahl N
gespeichert ist.
G.Heyer
15
Algorithmen und Datenstrukturen II
Einfügen eines neuen Elements ( P ) in einen Heap
X
T
P
G
A
G.Heyer
S
E
R
O
A
16
I
N
M
Algorithmen und Datenstrukturen II
Aufbau eines Heaps
- Implementierung der Operation insert
- Inkrementierung von N (da sich die Größe des Heaps um
1 erhöht)
- Ablegen des einzufügenden Datensatzes in a[N]
- falls die Heap-Bedingung verletzt ist (der neue Knoten ist
größer als sein Vorgänge), Austausch der Knoten
- falls immer noch Verletzung der Heap-Bedingung, dann
wieder Austausch der Knoten usw. bis alle Knoten in
der geforderten Reihenfolge liegen.
G.Heyer
17
Algorithmen und Datenstrukturen II
Beispiel-Programm
In diesem Programm fügt insert ein neues Element in a[N]
ein und ruft dann upheap (N) auf, um die Verletzung der
Heap-Bedingung in N zu korrigieren.
upheap ( int k)
{
int v ;
v = a [k] ; a [0] = INT_MAX;
while (a [k/2] <= v)
{
a [k] = a [k/2] ; k = k/2 ; }
a [k] = v ;
}
insert (int v)
{
G.Heyer
a [++ N] = v ; upheap (N) ; }
18
Algorithmen und Datenstrukturen II
Die replace-Operation erfordert das Ersetzen des Schlüssels
bei der Wurzel durch einen neuen Schlüssel und danach die
Bewegung im Heap von oben nach unten, um die HeapBedingung wieder herzustellen.
Die Operation „remove the largest“ (Entfernen des größten
Elements) erfordert nahezu den gleichen Prozess.
Da der Heap nach der Operation ein Element weniger haben
wird, ist es erforderlich, N zu dekrementieren.
Im Mittelpunkt der Implementation dieser beiden
Operationen steht der Prozess der Korrektur eines Heap, in
dem die Heap-Bedingung überall erfüllt ist, außer
möglicherweise an der Wurzel.
Wenn der Schlüssel an der Wurzel zu klein ist, muss er im
Heap nach unten bewegt werden, ohne dass dabei die HeapBedingung bei irgendeinem der berührten Knoten verletzt
wird.
G.Heyer
19
Algorithmen und Datenstrukturen II
Weitere Implementationen sind:
downheap (int k)
{ int j, v ;
v = A [k];
while ( k <= N/2 )
{ j=k+k;
if ( j < N && a [j] < a [j+1] ) j++ ;
if ( v >= a[j] ) break;
a [k] = a [j]; k = j ; }
a [k] = v ;
}
Diese Prozedur bewegt sich im Heap nach unten, wobei der
Knoten auf Position k, falls erforderlich, mit dem größeren
seiner beiden Nachfolger, ausgetauscht wird. Dieser Prozess
bricht ab, wenn der Knoten auf Position k größer ist als beide
Nachfolger oder wenn die unterste Ebene erreicht ist.
G.Heyer
20
Algorithmen und Datenstrukturen II
Bei der remove-Operation wird folgende Implementation
angewendet:
int remove ( )
{ int v= a[1] ;
a[1] = a[N --] ;
downheap (1) ;
return v ; }
Die Implementation der replace-Operation hat folgende Form:
int replace (int v)
{ a[0] = v ;
downheap (0) ;
return a[0] ;
}
G.Heyer
21
Algorithmen und Datenstrukturen II
Eigenschaften des Heap:
Alle grundlegenden Operationen insert, remove, replace,
(downheap und upheap), delete und change erfordern
weniger als 2 lg N Vergleiche, wenn sie für einen Heap mit
N Elementen ausgeführt werden.
Alle diese Operationen beinhalten eine Bewegung längs
eines Pfades zwischen der Wurzel und der untersten Ebene
des Heap, welcher für einen Heap der Größe N nicht mehr
als lg N Elemente umfasst.
Der Faktor 2 entsteht durch downheap, welches in seiner
inneren Schleife zwei Vergleiche ausführt; die anderen
Operationen erfordern nur lg N Vergleiche.
G.Heyer
22
Algorithmen und Datenstrukturen II
Heapsort
Auf der Grundlage der bisher dargestellten grundlegenden
Operationen mit Heaps kann eine elegante und effiziente
Sortiermethode definiert werden. Diese Heapsort genannte
Methode, verwendet keinen zusätzlichen Speicherplatz und
garantiert, dass M Elemente in ungefähr M log M Schritten
sortiert werden, unabhängig von den Eingabedaten.
Die Idee besteht darin, einen Heap aufzubauen, der die zu
sortierenden Elemente enthält, und sie danach alle in der
richtigen Reihenfolge zu entfernen.
Eine Möglichkeit des Sortierens besteht darin, die Elemente
nacheinander in einen ursprünglich leeren Heap einzufügen,
wie in den ersten zwei Zeilen des folgenden Codes (welche in
Wirklichkeit nur construct (a, N) implementiert), und danach
n remove-Operationen auszuführen, wobei das entfernte
Element auf den Platz gesetzt wird, der von dem sich
verkleinernden Heap gerade freigegeben wurde.
G.Heyer
23
Algorithmen und Datenstrukturen II
heapsort (int a [], int N )
{ int k;
construct (a, 0);
for ( k = 1 ; k <= N ; k++) insert ( a [k] ) ;
for (k = N; k >= 1; k --) a [k] = remove ( ) ;
}
A
S
A
S
A
T
O
S
O ...
A
R
...
I
Beispiel: ASORTINGEXAMPLE
G.Heyer
24
Algorithmen und Datenstrukturen II
Vollständiger Heap ,
wenn die Schlüssel in der angegebenen Reihenfolge in einen
ursprünglich leeren Heap eingefügt werden.
X
T
P
G
A
S
E
R
O
A
I
N
M
L
E
In Wirklichkeit ist es besser, den Heap aufzubauen, in dem
man ihn rückwärts durchläuft und kleine Heaps von unten
her erzeugt. Bei dieser Methode wird jede Position im Feld
als Wurzel eines kleinen Heaps betrachtet, und es wird die
Tatsache ausgenutzt, dass downheap für solche kleinen Heaps
ebenso gut arbeitet wie für den großen Heap. Indem der
Heap rückwärts durchlaufen wird, ist jeder Knoten die
Wurzel eines Heaps, für den die Heap-Bedingung erfüllt ist.
G.Heyer
25
Algorithmen und Datenstrukturen II
remove kann implementiert werden,
in dem zunächst das erste und das letzte Element vertauscht
werden, dann N dekrementiert und schließlich downheap (1)
aufgerufen wird.
Dies führt zu folgender Implementation von Heapsort:
heapsort ( int a [ ] , int N )
{
int k, t ;
for ( k = N / 2 ; k >= 1 ; k -- ) downheap (a, N , k ) ;
while ( N > 1)
{ t = a [ 1 ] ; a [1] = a [N] ; a [N] = t ;
downheap (a , --N, 1 ) ;
}
}
G.Heyer
26
Algorithmen und Datenstrukturen II
Eigenschaft: Bottom Up Aufbau eines Heaps erfolgt in
linearer Zeit.
Der Grund für diese Eigenschaft besteht darin, dass die meisten
bearbeiteten Heaps klein sind.
Um zum Beispiel einen Heap aus 127 Elementen aufzubauen,
ruft die Methode dawnheap für (64 Heaps der Größe 1), 32
Heaps der Größe 3, 16 Heaps der Größe 7, 8 Heaps der Größe
15, 4 Heaps der Größe 31, 2 Heaps der Größe 63 und einen Heap
der Größe 127 auf, so dass im ungünstigsten Fall
64 * 0 + 32 * 1 + 16 * 2 + 8 * 3 + 4 * 4 + 2 * 5 + 1 * 6 = 120
„Übertragungen“ (doppelt so viele Vergleiche ) benötigt werden.
Für N = 2 ist eine obere Schranke für die Anzahl der Vergleiche
durch
 (k - 1 ) 2 n - k = 2 n - n - 1 < N
1 k n
gegeben. Ein ähnlicher Beweis gilt, wenn N
keine Zweierpotenz ist.
G.Heyer
27
Algorithmen und Datenstrukturen II
Eigenschaft: Heapsort benötigt für das Sortieren von
N Elementen weniger als 2N lg N Vergleiche.
Durch diese Eigenschaft ist Heapsort von praktischem
Interesse.
Die Anzahl der benötigten Schritte für das Sortieren von
N Elementen ist für beliebige Eingabedaten garantiert
proportional zu N log N.
Im Unterschied zu anderen behandelten Methoden gibt es
keine „ungünstigen“ Eingabedaten, bei denen Heapsort
langsamer abläuft.
G.Heyer
28
Algorithmen und Datenstrukturen II
Indirekte Heaps
Bei vielen Anwendungen von Prioritätswarteschlangen möchte
man die Datensätze nicht verschieben. Stattdessen möchte
man, dass die Prioritätswarteschlangen-Routine nicht Werte
zurück gibt, sondern anzeigt, welcher der Datensätze der
größte ist. Das ist mit der schon beschriebenen Idee des
„indirekten Sortierens“ oder des „Pointer Sort“ (ZeigerSortieren) vergleichbar. Es ist zweckmäßig, Heaps auf diese
Art zu nutzen.
Hier arbeiten die Prioritätswarteschlangen-Routinen, anstatt
die Schlüssel im Feld a umzuordnen, mit einem Feld p von
Indizes, die sich auf das Feld a beziehen.
a [p[k]] ist somit der Datensatz, der dem k-ten Element des
Heap entspricht, für 1  k  N .
G.Heyer
29
Algorithmen und Datenstrukturen II
Außerdem wird ein Feld q eingeführt, in dem die HeapPosition des k-ten Elements des Feldes gespeichert wird,
damit werden die Operationen delete und change ermöglicht.
Der Eintrag von q für das größte Element im Feld ist die
Zahl 1.
Indirekte Heap-Datenstrukturen
k
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a [k]
A S O R T I N G E X A M P L E
p[k]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
a [p[k]] X T P R S O N G E A A M I L E
q[k]
10 5 6 4 2 13 7 8 9 1 11 12 3 14 15
Die Implementationen werden entsprechend modifiziert.
G.Heyer
30
Algorithmen und Datenstrukturen II
Weiterentwickelte Implementationen der join-Operation
Wenn die join-Operation effizient ausgeführt werden muss, sind
die bisher angegebenen Implementationen unzureichend und es
werden weiter entwickelte Techniken benötigt.
Unter „effizient“ verstehen wir, dass join ungefähr in der gleichen
Zeit ausgeführt werden sollte wie die anderen Operationen. Die
bisherigen Methoden scheiden aus, da zwei große Heaps nur
zusammen gefügt werden können, indem alle Elemente
mindestens eines Heaps in ein großer Feld bewegt werden.
Bei einer direkten Darstellung mit Verkettungen müßten in jedem
Knoten Verkettungen vorgesehen werden, die auf den Vorgänger
und beide Nachfolger zeigen.
Es erweist sich, dass die Heap-Bedingung selbst zu stark zu sein
scheint, als dass eine effiziente Implementation der joinOperation möglich wäre. Alle höherentwickelten Datenstrukturen, die zur Lösung dieses Problems bestimmt sind,
schwächen entweder die Heap-Bedingung oder die
Ausgewogenheits-Bedingung ab, um die für join benötigte
Flexibilität zu erhalten. Diese Strukturen gestatten die
Ausführung aller Operationen in logarithmischer Zeit.
G.Heyer
31
Algorithmen und Datenstrukturen II
Herunterladen