Systemnahe Programmierung in Chome Systemnahe Programmierung in C: Rot-Schwarz-Bäume Prof. Dr. Uwe Schmidt FH Wedel

Rot-Schwarz-Bäume

weiter

weiter

Implementierung von Rot-Schwarz-Bäumen

Rot-Schwarz-Bäume
sind Binärbäume, die beim Einfügen "on the fly" ausbalanciert werden.
 
Zusätzlich zu Binärbäumen wird in jedem Knoten eine Farbe gespeichert, und zwar gibt es rote und schwarze Knoten. Diese Zusatzinformation wird nur beim Einfügen und Löschen von Elementen berücksichtigt. Das Suchen läuft wie bei einfachen binären Bäumen.
 
Ein Rot-Schwarz-Baum muss folgende Balance-Invarianten erfüllen:
  1. Kein roter Knoten besitzt einen roten Kindknoten.
  2. Jeder Pfad von der Wurzel zu einem leeren Baum enthält die gleiche Anzahl von schwarzen Knoten.
Leere Bäume werden immer als schwarz eingefärbt betrachtet.
 
Diese beiden Invarianten garantieren, dass der längste mögliche Pfad in einem Rot-Schwarz-Baum, einer mit abwechselnd schwarzen und roten Knoten, höchstens doppelt so lang sein kann, wie der kürzeste, der nur schwarze Knoten enthält.
 
Daraus folgt, dass die maximale Tiefe eines Baumes mit n Elementen höchstens 2 * log2 (n + 1) sein kann.
 
Beim Einfügen wird der Baum auf dem Weg von der Einfügestelle zurück zur Wurzel an den Stellen ausbalanciert, an denen zwei rote Knoten entstanden sind. Neu eingefügte Knoten werden immer als rot markiert.
 
Das Löschen läuft prinzipiell genauso ab wie das Einfügen, nur können dabei doppelt schwarze Knoten entstehen, d.h. schwarze Knoten, die beim Zählen das Gewicht 2 haben. Diese werden durch lokale Umstrukturierung auf dem Rückweg zur Wurzel wieder eliminiert. Die Löschroutinen werden in diesem Beispiel nicht behandelt.
 
Die Typdefinition ist eine Erweiterung der für binäre Bäume: Pro Knoten wird zusätzlich eine Farbe gespeichert.
 
Diese Beispiel-Implementierung lässt noch Speilraum für lokale Optimierungen. Einmal können die einfachen Funktionen, insbesondere die Hilfsfunktionen, zum Teil durch Makros einkopiert werden. Weiter kann das Ausbalancieren vebessert werden: Wenn in den linken Teilbaum eingefügt wird, muss auch nur der linke Baum auf Ausgewogenheit getestet werden. Analoges gilt für den rechten Teilbaum. Die kritischen Funktionen erkennt man mit einem Profiling-Lauf.
weiter

weiter

Die Schnittstelle: Element.h

   1#ifndef ELEMENT_H__
   2#define ELEMENT_H__ 1
   3
   4typedef int Element;
   5
   6extern int compare(Element e1,
   7                   Element e2);
   8
   9#endif
weiter

weiter

Die Implementierung: Element.c

   1#include "Element.h"
   2
   3int
   4compare(Element e1Element e2)
   5{
   6  return (e1 >= e2) - (e2 >= e1);
   7}
weiter

weiter

Die Schnittstelle: Set.h

   1#ifndef SET_H__
   2#define SET_H__
   3
   4/*--------------------*/
   5
   6#include "Element.h"
   7
   8typedef struct Node *Set;
   9
  10struct Node
  11{
  12  enum
  13  { REDBLACK } color;
  14  Element info;
  15  Set l;
  16  Set r;
  17};
  18
  19/*--------------------*/
  20
  21extern Set mkEmptySet(void);
  22extern int isEmptySet(Set s);
  23
  24extern Set mkOneElemSet(Element e);
  25extern Set insertElem(Element eSet s);
  26
  27extern int isInSet(Element eSet s);
  28
  29extern unsigned int card(Set s);
  30
  31extern unsigned int maxPathLength(Set
  32                                  s);
  33extern unsigned int minPathLength(Set
  34                                  s);
  35
  36extern int invSetAsRedBlackTree(Set s);
  37
  38/*--------------------*/
  39
  40#endif
weiter

weiter

