Integraler Datenyp

Werbung
Algorithmen und Datenstrukturen
Übung 13: Priority Queues (Vorrangwarteschlangen1)
1. Definition und Operationen
Eine Priority Queue ist eine Datenstruktur zur Speicherung einer Menge von Elementen, für die eine
Halbordnung (Prioritätssteuerung) definiert ist, so dass folgende Operationen ausführbar sind
Initialisieren
Einfügen eines Elements (insert())
Minimum suchen (min() bzw. deleteMin())
Maximum suchen
Entfernen beliebiger Elemente (delete())
Herabsetzen eines Schlüssels um einen vorgegebenen Wert (decreaseKey-Operation). Hierbei wird
allerdings in der Regel vorausgesetzt, dass die Position der Schlüssel, die man entfernen möchte oder
dessen Wert erniedrigen möchte, bekannt ist.
Zusammenfügen (Verschmelzen) zweier elementfremder Priority Queues
2. (Java-) Interface für Priority Queues
interface PriorityQueue
{
boolean isEmpty ();
Object insert (Object p);
/* inserts a node with key equal to key and information attribute
equal to entry in the priority queue */
void meld (PriorityQueue Q);
/* the priority queue with the priority queue Q */
Object deleteMin ();
/* deletes the minimum and returns the entry with the minimum key */
void decreaseKey (double k, Object p);
/* decreases the key of p to k */
Object min ();
/* returns the entry with the minimum key */
void delete (Object p);
/* deletes the node with Object p in the priority queue */
int size ();
/* returns the number of entries in the priority queue */
void print ();
/* Erzeugt eine Ausgabe der Elemente in der priority queue */
}
1
vgl. Ottmann, T. u. Widmayer, P.: Algorithmen und Datenstrukturen, Mannheim / Wien / Zürich 1991
1
Algorithmen und Datenstrukturen
3. Heap-Implemtierung einer Priority-Queue
Heaps sind eine mögliche Implementierungsbasis für Priority-Queues. Ein Heap mit N Schlüsseln
erlaubt das Einfügen eines neuen Elements und das Entfernen des Minimums (bzw. Maximums) in
O(logN) Schritten. Da das Minimum (bzw. Maximum) stets am Anfang des Heap steht, kann die
Operation in konstanter Zeit ausgeführt werden.
Da Heaps eine sehr starre Struktur besitzen ist es besonders schwierig, zwei Heaps schnell zu einem
neuen zusammen zu fügen. Zwei Möglichkeiten bieten sich an:
1. Man kann sämtliche Elemente des kleineren Heap in den größeren Heap einfügen. Das ist in
O ( k ⋅ log( N + k )) Schritten ausführbar, wobei k die Anzahl der Elemente des kleineren Heap und N
die des größeren Heap ist.
2. Auflösen der vorhandenen Strukturen und Aufbau eines neuen Heap mit allen N+k Elementen. Der
Aufbau ist in O ( N + k ) Schritten durchführbar.
3.1 Implementierung einer Priority-Queue in C++ ("priority_queue" container adaptor) mit den HeapAlgorithmen der STL
neue Einträge
Eintrag
Eintrag
Eintrag
Eintrag
Eintrag
pop()
push()
niedrigste
...........
dritthöchste zweithöchste höchste
Priorität
Abb.: Eine Priority Queue
Die "priority_queue" nutzt die "Container"-Klassen "vector" bzw. deque.Typische Deklarationen
zu einer "priority_queue" sind:
priority_queue< vector<node> > x;
priority_queue< deque<string>,case_insensitive_compare > y;
priority_queue< vector<int>, greater<int> > z;
Die Implementierung der "priority-queue" in der STL-Version von Hewlett Packard zeigt folgende
Aussehen:
template <class Container, class Compare> class priority_queue
{
protected :
Container c;
Compare comp;
…
public :
bool empty() const { return c.empty(); }
size_type size() const { return c.size() }
value_type& top() const { return c.front(); }
void push(const value_type& x
{
c.push_back(x);
push_heap(c.begin(), c.end(), comp);
}
void pop()
{
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}
}
Die STL-Funktionen push_heap() und pop_heap() werden zur Implementierung herangezogen.
push() fügt das neue Element an das Ende des Container, push_heap() bringt danach das
2
Algorithmen und Datenstrukturen
Element an seinen richtigen Platz. pop() ruft zuerst pop_heap() auf. pop_heap() bringt das
Element an das Ende des Container, pop_back() entfernt das Element und verkürzt den Container.
Man kann aus dieser Anwendung ersehen, wie leicht es wurde, eine Container-Klasse mit Hilfe der
STL einzurichten.
Die "priority_queue" der STL ist ein Adapter und stützt sich ab auf die Klassen vector bzw.
deque.
template <class T, class Container = deque<T>,
class Compare = less<typename Container::value_type> >
class priority_queue
Präsentiert wird ein Interface, das zusätzlich zu den üblichen Container-Methoden fünf MemberFunktionen bereitstellt:
bool empty() const
gibt zurück, ob die Priority-Queue leer ist.
size_type size() const
gibt die Anzahl der in der
const value_type& top() const
gibt das erste Element zurück, d.h. das mit der größten Priorität
void push(const value_type& x)
fügt das Element x ein
void pop()
entfernt das erste Element in der Priority_Queue
Bsp.: Huffman Coding2 mit "priority_queue"-Container der STL
Gegeben ist eine Datei, z.B. mit folgendem Inhalt
AAAAAAAAAAAAAA
BBB
C
D
E
F
GGG
HHHH
Gesucht ist eine geeignete Binärcodierung (Code variabler Länge), die die Häufigkeit des Auftretens
der Zeichen berücksichtigt.
Der Huffman-Algorithmus realisiert die Binärcodierung mit Hilfe eines Binärbaums, der folgendes
Aussehen haben könnte:
2
vgl. 3.1.2.2
3
Algorithmen und Datenstrukturen
28
0
1
14
14
0
1
A
6
8
0
3
1
3
0
1
4
4
0
B
G
1
H
2
2
0
Abb.: Huffman-Codierungs-Baum
1 0
1
1
1
1
1
C
F
D
E
Jeder Knoten hat ein Gewicht, der den Platz im Huffman-Codierungs-Baum festlegt. Die STL
"priority_queue" wird zunächst zur Aufnahme der eingelesenen Zeichen benutzt. Je nach
Häufigkeit des Vorkommens der Zeichen erfolgt die Einordnung.
Danach werden die beiden Einträge (Knoten) mit der niedrigsten Priorität entfernt, ein neuer interner
Knoten (Addition der Gewichte) gebildet. Der neue Knoten wird wieder in die priority_queue
gebracht. Das wird solange wiederholt bis nur ein einziger Knoten in der priority_queue vorliegt.
//
// pqhuff.cpp
//
/* Das Programm liest Zeichen aus der Datei input.dat, baut
daraus einen Huffman-Codierungsbaum auf und benutzt dazu
die STL priority queue. Die resultierende Tabelle wird
dann ausgegeben
*/
#include <iostream.h>
#include <iomanip.h>
#include <fstream>
#include <vector>
#include <stack>
#include <queue>
#include <functional>
#include <string>
using namespace std;
// Die Knoten-Klasse
struct node
{
int weight;
unsigned char value;
const node *child0;
const node *child1;
// Konstruktor Blattknoten für Zeichen c
node(unsigned char c = 0,int i = -1)
{
value = c;
weight = i;
child0 = 0;
child1 = 0;
}
4
Algorithmen und Datenstrukturen
// Konstruktor interner Knoten mit den Nachfolgern c1 und c2
node(const node* c0, const node* c1)
{
value = 0;
weight = c0->weight + c1->weight;
child0 = c0;
child1 = c1;
}
// Vergleichsoperatoren zur Herstellung der
// Ordnungsbeziehung der priority queue
bool operator < (const node &a) const
{
return weight < a.weight;
}
bool operator > (const node &a) const
{
return weight > a.weight;
}
void traverse(string code = "") const;
};
// Die Member-Funktion traverse() dient zur Ausgabe
// des Code fuer einen gegebenen Knoten.
// Falls der Wurzelknoten angegeben ist, wird der
// ganze Baum ausgegeben.
void node::traverse(string code) const
{
if (child0)
{
child0->traverse(code + "0");
child1->traverse(code + "1");
}
else {
const char* s = code.c_str();
cout << " " << value << "
";
cout << setw(2) << weight;
cout
<< "
" << s << endl;
}
}
// Die folgende Routine zaehlt die Zeichen der
// Eingabe-Datei.
void count_chars(int * counts)
{
for (int i = 0; i < 256; i++)
counts[i] = 0;
ifstream file("input.dat");
if (!file)
{
cerr << "Kein Oeffnen der Eingabedatei moeglich!\n";
throw "abort";
}
file.setf(ios::skipws);
for (; ;)
{
unsigned char c;
file >> c;
if (file)
counts[c]++;
else
break;
}
}
main()
5
Algorithmen und Datenstrukturen
{
int counts[256];
count_chars(counts);
priority_queue< node, vector< node >, greater< node > > q;
//
// Einlagern der Blattkonten in die queue
//
for (int i = 0; i < 256; i++)
if (counts[i]) q.push(node( i, counts[i]));
//
// Die Schleife entfernt die beiden kleinsten Knoten
// aus der Schlange. Es wird ein neuer interner Knoten
// erzeugt, der diese beiden Knoten als Kinder besitzt
// Der neue interne Knoten wird dann in die priority
// queue eingefuegt. Falls es nur noch einen Knoten in der
// priority queue gibt, dann ist der Baum vollstaendig
//
while (q.size() > 1)
{
node *child0 = new node(q.top() );
q.pop();
node *child1 = new node(q.top() );
q.pop();
q.push(node(child0, child1));
}
// Ausgabe des Resultats
cout << "Zeichen Symbol
Code" << endl;
q.top().traverse();
return 1;
}
Abb.
6
Algorithmen und Datenstrukturen
3.2 Implementierung einer Priority-Queue in Java (auf der Basis eines Binary Heap)
Das Interface PriorityQueue für die Implementierung in einem Binary Heap
//
//
//
//
//
//
//
//
//
//
//
//
PriorityQueue interface
******************PUBLIC OPERATIONS*********************
Position insert( x )
--> Einfuegen x
Comparable deleteMin( )--> Rueckgabe und Entfernen kleinstes Element
Comparable findMin( ) --> Rueckgabe kleinstes Element
boolean isEmpty( )
--> Rueckgabe true, falls leer; anderenfalls false
void makeEmpty( )
--> Entfernen aller Elemente
int size( )
--> Rueckgabe aktuelle Groesse
void decreaseKey( p, v)--> Erhoehe den Wert in p um v
******************ERRORS********************************
Throws UnderflowException for findMin and deleteMin when empty
/**
* PriorityQueue interface.
* Vergleichsoperationen werden ueber compareTo() abgewickelt
* Author: Mark Allen Weiss, ueberarbeitet durch Juergen Sauer
*/
public interface PriorityQueue
{
/*
* The Position interface repraesentiert einen Typ, der
* evtl. fuer die Operation decreaseKey benutzt werden kann.
*/
public interface Position
{
/*
* Rueckgabe: Wert, der an dieser Position gespeicher ist.
*/
Comparable getValue( );
}
/*
* Einfuegen in die priority queue, bewahren der heap order.
* Duplikate sind zugelassen.
* Der Parameter x ist das einzufuegende Element.
* Rueckgabe: eine moeglicherweise nuetzliche Position fuer decreaseKey.
*/
Position insert( Comparable x );
/*
* Bestimme das kleinste Element in der Priority Queue.
* Rueckgabe: kleinster Wert.
* Falles leer, Auswurf UnderflowException.
*/
Comparable findMin( );
/*
* Entferne das kleinste Element aus der Priority Queue.
* Rueckgabe des kleinsten Elements.
* Auswurf UnderflowException, falls leer.
*/
Comparable deleteMin( );
/*
* Test, ob die Priority Queue keine Elemente mehr enthaelt.
* Rueckgabe true, falls leer, anderenfalls false.
*/
boolean isEmpty( );
/*
* Entferne alle Elemente aus der Priority Queue.
*/
7
Algorithmen und Datenstrukturen
void makeEmpty( );
/*
* Rueckgabewert: aktuelle Groesse.
*/
int size( );
/*
* Veraendern des Elements, das im pairing heap gespeichert ist.
* Parameter p: irgendeine non-null Position, zurueckgegen von insert.
* Parameter newVal: der neue Wert, der kleiner sein muss
* als der aktuell gespeicherte Wert.
* Auswurf: IllegalArgumentException , falls p ungueltig ist.
* Auswurf: UnsupportedOperationException, if appropriate.
*/
void decreaseKey( Position p, Comparable newVal );
}
Binary Heap, der dieses Interface implementiert
//
//
//
//
//
//
//
//
//
//
//
//
BinaryHeap class
KONSTRUKTION: Mit einem leeren oder mit Initialwerten gefuellten Array.
******************PUBLIC OPERATIONS*********************
void insert( x )
--> Insert x
Comparable deleteMin( )--> Return and remove smallest item
Comparable findMin( ) --> Return smallest item
boolean isEmpty( )
--> Return true if empty; else false
void makeEmpty( )
--> Remove all items
******************ERRORS********************************
Auswurf UnderflowException in findMin und deleteMin falls leer
/*
* Implements a binary heap.
* Vergleichsoperationen benutzen compareTo().
* author Mark Allen Weiss, ueberarbeitet von Juergen Sauer
*/
public class BinaryHeap implements PriorityQueue
{
/*
* Konstruktion Binary Heap.
*/
public BinaryHeap( )
{
currentSize = 0;
array = new Comparable[ DEFAULT_CAPACITY + 1 ];
}
/*
* Konstruiert den Binary Heap aus einenm array.
* Der Parameter items umfasst die initialen Daten
*/
public BinaryHeap( Comparable [ ] items )
{
currentSize = items.length;
array = new Comparable[ items.length + 1 ];
for( int i = 0; i < items.length; i++ )
array[ i + 1 ] = items[ i ];
buildHeap( );
}
/*
* Einfuegen in die Priority Queue.
* Duplikate sind erlaubt.
* Der Parameter x enthaelt das einzufuegende Datum
* Rueckgaberwert null, signalisiert decreaseKey kann nicht verwendet w.
8
Algorithmen und Datenstrukturen
*/
public PriorityQueue.Position insert( Comparable x )
{
if( currentSize + 1 == array.length )
doubleArray( );
// Percolate up
int hole = ++currentSize;
array[ 0 ] = x;
for( ; x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 )
array[ hole ] = array[ hole / 2 ];
array[ hole ] = x;
return null;
}
/*
* Auswurf UnsupportedOperationException, da keine Positions
* durch die Methode insert fuer den BinaryHeap
* zurueckgegeben werden.
*/
public void decreaseKey( PriorityQueue.Position p, Comparable newVal )
{
throw new UnsupportedOperationException(
"Cannot use decreaseKey for binary heap" );
}
/* Bestimme das kleinste Element
* in der Priority Queue.
* Rueckgabe des kleinsten Elements.
* Auswurf UnderflowException, falls leer.
*/
public Comparable findMin( )
{
if( isEmpty( ) )
throw new UnderflowException( "Empty binary heap" );
return array[ 1 ];
}
/*
* Entferne das kleinste Element aus der Priority Queue.
* Rueckgabe des kleinsten Elements.
* Auswurf UnderflowException, falls leer.
*/
public Comparable deleteMin( )
{
Comparable minItem = findMin( );
array[ 1 ] = array[ currentSize-- ];
percolateDown( 1 );
return minItem;
}
/*
* Einrichten einer Heap Ordungseigenschaft aus einer gegebenen
* Anordnung der Elemente. Laeuft in linearer Zeit.
*/
private void buildHeap( )
{
for( int i = currentSize / 2; i > 0; i-- )
percolateDown( i );
}
/*
* Test if the priority queue is logically empty.
* Rueckgabe true, falls leer, anderenfalls false.
*/
public boolean isEmpty( )
{
return currentSize == 0;
}
9
Algorithmen und Datenstrukturen
/*
* Rueckgabe aktuelle Groesse size.
*/
public int size( )
{
return currentSize;
}
/*
* Entfermen der Elemente.
*/
public void makeEmpty( )
{
currentSize = 0;
}
private static final int DEFAULT_CAPACITY = 100;
private int currentSize;
// Number of elements in heap
private Comparable [ ] array; // The heap array
/*
* Internal method to percolate down in the heap.
* param hole the index at which the percolate begins.
*/
private void percolateDown( int hole )
{
int child;
Comparable tmp = array[ hole ];
for( ; hole * 2 <= currentSize; hole = child )
{
child = hole * 2;
if( child != currentSize &&
array[ child + 1 ].compareTo( array[ child ] ) < 0 )
child++;
if( array[ child ].compareTo( tmp ) < 0 )
array[ hole ] = array[ child ];
else break;
}
array[ hole ] = tmp;
}
/*
* Interne Methode zur Array-Vergroesserung.
*/
private void doubleArray( )
{
Comparable [ ] newArray;
newArray = new Comparable[ array.length * 2 ];
for( int i = 0; i < array.length; i++ )
newArray[ i ] = array[ i ];
array = newArray;
}
// Test program
public static void main( String [ ] args )
{
int numItems = 10000;
BinaryHeap h1 = new BinaryHeap( );
Integer [ ] items = new Integer[ numItems - 1 ];
int i = 37;
int j;
for( i = 37, j = 0; i != 0; i = ( i + 37 ) % numItems, j++ )
{
h1.insert( new Integer( i ) );
items[ j ] = new Integer( i );
10
Algorithmen und Datenstrukturen
}
for( i = 1; i < numItems; i++ )
if( ((Integer)( h1.deleteMin( ) )).intValue( ) != i )
System.out.println( "Oops! " + i );
BinaryHeap h2 = new BinaryHeap( items );
for( i = 1; i < numItems; i++ )
if( ((Integer)( h2.deleteMin( ) )).intValue( ) != i )
System.out.println( "Oops! " + i );
}
}
3.3 java.util.PriorityQueue<E>
E – Typ des Elements, das in diese Collection aufgenommen wurde.
public class PriorityQueue<E> extends AbstractQueue<E>
implements Serializable
11
Herunterladen