Startseite < Informatik < Algorithmen < Sortieren < [ DRUCK , 2004 , 2005 , 2006 , 2007 , 2008 , 2009 ] BucketSort RadixSort / QuickSort MergeSort HeapSort / BubbleSort SelectionSort InsertionSort / SlowSort BogoSort > Komprimieren Suchen / Primzahltest > Datenstrukturen / Software-Engineering / Programmiersprachen / Künstliche Intelligenz > Schach Privates / Inhalt >
BucketSort
BucketSort
Content von Jürgen Henning.
Allgemein
Beim BucketSort hat man eine Eingabeliste, die sortiert werden soll, und eine Reihe Ablagefächer, die aus traditionellen Gründen Eimer genannt werden. Um die Datensätze auf die Eimer verteilen zu können benötigt man eine Funktion, die angibt, in welchen Eimer ein aktueller Datensatz hinein soll. Und diese Funktion muss die nachfolgende Relation erfüllen, damit das Sortieren klappen kann.
  1. a < b < c
  2. f(a) < f(b) < f(c)
a, b und c stellen hierbei beliebige Schlüsselwerte dar und in der nächsten Zeile stehen die Werte, die von der Verteilerfunktion zurück gegeben werden. Wenn man m Eimer hat und sie durchnummeriert, dann erzeugt die Verteilerfunktion einen positiven ganzzahligen Wert, der zwischen 1 und m liegt.

Normalerweise befinden sich nach einem Sortierdurchgang Datensätze mit unterschiedlichen Schlüsselwerten im gleichen Eimer. BucketSort ist also in seiner Grundform kein vollständiger Sortieralgorithmus; allerdings mit einer Ausnahme: es gibt genauso viele Eimer wie mögliche Schlüsselwerte. Wird nach vorzeichenlosen Integer-Zahlen mit 16-Bit Breite sortiert, benötigt man 65.536 Eimer, damit die Sortierung in einem Durchgang erfolgen kann, die Zeit steigt also linear mit der Anzahl der Datensätze an.

Der Algorithmus arbeitet stabil und die Zeit für eine Sortierung ist völlig unabhängig von der Verteilung der Schlüsselwerte.
Timing
Im Normalfall befinden sich, wie schon erwähnt, nach einem Sortierdurchgang Datensätze mit verschiedenen Schlüsselwerten in einem Eimer; die Inhalte müssen also ihrerseits noch sortiert werden. Diese interne Sortierung kann natürlich wiederum mittels BucketSort erfolgen, allerdings muss die Verteilerfunktion an die unterschiedlichen Wertebereiche angepasst werden. In Abhängigkeit von dieser Verteilerfunktion verändert sich das Verhalten von BucketSort völlig.

Falls nach positiven 8-Bit Zahlen sortiert wird, 256 Eimer verwendet werden, und der Wert des Schlüssels direkt für die Adressierung der Eimer verwendet wird, dann kann BucketSort nicht von ExtraDix unterschieden werden. Dementsprechend ergibt sich das zeitliche Verhalten durch
  1. T = E * n * lg256(N) = E * n
wobei E eine Konstante ist, die sich aus der Implementierung ergibt. Die benötigte Zeit steigt also linear mit der Anzahl der Datensätze.

Jetzt benutzen wir zwei Eimer und benutzen einen Pivotwert für die Verteilung. Ist der Wert des Schlüssels kleiner oder gleich dann wandert der Datensatz in den ersten Eimer, sonst in den zweiten. Die Sortierfunktion wird rekursiv aufgerufen. BucketSort hat sich in QuickSort verwandelt. Folglich ergibt sich das zeitliche Verhalten zu
  1. T = C * n * lg2(n)
wobei C wiederum eine Konstante ist, die die Implementation widerspiegelt.

Wir benutzen zehn Eimer; die Verteilerfunktion wandelt die Schlüsselwerte in Dezimalzahlen um und gibt, entsprechend zur Rekursionstiefe, eine Ziffer zurück, wobei mit der höchstwertigen Ziffer begonnen wird. Diese Ziffer wird direkt zur Bestimmung des Zieleimers benutzt. BucketSort verhält sich jetzt wie ein umgekehrter RadixSort. Das zeitliche Verhalten ergibt sich über
  1. T = D * n * lg10(N)
wobei D wiederum eine Konstante ist, die die Implementation widerspiegelt.

Wir benutzen zehn Eimer; die Verteilerfunktion wandelt die Schlüsselwerte in Dezimalzahlen um und gibt, entsprechend zur Rekursionstiefe, eine Ziffer zurück, wobei mit der niederwertigen Ziffer begonnen wird. Diese Ziffer wird direkt zur Bestimmung des Zieleimers benutzt. BucketSort verhält sich jetzt exakt wie RadixSort. Das zeitliche Verhalten ergibt sich über
  1. T = D * n * lg10(N)
wobei D wiederum eine Konstante ist, die die Implementation widerspiegelt.

Die übliche Art, wie BucketSort in rekursiver Weise verwendet werden kann, arbeitet mit einer fest vorgegebenen Anzahl von Eimern auf jedem Rekursionslevel. Es werden die Schlüssel aller Datensätze betrachtet, um den kleinsten und den größten Schlüsselwert zu bestimmen. Dann wird die Differenz zwischen diesen beiden Werten gleichmäßig in m Intervalle aufgeteilt und für jedes Intervall ein Startwert ermittelt. Die Verteilerfunktion besteht aus einer Sequenz verschachtelter If-Abfragen; ist der Schlüsselwert größer als x dann geht der Datensatz in den Eimer m, ist der Schlüsselwert größer als y dann geht er in den Eimer m-1 und so weiter. Der Inhalt der Eimer wird rekursiv wiederum auf m Unter-Eimer verteilt. Das zeitliche Verhalten ergibt sich zu
  1. T = E * n * lgm(n)
wobei E wiederum eine Konstante ist, die die Implementation widerspiegelt.
Beispiel
Für das Beispiel verwenden wir die Variante, die Quicksort mit zwei Eimern simuliert. Die Anzahl der Rekursionslevel wird dynamisch ermittelt. Wir starten mit den folgenden Daten:

Eingabeliste:
  1. 387 310 529 174 011 227 020 901 522 771
Der Durchschnittswert der Schlüssel beträgt 385; daher wandern alle Datensätze deren Schlüssel kleiner oder gleich 385 ist in den ersten Eimer und alle anderen in den zweiten Eimer.
  • Eimer 1:
    1. 310 174 011 227 020
  • Eimer 2:
    1. 387 529 901 522 771
Nun wird der Inhalt von Eimer 1 weiter behandelt. Der Durchschnittswert der Schlüssel beträgt 148; daher wandern alle Datensätze deren Schlüssel kleiner oder gleich 148 ist in den dritten Eimer und alle anderen in den vierten Eimer.
  • Eimer 3:
    1. 011 020
  • Eimer 4:
    1. 310 174 227
Nun wird der Inhalt von Eimer 3 weiter behandelt. Der Durchschnittswert der Schlüssel beträgt 15; daher wandern alle Datensätze deren Schlüssel kleiner oder gleich 15 ist in den fünften Eimer und alle anderen in den sechsten Eimer.
  • Eimer 5:
    1. 011
  • Eimer 6:
    1. 020
Jetzt kann Eimer 3 wieder zusammen geführt werden:
  • Eimer 3:
    1. 011 020
Wir machen mit Eimer 4 weiter. Der Durchschnittswert der Schlüssel beträgt 237; daher wandern alle Datensätze deren Schlüssel kleiner oder gleich 237 ist in den siebten Eimer und alle anderen in den achten Eimer.
  • Eimer 4:
    1. 310 174 227
  • Eimer 7:
    1. 174 227
  • Eimer 8:
    1. 310
Wir machen mit Eimer 7 weiter. Der Durchschnittswert der Schlüssel beträgt 200; daher wandern alle Datensätze deren Schlüssel kleiner oder gleich 200 ist in den neunten Eimer und alle anderen in den zehnten Eimer.
  • Eimer 9:
    1. 174
  • Eimer 10:
    1. 227
Wir können diese beiden Eimer wieder zu Eimer 7 zusammen führen.
  • Eimer 7:
    1. 174 227
Eimer 8 kann nicht weiter aufgeteilt werden. Wir kombinieren ihn mit Eimer 7, um den Eimer 4 neu zu bilden.
  • Eimer 4:
    1. 174 227 310
Jetzt können Eimer 3 und Eimer 4 zusammen geführt werden, um wieder Eimer 1 zu bilden.
  • Eimer 1:
    1. 011 020 174 227 310
Auf die gleiche Art wie zuvor Eimer 1 wird jetzt auch Eimer 2 behandelt, wobei sich dieses Ergebnis einstellt.
  • Eimer 2:
    1. 387 522 529 771 901
Abschließend werden die Eimer 1 und Eimer 2 wieder kombiniert und ergeben die sortierte Liste:

Ausgabeliste:
  1. 011 020 174 227 310 387 522 529 771 901
Implementierung Hennings BucketSort
BucketSort

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   This is a demo program to show how good BucketSort can be.            *
 *                                                                         *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
 *   Copyright (C) 2010 by Ingenieurbüro Henning, Kiel, Germany            *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published     *
 *   by the Free Software Foundation.                                      *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


#include <stdio.h>