Die Implementierung: Set.c

   1#include "Set.h"
   2
   3#include <stdlib.h>
   4#include <stdio.h>
   5#include <assert.h>
   6
   7/*--------------------*/
   8
   9/* special empty node, marked as black */
  10
  11static struct Node finalNode = { BLACK }
  12
  13Set
  14mkEmptySet(void)
  15{
  16  return &finalNode;
  17}
  18
  19/* local optimization */
  20
  21#define mkEmptySet() (&finalNode)
  22
  23/*--------------------*/
  24
  25int
  26isEmptySet(Set s)
  27{
  28  return s == &finalNode;
  29}
  30
  31/* local optimization */
  32
  33#define isEmptySet(s) ((s) == &finalNode)
  34
  35/*--------------------*/
  36
  37/* auxiliary function: file scope */
  38
  39static Set
  40mkNewRedNode(Element e)
  41{
  42  Set res = malloc(sizeof(*res));
  43
  44  if (!res)
  45    {
  46      perror
  47        ("mkNewNode: malloc failed");
  48      exit(1);
  49    }
  50
  51  res->color = RED;             /* every new node is red */
  52  res->info = e;
  53  res->l = mkEmptySet();
  54  res->r = mkEmptySet();
  55
  56  return res;
  57}
  58
  59/*--------------------*/
  60
  61Set
  62mkOneElemSet(Element e)
  63{
  64  return insertElem(emkEmptySet());
  65}
  66
  67/*--------------------*/
  68
  69int
  70isInSet(Element eSet s)
  71{
  72  if (isEmptySet(s))
  73    return 0;
  74
  75  switch (compare(es->info))
  76    {
  77    case -1:
  78      return isInSet(es->l);
  79    case 0:
  80      return 1;
  81    case +1:
  82      return isInSet(es->r);
  83    }
  84
  85  assert(0);
  86  return 0;
  87}
  88
  89/*--------------------*/
  90
  91/* color predicates */
  92
  93#define isBlackNode(s) ((s)->color == BLACK)
  94#define isRedNode(s) (! isBlackNode(s))
  95
  96static int
  97hasRedChild(Set s)
  98{
  99  return
 100    ! isEmptySet(s)
 101    && (isRedNode(s->l)
 102        ||
 103        isRedNode(s->r));
 104}
 105
 106/*--------------------*/
 107
 108/* reorganize tree */
 109
 110static Set
 111balanceTrees(Set xSet ySet z,
 112             Set bSet c)
 113{
 114  x->r = b;
 115  z->l = c;
 116
 117  y->l = x;
 118  y->r = z;
 119
 120  x->color = BLACK;
 121  z->color = BLACK;
 122  y->color = RED;
 123
 124  return y;
 125}
 126
 127/* check invariant */
 128
 129/* check balance in left subtree */
 130
 131static Set
 132checkBalanceLeft(Set s)
 133{
 134  assert(!isEmptySet(s));
 135
 136  /* no balancing of trees with red root */
 137
 138  if (isRedNode(s))
 139    return s;
 140
 141  if (isRedNode(s->l)
 142      && isRedNode(s->l->l))
 143    return balanceTrees(s->l->l,
 144                        s->l,
 145                        s,
 146                        s->l->l->r,
 147                        s->l->r);
 148
 149  if (isRedNode(s->l)
 150      && isRedNode(s->l->r))
 151    return balanceTrees(s->l,
 152                        s->l->r,
 153                        s,
 154                        s->l->r->l,
 155                        s->l->r->r);
 156
 157  return s;
 158}
 159
 160/* check balance in right subtree */
 161
 162static Set
 163checkBalanceRight(Set s)
 164{
 165  assert(!isEmptySet(s));
 166
 167  /* no balancing of trees with red root */
 168
 169  if (isRedNode(s))
 170    return s;
 171
 172  if (isRedNode(s->r)
 173      && isRedNode(s->r->l))
 174    return balanceTrees(s,
 175                        s->r->l,
 176                        s->r,
 177                        s->r->l->l,
 178                        s->r->l->r);
 179
 180  if (isRedNode(s->r)
 181      && isRedNode(s->r->r))
 182    return balanceTrees(s,
 183                        s->r,
 184                        s->r->r,
 185                        s->r->l,
 186                        s->r->r->l);
 187
 188  return s;
 189}
 190
 191/*--------------------*/
 192
 193static Set
 194insertElem1(Element eSet s)
 195{
 196  if (isEmptySet(s))
 197    return mkNewRedNode(e);
 198
 199  switch (compare(es->info))
 200    {
 201    case -1:
 202      s->l = insertElem1(es->l);
 203
 204      /* invariant is checked with left subtree */
 205      return checkBalanceLeft(s);
 206
 207    case 0:
 208      break;
 209
 210    case +1:
 211      s->r = insertElem1(es->r);
 212
 213      /* invariant is checked with right subtree */
 214      return checkBalanceRight(s);
 215    }
 216
 217  return s;
 218}
 219
 220/*--------------------*/
 221
 222Set
 223insertElem(Element eSet s)
 224{
 225  Set res = insertElem1(es);
 226
 227  /* the root is always black */
 228
 229  res->color = BLACK;
 230
 231  assert(invSetAsRedBlackTree(res));
 232
 233  return res;
 234}
 235
 236/*--------------------*/
 237
 238static int
 239lessThan(Set sElement e)
 240{
 241  return
 242    isEmptySet(s)
 243    || (compare(s->infoe) == -1
 244        && lessThan(s->re));
 245
 246}
 247
 248/*--------------------*/
 249
 250static int
 251greaterThan(Set sElement e)
 252{
 253  return
 254    isEmptySet(s)
 255    || (
 256        compare(s->infoe) == +1
 257        &&
 258        greaterThan(s->le));
 259}
 260
 261/*--------------------*/
 262
 263static int
 264invSetAsBinTree(Set s)
 265{
 266  return
 267    isEmptySet(s)
 268    || (
 269        lessThan(s->ls->info)
 270        &&
 271        greaterThan(s->rs->info)
 272        &&
 273        invSetAsBinTree(s->l)
 274        &&
 275        invSetAsBinTree(s->r));
 276}
 277
 278/*--------------------*/
 279
 280static int
 281invNoRedNodeHasRedChild(Set s)
 282{
 283  if (isEmptySet(s))
 284    return 1;
 285
 286  return
 287    (isBlackNode(s)
 288     ||
 289     ! hasRedChild(s)
 290    )
 291    &&
 292    invNoRedNodeHasRedChild(s->l)
 293    &&
 294    invNoRedNodeHasRedChild(s->r);
 295}
 296
 297/*--------------------*/
 298
 299static int
 300noOfBlackNodes(Set s)
 301{
 302  if (isEmptySet(s))
 303    return 1;
 304
 305  {
 306    int nl = noOfBlackNodes(s->l);
 307    int nr = noOfBlackNodes(s->r);
 308
 309    if (nl == nr && nl != -1)
 310      return nl + isBlackNode(s);
 311
 312    /* invariant does not hold */
 313    return -1;
 314  }
 315}
 316
 317/*--------------------*/
 318
 319int
 320invSetAsRedBlackTree(Set s)
 321{
 322  return
 323    invSetAsBinTree(s)
 324    &&
 325    invNoRedNodeHasRedChild(s)
 326    &&
 327    noOfBlackNodes(s) != -1;
 328}
 329
 330/*--------------------*/
 331
 332unsigned int
 333card(Set s)
 334{
 335  if (isEmptySet(s))
 336    return 0;
 337
 338  return card(s->l) + 1 + card(s->r);
 339}
 340
 341/*--------------------*/
 342
 343static unsigned int
 344max(unsigned int iunsigned int j)
 345{
 346  return i > j ? i : j;
 347}
 348
 349/*--------------------*/
 350
 351static unsigned int
 352min(unsigned int iunsigned int j)
 353{
 354  return i < j ? i : j;
 355}
 356
 357/*--------------------*/
 358
 359unsigned int
 360maxPathLength(Set s)
 361{
 362  if (isEmptySet(s))
 363    return 0;
 364
 365  return
 366    1 + max(maxPathLength(s->l),
 367            maxPathLength(s->r));
 368}
 369
 370/*--------------------*/
 371
 372unsigned int
 373minPathLength(Set s)
 374{
 375  if (isEmptySet(s))
 376    return 0;
 377
 378  return
 379    1 + min(minPathLength(s->l),
 380            minPathLength(s->r));
 381}
 382
 383/*--------------------*/
