Algorithmen und Datenstrukturen Übung 13: Binomial Queue Binomialbaum Definition: Ein Baum mit einem Knoten ist der Binomialbaum B0. Ein Baum mit zwei Knoten ist der Binomialbaum B1. Man erhält den Binomialbaum Bk, indem man die Wurzel der zwei Binomialbäume Bk-1 mit einer Kante verbindet. B0 B1 B2 B3 B4 Abb.: Binomialbäume B0, B1, B2, B3 und B4 Für einen Binomialbaum Bk gilt: Bk hat 2k Knoten. Die Wurzel von Bk hat den Grad k. Die Anzahl der Knoten in Tiefe t von einem Bk ist k t Binomialqueue Definition: Eine Menge von „Heap“ geordneten Binomialbäumen , von denen keine den gleichen Grad haben, heißt Binomialqueue, z.B.: 1. Eine Binomialqueue für 7 Elemente besteht aus B2 + B1 + B0 2. Eine Binomialqueue der Größe 13 kann durch B3 + B2 + B0 präsentiert werden. 3. Eine Binomialqueue mit 6 Elementen kann bspw. so aussehen H1 16 12 18 21 24 65 1 Algorithmen und Datenstrukturen Operationen Bestimmen des kleinsten Elements: Das kleinste Element kann nach Durchlaufen aller Baumwurzeln gefunden werden. Falls es N unterschiedliche Bäume gibt, kann das Minimum in O(logN) [ ZE] gefunden werden. Mischen 2er Binomialqeueues: H1 16 12 18 21 24 65 H2 13 14 23 26 51 24 65 B0 von H2 hat kein Gegenstück und kann daher nach H3 übernommen werden. H1 und H2 haben Binomialbäume der Höhe 1. Sie werden zusammengemischt. Der Baum mit der größeren Wurzel wird Teilbaum der kleineren. 14 26 16 18 Es entsteht in H3 ein Binomiallbaum der Höhe 2, in H3 gibt es keinen Baum der Höhe 1. H3 13 23 12 51 24 21 24 65 14 65 26 16 18 Einfügen. Das Einfügen von Knoten ist ein Spezialfall des Mischens. Es wird ein aus einem Knoten bestehender Baum erzeugt und ein Mischvorgang ausgeführt. Bsp.: Einfügen der Zahlen 1 bis 7 1 1 3 1 2 4 3 2 1 3 2 1 4 1 2 2 3 4 2 Algorithmen und Datenstrukturen Das Einfügen der 4 erfordert drei Schritte (2 Mischvorgänge und einen Stop-Vorgang. 5 1 2 3 4 5 1 6 2 3 4 7 5 1 6 2 3 4 Löschen des kleinsten Elements Zuerst wird der Baum mit der kleinsten Wurzel gesucht. Sie befindet sich bspw. im Baum Bk der Binomialqueue. Bk wird aus dem Wald der Binomialbaeume entfernt, dies führt zu H’. Aus B k wird die Wurzel entfernt, daraus resultieren B0, B1, Bk-1, die zusammen die Binomialqueue H’’ bilden. Abgeschlossen wird die Operation durch Mischen von H’ und H’’. Bsp.: H3 13 23 12 51 24 21 65 24 14 65 26 16 18 H’ 13 23 51 24 65 3 Algorithmen und Datenstrukturen H’’ 21 24 14 24 16 18 Implementierung Die Binomialqueue H3 13 23 12 51 24 21 24 65 14 65 26 16 18 wird folgendermaßen implementiert: 12 14 16 24 23 21 65 24 13 51 65 18 Die Binomialqueue ist durch einen Array von Binomialbäumen repräsentiert. Jeder Knoten von einem Binomialbaum in einer Binomialqueue umfasst Daten, das erste (linke) Kind und den rechten Nachbarn. // Knoten fuer Binomialbaeume in Binomial Queues class BinomialKnoten { // Freundliche Daten mit Zugriffsmoeglichkeit vom // gleichen Verzeichnis bzw. Paket Comparable element; // Daten im Knoten BinomialKnoten linkesKind; // Linkes Kind BinomialKnoten naechsterNachbar; // Rechtes Kind // Konstructor BinomialKnoten( Comparable dasElement ) { this( dasElement, null, null ); } BinomialKnoten( Comparable dasElement, BinomialKnoten lb, BinomialKnoten nb ) 4 Algorithmen und Datenstrukturen { element = dasElement; linkesKind = lb; naechsterNachbar = nb; } } Schnittstellen der Klasse Binomialqueue // // // // // // // // // // // // // // Die Klasse BinomialQueue CONSTRUCTION: with a negative infinity sentinel ******************OEFFENTLICHE OPERATIONEN********************* void insert( x ) --> Einfuegen x Comparable loescheMin( )--> Return und Rueckgabe des kleinsten Elements Comparable findeMin( ) --> Return und Rueckgabe des kleinsten Elements boolean istLeer( ) --> Return true, falls; sonst false boolean istVoll( ) --> Return true, falls voll; sonst false void macheLeer( ) --> Entferne alle Elemente vod mische( rhs ) --> Absorbiere rhs in den vorliegenden heap ****************** Fehler ******************************** Overflow, falls die Kapazitaet erschoepft ist 5 Algorithmen und Datenstrukturen Lösungen // // // // // // // // // // // // // // Die Klasse BinomialQueue CONSTRUCTION: with a negative infinity sentinel ******************OEFFENTLICHE OPERATIONEN********************* void insert( x ) --> Einfuegen x Comparable loescheMin( )--> Return und Rueckgabe des kleinsten Elements Comparable findeMin( ) --> Return und Rueckgabe des kleinsten Elements boolean istLeer( ) --> Return true, falls; sonst false boolean istVoll( ) --> Return true, falls voll; sonst false void macheLeer( ) --> Entferne alle Elemente vod mische( rhs ) --> Absord rhs into this heap ****************** Fehler ******************************** Overflow, falls die Kapazitaet erschoepft ist /** * Implementierung einer Binomialqueue. */ public class BinomialQueue { private static final int MAX_BAEUME = 14; private int aktuelleGroesse; // Anzahl elemente der Binomialqueue private BinomialKnoten [] dieBaeume; // Ein Array mit den Baumwurzeln /** * Erzeuge die Binomialqueue. */ public BinomialQueue( ) { dieBaeume = new BinomialKnoten[ MAX_BAEUME ]; macheLeer( ); } /** * Mische rhs in die Binomialqueue. * rhs wird leer. rhs muss sich unterscheiden * von der aufrufenden Binomialqueue. * Parameter rhs ist die andere Binomialqueue. * Ausnahme Overflow, falls das Ergebnis die Kapazitaet ueberschreitet */ public void mische( BinomialQueue rhs ) throws Overflow { if( this == rhs ) // return; if( aktuelleGroesse + rhs.aktuelleGroesse > kapazitaet( ) ) throw new Overflow( ); aktuelleGroesse += rhs.aktuelleGroesse; BinomialKnoten carry = null; for( int i = 0, j = 1; j <= aktuelleGroesse; i++, j *= 2 ) { BinomialKnoten t1 = dieBaeume[ i ]; BinomialKnoten t2 = rhs.dieBaeume[ i ]; int welcherFall = t1 == null ? 0 : 1; welcherFall += t2 == null ? 0 : 2; welcherFall += carry == null ? 0 : 4; switch( welcherFall ) { case 0: /* Keine Baeume */ case 1: /* Nur die aufrufende Binomialqueue */ break; case 2: /* Nur Parameter rhs */ dieBaeume[ i ] = t2; 6 Algorithmen und Datenstrukturen case 4: case 3: case 5: case 6: case 7: rhs.dieBaeume[ i ] = null; break; /* Only carry */ dieBaeume[ i ] = carry; carry = null; break; /* this und rhs */ carry = verbindeBaeume( t1, t2 ); dieBaeume[ i ] = rhs.dieBaeume[ i ] = null; break; /* this and carry */ carry = verbindeBaeume( t1, carry ); dieBaeume[ i ] = null; break; /* rhs und carry */ carry = verbindeBaeume( t2, carry ); rhs.dieBaeume[ i ] = null; break; /* Alle drei */ dieBaeume[ i ] = carry; carry = verbindeBaeume( t1, t2 ); rhs.dieBaeume[ i ] = null; break; } } for( int k = 0; k < rhs.dieBaeume.length; k++ ) rhs.dieBaeume[ k ] = null; rhs.aktuelleGroesse = 0; } /** * Rueckgabe des Ergebnis vom Mischen gleichgrosser b1 and 22. */ private static BinomialKnoten verbindeBaeume( BinomialKnoten b1, BinomialKnoten b2) { if( b1.element.compareTo( b2.element ) > 0 ) return verbindeBaeume( b2, b1 ); b2.naechsterNachbar = b1.linkesKind; b1.linkesKind = b2; return b1; } /** * Einfuegen in die Binomialqueue, Verwalten der heap Ordnung. * Parameter x, einzufuegendes Element. * Ausnahme Overflow, falls kapazitaet ueberschritten wird. */ public void insert( Comparable x ) throws Overflow { BinomialQueue einElement = new BinomialQueue( ); einElement.aktuelleGroesse = 1; einElement.dieBaeume[ 0 ] = new BinomialKnoten( x ); mische( einElement ); } /** * Finde das kleinste Element in der Binomialqueue. * Rueckgabe des kleinsten Elements, oder null, falls leer. */ public Comparable findeMin( ) { if( istLeer( ) ) return null; return dieBaeume[ findeMinIndex() ].element; } /** 7 Algorithmen und Datenstrukturen * Finde den Index des Baums, der das kleinste Element in der * Binomialqueue enthaelt. * Die Binomialqueue darf nicht leer sein. * Rueckgabe Index des Baums, der das kleinste Element enthaelt. */ private int findeMinIndex() { int i; int minIndex; for( i = 0; dieBaeume[ i ] == null; i++ ) ; for( minIndex = i; i < dieBaeume.length; i++ ) if( dieBaeume[ i ] != null && dieBaeume[ i ].element.compareTo( dieBaeume[ minIndex ].element ) < 0 ) minIndex = i; return minIndex; } /** * Entferne das kleinste Element aus der BinomialQueue. * Rueckgabe des kleinsten Elements, oder null, falls leer. */ public Comparable loescheMin( ) { if( istLeer( ) )return null; int minIndex = findeMinIndex(); Comparable minItem = dieBaeume[ minIndex ].element; BinomialKnoten deletedTree = dieBaeume[ minIndex ].linkesKind; BinomialQueue deletedQueue = new BinomialQueue( ); deletedQueue.aktuelleGroesse = ( 1 << minIndex ) - 1; for( int j = minIndex - 1; j >= 0; j-- ) { deletedQueue.dieBaeume[ j ] = deletedTree; deletedTree = deletedTree.naechsterNachbar; deletedQueue.dieBaeume[ j ].naechsterNachbar = null; } dieBaeume[ minIndex ] = null; aktuelleGroesse -= deletedQueue.aktuelleGroesse + 1; try { mische( deletedQueue ); } catch( Overflow e ) { } return minItem; } /** * Test, ob die Binomialqueue leer ist. * Rueckgabe true, falls empty,anderenfallsfalse. */ public boolean istLeer( ) { return aktuelleGroesse == 0; } /** * Test, ob die Binomialqueue voll ist. * Rueckgabe true, falls voll, anderenfall false. */ public boolean istVoll( ) { return aktuelleGroesse == kapazitaet( ); } /** * Mache die Binomialqueue leer. */ 8 Algorithmen und Datenstrukturen public void macheLeer( ) { aktuelleGroesse = 0; for( int i = 0; i < dieBaeume.length; i++ ) dieBaeume[ i ] = null; } /** * Rueckgabe kapazitaet. */ private int kapazitaet( ) { return ( 1 << dieBaeume.length ) - 1; } } Die Klasse BinomialQueueTest public class BinomialQueueTest { public static void main( String [ ] args ) { int anzElemente = 10000; BinomialQueue h = new BinomialQueue( ); BinomialQueue h1 = new BinomialQueue( ); int i = 37; System.out.println( "Starting check." ); try { for( i = 37; i != 0; i = ( i + 37 ) % anzElemente ) if( i % 2 == 0 ) h1.insert( new Integer( i ) ); else h.insert( new Integer( i ) ); h.mische( h1 ); for( i = 1; i < anzElemente; i++ ) if( ((Integer)( h.loescheMin( ) )).intValue( ) != i ) System.out.println( "Oops! " + i ); } catch( Overflow e ) { System.out.println( "Unexpected overflow" ); } System.out.println( "Check done." ); } } 9