#define MAXKEYS    100000   // max. number of data records
#define MAXWLNG        25   // max. length of the words in the data record
#define MAXBUCKETS     20   // max. number of buckets

struct FiAd       // register of buckets with the star-points in chained lists
  {
  int  StartIndex;// element in chained list where the bucket starts
  int  LastIndex; // last element of the bucket (to find it fast)
  int  NumInFifo; // number of references in the fifo
  };

struct FiFo       // structure of the chained lists used as bucket
  {
  int  PosIndex;  // where in the table can this element be found
  int  Next;      // pointer to next element in this bucket
  };


struct DataRec    // the structure of the test data
  {
  char   TheChar;
  short  TheShint;
  int    TheInt;
  float  TheFloat;
  double TheDouble;
  int    ThePos;
  int    TheLength;
  char   TheKey[MAXWLNG + 1];
  };

// local subroutines
void BuckSortInt (struct DataRec* Source, struct FiFo* InFif, int Start,
                 int NumLines, int NumBuckets, int* NewPosis, int* NewPosNr);
void WriteText   (char * TextName);

struct DataRec DataRecs   [MAXKEYS];          // the input list (unsorted)
struct DataRec SortedRecs [MAXKEYS];          // the sorted output list
int    NumRecs;                               // number of data records

// the input and output files
char In_ListN_1 [40] = "/home/jdhenning/Database/EinList.bin";
char OutTextT_2 [40] = "/home/jdhenning/Database/BucketSort.txt";


int main(int argc, char *argv[])
  {
  struct FiFo Positions  [MAXKEYS];           // the sequence of records
  int         NewPosis   [MAXKEYS];           // where the
  int         RecLng = sizeof(struct DataRec);// size of one data record
  int         PosNr  = 0;                     // position to store result
  int         I, LastInt, NoBuckets;
  FILE        *fp;

  NumRecs   = 100000;                         // number of records to sort
  NoBuckets = 7;                              // number of recursion levels

  // we get the input data which we want to sort
  fp = fopen(In_ListN_1, "rb");
  if(fp != NULL)
    {
    fread(DataRecs, NumRecs * sizeof(struct DataRec), 1, fp);
    fclose(fp);
    }
   else
    {
    printf("something is wrong with the file\n");
    return;
    };

  // we set up a linked list with references to all records
  for(= 0; I < NumRecs; I++)                // filling linked list
    {
    Positions[I].PosIndex = I;                // record x is at position x
    Positions[I].Next     = I + 1;            // pointer to the next element
    };
  Positions[- 1].Next   = -1;               // end of the chained list

  // we indirectly sort the data records; correct sequence will be in NumRecs
  BuckSortInt(DataRecs, Positions, 0, NumRecs, NoBuckets, NewPosis, &PosNr);

  // we copy the records in correct order to the array SortedRecs
  for(= 0; I < NumRecs; I++)
    {
    memmove(&SortedRecs[I], &DataRecs[NewPosis[I]], RecLng);
    };

  // and we check for correct sorting
  LastInt = 0x80000000;
  for(= 0; I < NumRecs; I++)
    {
    if(SortedRecs[I].TheInt < LastInt)
      {
      printf("sorting not correct\n");
      return;
      };
    LastInt = SortedRecs[I].TheInt;
    };

  printf("sorting correct\n");
  WriteText(OutTextT_2);
  }


void BuckSortInt(struct DataRec* Source, struct FiFo* InFif, int Start,
                 int NumLines, int NumBuckets, int* NewPosis, int* NewPosNr)
  {
  int I, K, Index, LastLast, FirstFree, Next;
  int StepSize, MinVal, MaxVal, ActVal;
  long long Interval;
  int  BuckBorder[MAXBUCKETS];                // border values between buckets
  struct FiAd* FifoAdmin;                     // the fifo administration
  struct FiFo* Fifo;                          // the left fifo

  if(NumBuckets > MAXBUCKETS) return;

  // we reserve space for the chained lists, depending on length of table
  FifoAdmin = (struct FiAd*) malloc(NumBuckets * sizeof(struct FiAd));
  Fifo      = (struct FiFo*) malloc(NumLines   * sizeof(struct FiFo));

  for(= 0; K < NumBuckets; K++)
    {
    FifoAdmin[K].StartIndex = FifoAdmin[K].LastIndex  = -1;
    FifoAdmin[K].NumInFifo  = 0;
    };

  FirstFree = 0;                              // first free element

  // now we find the smallest and biggest element for the actual bucket
  MinVal = 0x7FFFFFFF;                        // min. value to biggest value
  MaxVal = 0x80000000;                        // max. value to smallest value
  Next   = Start;                             // this is the next element
  while(Next >= 0)                            // while bucket is not empty ...
    {
    Index = InFif[Next].PosIndex;             // next record of this bucket
    ActVal = Source[Index].TheInt;            // the key value of the record
    if(ActVal < MinVal) MinVal = ActVal;      // key smaller => swap value
    if(ActVal > MaxVal) MaxVal = ActVal;      // key bigger  => swap value
    Next = InFif[Next].Next;                  // ponter to next element
    };

  // only identical key values => the record numbers go directly to the result
  if(MinVal == MaxVal)                        // all keys identical ?
    {
    Next   = Start;                           // this is the next element
    while(Next >= 0)                          // while bucket is not empty ...
      {
      NewPosis[NewPosNr[0]] = InFif[Next].PosIndex;// final position of record
      NewPosNr[0]++;                          // we point to free element
      Next = InFif[Next].Next;                // next element of linked list
      };
    };

  // now we make the border-array ready
  Interval = (long long) MaxVal - (long long) MinVal; // we get the domain
  StepSize = Interval / NumBuckets;           // makes chunks of this size
  for(= NumBuckets - 1; I > 0; I--)         // loop over the buckets
    {
    BuckBorder[I] = MaxVal - StepSize;        // lower border of the bucket
    MaxVal = MaxVal - StepSize;               // lower border for next bucket
    };
  BuckBorder[I] = MinVal;                     // this way is save

  //now we distribute the records of the input bucket to the new sub-buckets
  Next   = Start;                             // this is the next element
  while(Next >= 0)                            // while bucket is not empty ...
    {
    Index = InFif[Next].PosIndex;             // next record of this bucket
    ActVal = Source[Index].TheInt;            // key value of the record
    for(= NumBuckets - 1; I >= 0; I--)
       {
       if(ActVal >= BuckBorder[I])            // pointing to corrrect bucket?
         {
         if(FifoAdmin[I].StartIndex < 0)      // already entries?
           {
           // this bucket is used the first time; we start the new linked list
           FifoAdmin[I].StartIndex  = FirstFree; // start and end
           FifoAdmin[I].LastIndex   = FirstFree; // are identical
           FifoAdmin[I].NumInFifo   = 1;      // one element in Fifo
           Fifo[FirstFree].PosIndex = Index;  // number of record to list
           Fifo[FirstFree].Next     = -1;     // no further elements in list
           FirstFree++;
           }
          else
           {
           // this bucket was already used; we add the reference
           LastLast                 = FifoAdmin[I].LastIndex; // last entry
           FifoAdmin[I].NumInFifo++;          // one element more in fifo
           FifoAdmin[I].LastIndex   = FirstFree; // new last element
           Fifo[LastLast].Next      = FirstFree; // linking ex-last element
           Fifo[FirstFree].PosIndex = Index;  // the record number
           Fifo[FirstFree].Next     = -1;     // the linked list ends here
           FirstFree++;
           };
         break;                               // record distributed
         };
       };
    Next = InFif[Next].Next;                  // next record of actual bucket
    };

  // we make the recursion and call this function for any subbucket
  for(= 0; I < NumBuckets; I++)
    {
    if(FifoAdmin[I].NumInFifo > 1)
      BuckSortInt(Source, Fifo, FifoAdmin[I].StartIndex,
                  FifoAdmin[I].NumInFifo, NumBuckets, NewPosis, NewPosNr);
     else
      if(FifoAdmin[I].NumInFifo == 1)
        {
        NewPosis[NewPosNr[0]] = Fifo[FifoAdmin[I].StartIndex].PosIndex;
        NewPosNr[0]++;
        }
    };

  // we must free the memory we got from the operating system
  free(Fifo);
  free(FifoAdmin);
  };


void WriteText(char * TextName)
  {
  FILE *fp;
  int I;
  char   TChar;
  short  TShint;
  int    TInt;
  float  TFloat;
  double TDouble;
  int    TPos;
  int    TLength;

  fp = fopen(TextName, "wb");
  if(fp != NULL)
    {
    for(= 0; I < NumRecs; I++)
      {
      TChar   = SortedRecs[I].TheChar;
      TShint  = SortedRecs[I].TheShint;
      TInt    = SortedRecs[I].TheInt;
      TFloat  = SortedRecs[I].TheFloat;
      TDouble = SortedRecs[I].TheDouble;
      TPos    = SortedRecs[I].ThePos;
      TLength = SortedRecs[I].TheLength;
      fprintf(fp, "%6d  %6d  %12d  %14e  %14e  %6d   %4d  %s\n", TChar,
      TShint, TInt, TFloat, TDouble, TPos, TLength, SortedRecs[I].TheKey);
      };
    fclose(fp);
    }
   else
    {
    printf("Ausgabedatei %s kann nicht angelegt werden\n", TextName);
    return;
    };
  };