weiter

weiter

Ein einfacher Test: Test.c

   1#include <stdio.h>
   2#include <assert.h>
   3
   4#include "Element.h"
   5#include "Set.h"
   6
   7int
   8main(int argcchar *argv[])
   9{
  10  unsigned int noOfElems = 1000;
  11
  12  if (argc == 2)
  13    sscanf(argv[1]"%u"&noOfElems);
  14
  15  fprintf(stderr,
  16          "%s %u %s",
  17          "run red black trees with inserting",
  18          noOfElems,
  19          "elements in ascending order\n"
  20          );
  21
  22  {
  23    Set s = mkEmptySet();
  24    unsigned int i;
  25
  26    for (i = 0; i < noOfElems++i)
  27      {
  28        s = insertElem((Element) is);
  29      }
  30
  31    fprintf(stderr,
  32            "card(s)          = %u\n",
  33            card(s));
  34    fprintf(stderr,
  35            "minPathLength(s) = %u\n",
  36            minPathLength(s));
  37    fprintf(stderr,
  38            "maxPathLength(s) = %u\n",
  39            maxPathLength(s));
  40  }
  41
  42  return 0;
  43}
weiter

weiter

Übersetzen:

make clean Test

weiter

weiter

worst case Test: Sortiertes Einfügen mit 7 Elementen

make run CARD=7

weiter

weiter

worst case Test: Sortiertes Einfügen mit 100.000 Elementen

make run CARD=100000

weiter

weiter

worst case Test: Sortiertes Einfügen mit 1.000.000 Elementen

make run CARD=1000000

weiter

weiter

worst case Test: Sortiertes Einfügen mit 2.000.000 Elementen

make run CARD=2000000

weiter

weiter

worst case Test: Sortiertes Einfügen mit 4.000.000 Elementen

make run CARD=4000000

weiter

weiter

worst case Test: Sortiertes Einfügen mit 8.000.000 Elementen

make run CARD=8000000

weiter

weiter

Testreihe: 1, 2, 4 und 8M Elemente

make -s pt

weiter

weiter

Test mit Profiling: Sortiertes Einfügen mit 1.000.000 Elementen

make prun CARD=1000000

weiter

weiter

Download

Quellen

Letzte Änderung: 19.12.2014
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel