часть вторая алгоритмы

структуры

списки

  • односвязный список
  • реверсирование односвязного списка
  • skip list
  • стеки
  • стек курильщика
  • стек здорового человека
  • хеши
  • таблица с прямой адресацией
  • таблица с цепочками
  • универсальная таблица
  • кучи
  • куча на массиве
  • leftist heap на бинарном дереве
  • skew heap
  • binomial heap на списке бинарных деревьев
  • деревья
  • бинарное дерево
  • AVL дерево
  • splay дерево
  • черно-красное дерево
  • декартово дерево
  • k-d дерево
  • глобалы
  • глобалы

  • списки

    "C" не имеет ссылок - только указатели. так что "C" не имеет механизма "pass-by-ref". но вы можете передавать указатель и насладиться

    карандашная надпись на обложке BlueBible

    односвязный список

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct elem Node ;
    
    struct elem { int val
                ; Node* next
                ; } ;
    
    int lookup (int) , insert (int) , delete (int)  ;
    
    Node *p ;               // pointer to the list
    Node *r ;               // pointer to the current position for insertion
    #define LEN 256         // initial lenght of the list
    
    int
    main (int argc , char** argv , char** envp)
    {
      r = p = malloc (LEN * sizeof (Node)) ;
      p->val = 0 ; p->next = NULL ;
    
      // some stuff here
    
      free (p) ;
      return 0 ;
    }
    
    int
    lookup (int val)
    {
      Node* s = p ;
    
      while (s->val != val && s->next) s = s->next ;
    
      if (s->val == val) return val ;
      else return 0 ;
    } ;
    
    int
    insert (int val)
    {
      if (r == p + LEN - 1) { printf ("overflow\n") ; return 1 ; } ;
    
      r->val = val ; r->next = NULL ;
      if (r != p) (r - 1)->next = r ;
      r += 1 ;
    
      return 0 ;
    } ;
    
    int
    delete (int val)
    {
      Node* s = p ;
    
      while (s->val != val && s->next) s = s->next ;
    
      if (s->next && s->val == val)                // start gc
      {
        while (s->next) { s->val = s->next->val ; s += 1 ; } ;
        r = s ;
        return 0 ;
      }
      else if (s->next == NULL && s->val == val)  // just reset pointer to succ elem
      {
        if (s != p) (s - 1)->next = NULL ;
        r = s ;
        return 0 ;
      }
    
      else return 1;  // not found
    } ;
    

    реверсирование односвязного списка

    как задача на собеседовании

    #include <stdio.h>
    
    typedef struct elem Node ; struct elem { int val ; Node* next ; } ;
    
    void prn (Node*), rreverse (Node*), ireverse (Node*), trreverse (Node*, Node*) ;
    
    int
    main (int argc, char** argv, char** envp)
    {
      Node a, b, c, d ;
    
      a.val = 10 ; a.next = &b ;
      b.val = 20 ; b.next = &c ;
      c.val = 30 ; c.next = &d ;
      d.val = 40 ; d.next = NULL ;
    
      // debug printing:
      prn (&a) ; rreverse (&a) ;
      prn (&d) ; ireverse (&d) ;
      prn (&a) ; trreverse (NULL, &a) ;
      prn (&d) ; return 0 ;
    }
    
    //           ***   recursive way:
    
    void
    rreverse (Node* p)
    {
      Node* r = p->next ;  // r - pointer to my current tail
      if (!r) return ;        // if no tail - r points on former last elem.
                              //it will not be changed anymore
      rreverse (r) ;
      p->next->next = p ; // change pointer of my former succ to point on me now
      p->next = NULL ;  // change my new succ to NULL, but NULL will be buble up continiously
      p = r ;       // change pointer to point on former last elem (see line 02)
                    // any time it will be different p, but just the same r
                    // at last p will be the global pointer
    }
    // NB: no tail recursion here, so the stack will grow up
    
    
    //      ***   tail recursive way:
    
    void
    trreverse (Node* a, Node* p)
    {
      Node* r = p->next ;
      p->next = a ;
      if (r) trreverse (p, r) ;
    }
    // if your compiler is TOC - you are win
    
    
    //     ***   iterative way:
    
    void
    ireverse (Node* p)
    {
      Node* iter ;
      Node* temp = NULL ;       // the first elem will put it in the next pointer
      Node* work = p ;          // for while loop only - pointer to the first elem
    
      while (work)             //           my pointer here
      {
        iter = work->next ;     // save      where to move on the next step
        work->next = temp ;     // rewrite   my own next-pointer
        temp = work ;           // remember  my adr
        work = iter ;           // for while loop only - pointer to the next elem
      }
    
      p = temp ;                // change global pointer to former last elem
    }
    
    
    //   ***   for debug output only
    void
    prn (Node* h)
    {
      while (h) { printf ("--> %i\t", h->val) ; h = h->next ; }
      printf ("\n") ;
    }
    

    skip list

    #include <stdio.h>
    #include <stdlib.h>
    
    #define SL_SIZE 17
    
    typedef struct elem Node ; struct elem { int val ; Node* next ; } ;
    Node* p ; Node* r ;
    
    typedef struct supelem Node1 ; struct supelem { int val ; Node1* next ; Node* down ; } ;
    Node1* p1 ; Node1* r1 ;
    
    int minval () ;
    int coin (int s) { srandom (s) ; int x = random () ; return 1 & x ; }
    
    int
    main (int argc, char** argv, char** envp)
    {
      // generating skip-list structure with size (SL_SIZE)
      p = malloc (SL_SIZE * sizeof (Node)) ;                 // for the first-level list
      p->next = p ;                                          // create null-sentinel
      r = p + 1 ;                                            // current pos
      p1 = malloc ((3 * SL_SIZE / 4) * sizeof (Node1)) ;     // for the second-level list
      p1->next = p1 ;                                        // create null-sentinel
      r1 = p1 + 1 ;                                          // current pos
    
      // some stuff here
    
      free (p) ; free (p1) ; return 0 ;
    }
    
    
    int
    insert1 (Node* x)
    {
      if (r1 - p1 > (3 * SL_SIZE / 4)) { return 1; } ;
    
      Node1 y ; y.val = x->val ; y.down = x ;
    
      // the first element insertion
      if (r1 == p1) { y.next = r1 ; *r1 = y ; r1 += 1 ; return 0 ; }
    
      Node1 *s = p1 ;  // for list traversing
      Node1 *t = p1 ;
    
      // all except the first and the last elements insertion
      while (s != s->next)
      {
        if (s->next->val > x->val)
        {
          y.next = s->next ;
          s->next = r1 ;
          *r1 = y ; r1 += 1 ; return 0 ;
        }
        t = s ;       // save as previos element pointer
        s = s->next ; // move to the next element
      };
    
      // the last element insertion
      if   (s->val < x->val) { s->next = r1 ; y.next  = r1 ; }
      else { y.next  = s ; t->next = r1 ; }
    
      *r1 = y ; r1 += 1 ; return 0 ;
    }
    
    
    int
    delete (int x)
    {
      if (x < minval ()) return 1 ;
    
      // at first in low-level list:
      Node *s = p ;  // for list traversing
      Node *t = p ;  //
    
      while (s != s->next)
      {
        if (s->val == x) { t->next = s->next ; return 0 ; }
        t = s; s = s->next ; // save previos pointer and increment
      }
      if (s->val == x) { t->next = t->next ; }
    
      // now in high-level list:
      Node1 *s1 = p1 ;  // for list traversing
      Node1 *t1 = p1 ;
    
      while (s1 != s1->next)
      {
        if (s1->val == x) { t1->next = s1->next ; return 0 ; }
        t1 = s1 ; s1 = s1->next ; // save previos pointer and increment
      }
      if (s1->val == x) { t1->next = t1->next ; return 0 ; }
    
      return 1 ;
    }
    
    
    int
    lookup (int x)
    {
      if (r == p + 1) return 0 ;
      if (x < minval ()) return 0 ;
    
      Node1* s1 = p1 ;
      Node1* t1 = p1 ;
      while (s1->next->val < x && s1->next != s1) s1 = s1->next ;
      if (s1->val == x) return 1 ;
      Node* s = s1->down ;
      while (s->next != s)
      {
        if (s->val == x) return 1 ;
        s = s->next ;
      } ;
    
      return 0 ;
    }
    
    
    int minval () { return p->next->val ; }
    
    
    int
    maxval ()
    {
      if (r == p + 1) return r->val ;
      Node1* s1 = p1 ;
      while (s1->next != s1) s1 = s1->next ;
      Node* s = s1->down;
      while (s->next != s) s = s->next ;
    
      return s->val ;
    }
    
    
    int
    insert (int x)
    {
      if (r - p > SL_SIZE) return 1 ;
    
      Node y ; y.val = x ;
    
      // the first element insertion
      if (r == p)
      {
        if (!x) return 1 ;
        else
        {
          y.next = r ; *r = y ;
          if (coin (x)) insert1 (r) ;
          r += 1 ; return 0 ;
        }
      }
    
      // the next element insertion
      Node *s = p ;  // for list traversing
      Node *t = p ;
    
      // all except the first and the last elements insertion
      while (s != s->next)
      {
        if (s->next->val > x)
        {
          if (s->val != x)
          {
            y.next = s->next ; s->next = r ; *r = y ;
            if (coin (x)) insert1 (r) ;
            r += 1 ;  return 0 ;
          }
          else return 1 ;
        };
        t = s ;       // save as previos element pointer
        s = s->next ; // move to the next element
      };
    
      // the last element insertion
      if (s->val  < x) { s->next = r ; y.next  = r ; }
      else if (s->val == x) return 1 ;
      else { y.next  = s ; t->next = r ; }
    
      *r = y ; r += 1 ; return 0 ;
    }
    

    стек

    ansi c
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    
    #define LEN 128
    
    int isEmpty (void) ;
    int push    (int) ;
    int pull    (void) ;
    
    int* p ;    // stack pointer
    int* r ;    // current position
    
    int
    main (int argc , char** argv , char** envp)
    {
      p = r = malloc (LEN  * sizeof (int)) ;
    
      // assert (isEmpty ()) ;
      assert (push (100)) ; assert (isEmpty()) ; assert (push (200)) ;
      int x = pull () ; assert (200 == x) ; assert (isEmpty ()) ;
      x = pull () ; assert (100 == x) ;
      // assert (pull ()) ;
    
      free (p) ;
      return 0 ;
    }
    
    int
    isEmpty (void)
    {
      if (p == r) return 0 ; else return 1 ;
    } ;
    
    int
    push (int x)
    {
      if (r == p + LEN - 1) { puts ("overflow\t") ; }
      else { r = r + 1 ; *r = x ; } ;
      return r - p ;
    } ;
    
    int
    pull ()
    {
      int x ;
      if (r == p) { puts ("empty\t") ; return r - p ; }
      else { x = *r ; r = r - 1 ; return x ; }
    } ;
    
    ocaml (fm Okasaki "PFDS"):
    module type Stack =
    sig
      type t
      type a
    
      val empty : a
      val isEmpty : a -> bool
      val push : t * a -> a
      val pull : a -> t
    end
    
    module C : Stack =
    struct
      type t = int
      type a = Nil | Cons of t * a
    
      let empty = Nil
      let isEmpty xs = match xs with | Nil -> true | _ -> false
      let push (x , s) = Cons (x , s)
      let pull s = match s with Nil -> 0 | Cons (x , s) -> x
    end
    

    хеш-таблицы

    указатели "С" страдают тем, что их можно присвоить переменной с временем жизни большим, чем запись активации функции, где эта переменная находится - и здравствуй segfault

    Антон Москаль

    таблица с прямой адресацией

    #include <stdio.h>
    #include <stdlib.h>
    
    #define KEY_NUM 16                             // size
    #define ALL_BUSY ((1 << KEY_NUM) - 1)          // for flags
    
    typedef struct vals Vals;
    struct vals { int key; int val; };
    
    int  ukeys[KEY_NUM];       // keyspace
    Vals tvals[KEY_NUM];       // table with key-value objects
    unsigned int flags = 0;    // busy|free keystatus
    
    Vals lookup (int) ;
    int  insert (int) , delete (int) ;
    
    
    int
    main (int argc, char** argv, char** envp)
    {
      // initialize keyspace with sequence from 0 to KEY_NUM - 1:
      int i = 0 ; while (i < KEY_NUM) { ukeys[i] = i ; i++ ; }
    
      return 0 ;
    }
    
    
    Vals
    lookup (int key)
    {
      if (key < 0 || key > KEY_NUM - 1)
        { printf ("nonexist elem: key=%i\n", key) ;
          exit (1) ;
        } ;
      return tvals[key] ;
    }
    
    
    int
    insert (int val)
    {
      if (flags == ALL_BUSY) { printf ("overflow. val=%i\n", val) ; return 1 ; } ;
    
      // find the first free key in keyspace:
      int i, k ;
      for ( i = 0, k = flags; i < 8; i++, k >>= 1 )
          { if (k & 1) continue ; else break ; } ;
    
      // store value in the table:
      tvals[ukeys[i]].key = i; tvals[ukeys[i]].val = val;
    
      // re-arrange flags
      flags |= (1 << i) ;   // to indicate that this key is in use now
      if (flags == ALL_BUSY) printf ("table is full now.\n") ;
    
      return 0 ;
    }
    
    
    int
    delete (int key)
    {
      if (key < 0 || key > KEY_NUM - 1)
        { printf ("nonexist key=%i\n", key) ; return 1;  } ;
    
      int k = (1 << key) ;     // is the key in use?
      if (k & flags) { tvals[key].val = 0 ; } else { return 1 ; } ;
    
      // re-arrange flags
      flags = ~k & flags ;        // to indicate that this key is free now
    
      return 0 ;
    }
    

    таблица с цепочками

    // keys : natural nums, vals : natural nums > 0
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #define PRIME 11
    #define MAX 5
    
    typedef struct elem Elem;
    
    struct elem
            { int    key ;
              int    val ;
              Elem*  next ;
              Elem*  prev ;
            } ;
    Elem* table[PRIME] ;           // table with lists of key-value objects
    Elem *r, *p ;                  // current position , init position
    
    int lookup (int) , insert (int, int) , delete (int) , hash_func (int) ;
    
    int
    main (int argc, char** argv, char** envp)
    {
      // initialization
      r = p = malloc (MAX * sizeof (Elem)) ;
      int i ; for (i = 0 ; i < PRIME ; i++) table[i] = NULL ;
    
      // some stuff here
    
      // finalization
      free (p) ; return 0 ;
    }
    
    
    int
    lookup (int key)
    {
      int h = hash_func (key) ; Elem* s = table[h] ;
    
      while (s)
      {
        if (s->key == key) return s->val ;
        s = s->next ;
      } ;
    
      return 0 ;
    }
    
    
    int
    insert (int key, int val)
    {
      if (r - p > MAX - 1) { printf ("overflow\n") ; return -1 ; } ;
    
      int h = hash_func (key) ; Elem* s = table[h] ;
      Elem x ; x.key = key ; x.val = val ;
    
      if (!s)
      {
        x.prev = r ; x.next = NULL ;
        *r = x ;
        table[h] = r ;
      }
      else
      {
        while (s->val < val && s->next) s = s->next ;
        if (s->next)
        {
          x.prev = s->prev ; x.next = s ;
          *r = x ;
          s->prev->next = r ; s->prev = r ;
        }
        else
        {
          x.prev = s ; x.next = NULL ;
          *r = x ;
          s->next = r ;
        } ;
      } ;
      r += 1 ;
    
      return h ;
    }
    
    
    int
    delete (int key)
    {
      int h = hash_func (key) ; Elem* s = table[h] ;
    
      while (s)
      {
        if (s->key == key)
        {
          if (s->prev) s->prev->next = s->next ;
          else table[h] = s->next ;
          s->val = 0 ;
          return 0 ;
        } ;
        s = s->next ;
      } ;
    
      return 1 ;
    }
    
    
    int
    hash_func (int key)
    { return key % PRIME; }
    
    
    // naive garbage collector
    void
    gc (void)
    {
      Elem* g = p ;
    
      while (g < r)
      {
        if (!g->val)
        {
          while (!r->val) { r -= 1; } ;
          r->prev->next = g ;
          *g = *r ;
          r->val = 0 ;
        } ;
        g += 1 ;
      } ;
    }
    

    универсальная таблица

    // universal hash  for m * 2 elements
    // p    : prime number
    // keys : natural nums [0, p - 1]
    //
    // hash function in the form: (((a * key + b) mod p) mod m)
    // restrictions : 0 < a < p - 1;     0 < b < p - 1;   m < p;     key < p
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #define MSIZE 31   // size of the main key table m < p
    #define PRIME 283  // primary number p > m
    
    typedef struct elem Elem;
    
    struct elem
            { int    a ;
              int    b ;
              int    tv[4] ;
            } ;
    
    Elem table[MSIZE] ;
    
    int coin (void) , hash_func (int, int, int, int) ;
    int lookup (int) , insert (int), delete (int) ;
    
    
    int
    main (int argc, char** argv, char** envp)
    {
      // initialization. size of all secondary tables is 4
      int i = 0 ;
    
      while (i < MSIZE)
        { Elem x = { x.a = 0, x.b = 0 } ; table[i] = x ; i++ ; } ;
    
      srandom ((long int) &table) ;  // seed the gen for 'coin flip' proc
    
      // some stuff here
    
      return 0 ;
    }
    
    
    int
    hash_func (int a, int b, int m, int key)
    { return (a * key + b) % PRIME % m; }
    
    
    int
    insert (int key)
    {
      int h1, h2, h0 = hash_func (1, 0, MSIZE, key) ;
      Elem* s = &table[h0] ;
    
      h1 = hash_func (s->a, s->b, 4, key) ;
    
      if ( ! s->tv[h1] ) s->tv[h1] = key ;                 // ok
      else
      {                                                    // key collision
        lb1:  s->a = coin () ;                             // change coef "a" randomly
              s->b = coin () ;                             // change coef "b" randomly
              h1 = hash_func (s->a , s->b , 4 , key) ;
              h2 = hash_func (s->a , s->b , 4 , *s->tv) ;
              if (h2 == h1) goto lb1 ;                     // yes-yes, I know...
        s->tv[h2] = *s->tv ;
        s->tv[h1] = key ;
      } ;
    
      return 0 ;
    }
    
    
    int
    lookup (int key)
    {
      int h0 = hash_func (1, 0, MSIZE, key) ;
      Elem s = table[h0] ;
      int h1 = hash_func (s.a, s.b, 4, key) ;
    
      return s.tv[h1] ;
    }
    
    
    int
    delete (int key)
    {
      int h0 = hash_func (1, 0, MSIZE, key) ;
      Elem *s = &table[h0] ;
      int h1 = hash_func (s->a, s->b, 4, key) ;
      s->tv[h1] = 0 ;
    
      return 0 ;
    }
    
    
    int
    coin ()
    {
      int y = random () ;
      srandom (y) ;
      return y % PRIME ;
    }
    

    кучи

    куча на массиве

    #include <stdlib.h>
    #include <stdio.h>
    #define INIT 15   // max heap size
    
    int p[INIT];
    int k;
    
    void
    max_health (int i)
    {
      int cl = (i << 1) | 1;   // left child
      int cr = (i << 1) + 2;   // right child
    
      if      (p[cl] > p[cr] && p[i] < p[cl]) { p[i] ^= p[cl] ^= p[i] ^= p[cl]; }
      else if (p[cl] < p[cr] && p[i] < p[cr]) { p[i] ^= p[cr] ^= p[i] ^= p[cr]; };
    
      if (i <= k/2 - 2) { max_health (cl); max_health (cr); };
    }
    
    int
    insert (int x)
    {
      if (k == INIT) return 0;
      p[k] = x;
      max_health (0);
      k += 1;
      return k;
    }
    
    int
    extract ()
    {
      int m = *p;
      *p = 0;
      max_health (0);
      k -= 1;
      return m;
    }
    
    int count (void) { return k; }
    
    int
    main (int argc, char** argv, char** envp)
    {
      int i = 0; while (i < INIT) { p[i] = 0; i++; };
      k = 0;
    
      return 0;
    }
    

    leftist heap на бинарном дереве (Окасаки)

    как правило, множества и хеш-таблицы поддерживают эффективный доступ к произвольным элементам. однако иногда требуется эффективный доступ только к минимальному элементу. структура данных, поддерживающая такой режим доступа, называется priority queue или heap

    отношение порядка играет важную роль в семантике кучи. часто кучи реализуются через деревья с порядком кучи (heap-ordered), т.е. в которых элемент при каждой вершине не больше элементов в поддеревьях. при таком упорядочении минимальный элемент дерева всегда находится в корне

    левоориентированные кучи представляют собой двоичные деревья с порядком кучи, обладающие свойством: ранг любого левого поддерева не меньше ранга его сестринской правой вершины. ранг узла определяется как длина его right spine (т.е. самого правого пути от данного узла до пустого). простым следствием свойства левоориентированности является то, что правая периферия любого узла - кратчайший путь от него к пустому узлу

    если у нас есть некоторая структура упорядоченных элементов, мы можем представить левоориентированные кучи как двоичные деревья, снабженные информацией о ранге:

    type 'a heap = E | T of int * 'a * 'a heap * 'a heap

    заметим, что элементы правой периферии левоориентированной кучи (да и любого дерева с порядком кучи) расположены в порядке возрастания

    главная идея левоориентированной кучи заключается в том, что для слияния двух куч достаточно слить их правые периферии как упорядоченные списки, а затем вдоль полученного пути обменивать местами поддеревья при вершинах, чтобы восстановить свойство левоориентированности

    реализация левоориентированной кучи для целых:

    module type Heap =
      sig
        type e
        type h
    
        val empty     : h
        val isEmpty   : h -> bool
        val insert    : e  *  h -> h
        val merge     : h  *  h -> h
        val findMin   : h -> e          (* бросает исключение Empty_heap при пустой куче *)
        val deleteMin : h -> h          (* бросает исключение Empty_heap при пустой куче *)
      end
    
    type 'a heap = E | T of int  *  'a  *  'a heap  *  'a heap
    
    module IntLeftHeap : Heap =
      struct
    
        exception Empty_heap ;;
    
        type e    = int
        type h    = int heap
        let empty = E
    
    
        let rank xs = match xs with | E -> 0 | T (r , _ , _ , _) -> r
    
    
        let makeT (x, a, b) =
          if (rank a) >= (rank b)
          then T (rank b + 1, x, a, b)
          else T (rank a + 1, x, b, a)
    
    
        let rec merge (xs , ys) = match (xs , ys) with
          | (E , h) | (h , E) -> h
          | ((T(_ , x , a1 , b1) as h1) , (T(_ , y , a2 , b2) as h2)) ->
              if x <= y
              then makeT (x , a1 , merge (b1 , h2))
              else makeT (y , a2 , merge (h1 , b2))
    
    
        let isEmpty xs = match xs with | E -> true | _ -> false
    
        let insert (x , xs) = merge (T(1, x , E, E) , xs)
    
    
        let deleteMin xs = match xs with
          | E -> raise Empty_heap
          | (T(_ , x , a , b)) -> merge (a , b)
    
    
        let findMin xs = match xs with
          | E -> raise Empty_heap
          | (T(_ , x , a , b)) -> x
    
      end
    

    skew heap

                       // TODO
    

    binomial heap (Окасаки)

    биномиальные очереди, которые мы, чтобы избежать путаницы с очередями FIFO, будем называть биномиальными кучами - ещё одна распространенная реализация куч. биномиальные кучи устроены сложнее, чем левоориентированные, и, на первый взгляд, не возмещают эту сложность никакими преимуществами. однако в различных вариантах биномиальных куч можно заставить insert и merge выполняться за время O(1)

    биномиальные кучи строятся из более простых объектов, называемых биномиальными деревьями

    биномиальные деревья индуктивно определяются так:

  • -- биномиальное дерево ранга 0 представляет собой одиночный узел
  • -- биномиальное дерево ранга r+1 получается путем связывания двух биномиальных деревьев ранга r, так что одно из них становится самым левым потомком второго

    из этого определения видно, что биномиальное дерево ранга r содержит ровно 2^r элементов

    существует второе, эквивалентное первому, определение биномиальных деревьев, которым иногда удобнее пользоваться: биномиальное дерево ранга r представляет собой узел с r потомками t1, ..., tr, где каждое ti является биномиальным деревом ранга r-i

    мы представляем вершину биномиального дерева в виде элемента и списка его потомков. для удобства мы также помечаем каждый узел его рангом

    module type Ord =
      sig
        type t
        val compare : t -> t -> int
      end
    
    module BinomialHeap = functor (X : Ord) ->
      struct
    
        type 'a tree = Node of int  *  X.t  *  X.t tree list
        type 'a heap = X.t tree list
    
        exception Empty
    
        let rank (Node (r , _ , _)) = r
    
        let root (Node (_ , x , _)) = x
    
    
    (* function [link] deals with equial rank nodes only  *)
    
        let link ((Node (r, x1, lst1) as t1) , (Node (_, x2, lst2) as t2)) =
          if x1 < x2
          then Node (r + 1, x1, t2 :: lst1)
          else Node (r + 1, x2, t1 :: lst2)
    
    
        let rec insTree = function (x) ->
          match x with
          | (t , []) -> [t]
          | (t , ((t' :: ts') as ts)) ->
             if rank t < rank t'
             then t :: ts
               (* [link] deals with equial rank nodes only *)
             else insTree (link (t , t') , ts')
    
    
        let insert (x , ts) = insTree (Node (0 , x , []) , ts)
    
    
        let rec merge = function (x , y) ->
          match (x , y) with
          | (ts, []) | ([], ts) -> ts
          | ((t1 :: ts1 as a) , (t2 :: ts2 as b)) ->
             if   rank t1 < rank t2
             then t1 :: merge (ts1 , b)
             else if   rank t2 < rank t1
                  then t2 :: merge (a , ts2)
                  else insTree (link (t1 , t2) , merge (ts1, ts2))
    
    
        let rec removeMinTree = function (xs) ->
          match xs with
          | [] -> raise Empty
          | [t] -> (t, [])
          | t :: ts ->
                let (t' , ts') = removeMinTree ts
                in if root t < root t' then (t , ts) else (t' , t :: ts')
    
    
        let findMin = function (ts) ->
          let (t , _) = removeMinTree ts in root t
    
    
        let deleteMin = function (ts) ->
          let (Node (_ , x , ts1) , ts2) = removeMinTree ts
          in merge (List.rev ts1 , ts2)
    
      end
    

    деревья

    бинарное дерево

    module type ORDERED =
    sig
      type t
    
      val eq  : t * t -> bool
      val lt  : t * t -> bool
      val leq : t * t -> bool
    end
    
    (* реализация двоичных деревьев поиска в виде функтора *)
    
    module Unbalanced = functor (Element: ORDERED) ->
    struct
      type e = Element.t
      type tree = E | T of tree * e * tree
    
      let empty = E
    
      let rec member (x, z) = match z with
        | E -> false
        | T (a, y, b) ->
           if Element.lt (x, y)
           then member (x, a)
           else if Element.lt (y, x)
                then member (x, b)
                else true
    
      let rec insert (x, z) = match z with
        | E -> T (E, x, E)
        | T (a, y, b) ->
           if Element.lt (x, y)
           then T (insert (x, a), y, b)
           else if Element.lt (y, x)
                then T (a, y, insert (x, b))
                else z
    end
    

    AVL дерево

    // TODO
    

    splay дерево

    // TODO
    

    черно-красное дерево (Окасаки)

    двоичные деревья поиска хорошо ведут себя на случайных данных, однако на упорядоченных данных дерево становится "бамбуком"/"гребенкой" и каждая операция может занимать до O(n) времени

    решение состоит в том, чтобы каждое поддерево поддерживать в приблизительно сбалансированном состоянии, тогда каждая операция выполняется не хуже, чем за время O(log n)

    красно-чёрное дерево представляет собой двоичное дерево поиска, в котором каждый узел окрашен. все терминальные узлы считаются чёрными

    всякое красно-чёрное дерево соблюдает два инварианта:
    1. y красного узла не может быть красного ребёнка
    2. каждый путь от любого подкорня до терминального узла содержит одинаковое количество чёрных узлов

    эти два инварианта гарантируют, что самый длинный возможный путь по красно-чёрному дереву, где красные и чёрные узлы постоянно чередуются, не более чем вдвое длиннее самого короткого пути, состоящего только из чёрных узлов

    функция member для красно-чёрных деревьев не обращает внимания на цвета

    функция insert должна поддерживать два инварианта баланса:
    во-первых, когда мы создаем новый узел, мы сначала окрашиваем его в красный цвет
    во-вторых, в окончательном результате мы корень дерева всегда окрашиваем чёрным
    в ветках x < y и x > y мы вызовы конструктора T заменяем на обращения к функции balance

    функция balance действует подобно конструктору T, но она переупорядочивает свои аргументы, чтобы обеспечить инварианты

    если новый узел окрашен красным, все в порядке с Инвариант2, но если отец нового узла тоже красный, нарушается Инвариант1. мы временно позволяем существовать такому нарушению - balance обнаруживает и исправляет красно-красные нарушения тогда, когда обрабатывает чёрного родителя красного узла с красным ребёнком

    такая чёрно-красно-красная цепочка может возникнуть в четырёх различных конфигурациях, в зависимости от того, левым или правым ребёнком является каждая из красных вершин. однако в каждом из этих случаев решение одно и то же: нужно преобразовать чёрно-красно-красный путь в красную вершину с двумя чёрными детьми. нетрудно проверить, что в получающемся поддереве будут соблюдены оба инварианта красно-чёрного баланса

    после балансировки некоторого поддерева красный корень этого поддерева может оказаться ребёнком ещё одного красного узла. таким образом, балансировка продолжается до самого корня дерева. на самом верху дерева мы можем получить красную вершину с красным ребёнком, но без чёрного родителя. с этим вариантом мы справляемся, всегда перекрашивая корень в чёрное

    код на хаскеле:

    {-# LANGUAGE OverloadedStrings , MultiParamTypeClasses , FlexibleInstances #-}
    
    module RBTree (RBTree , empty , member , insert , maxElem, minElem) where
    
    data Color = R | B deriving (Show)
    
    data RBTree a = E | T Color a (RBTree a) (RBTree a) deriving (Show)
    
    balance :: Color -> q -> RBTree q -> RBTree q -> RBTree q
    balance B z (T R y (T R x a b) c) d = T R y (T B x a b) (T B z c d)
    balance B z (T R x a (T R y b c)) d = T R y (T B x a b) (T B z c d)
    balance B x a (T R z (T R y b c) d) = T R y (T B x a b) (T B z c d)
    balance B x a (T R y b (T R z c d)) = T R y (T B x a b) (T B z c d)
    balance p x a b = T p x a b
    
    empty :: RBTree a
    empty = E
    
    member :: Ord a => a -> RBTree a -> Bool
    member _ E = False
    member x (T _ y a b) | x < y = member x a | x > y = member x b | otherwise =  True
    
    insert :: Ord a => a -> RBTree a -> RBTree a
    insert x ts = T B y a b where
      ins E = T R x E E
      ins q@(T p z g h) | x < z = balance p z (ins g) h
                        | x > z = balance p z g (ins h)
                        | otherwise = q
      T _ y a b = ins ts
    
    minElem :: RBTree a -> a
    minElem (T _ x E _) = x
    minElem (T _ _ l _) = minElem l
    
    maxElem :: RBTree a -> a
    maxElem (T _ x _ E) = x
    maxElem (T _ _ _ r) = maxElem r
    
    
    
    
    -- |                     unit tests for RBTree
    
    {-# language ScopedTypeVariables #-}
    
    module Main where
    
    import RBTree as RB
    import Test.QuickCheck
    
    t3 = quickCheckWith (stdArgs { maxSuccess = 5000 })
      (vector 100 >>= \xs -> return $
        let ts = foldr (\x a -> insert x a) empty xs
        in all (\x -> RB.member x ts) (xs :: [Int]))
    
    t4 = quickCheckWith (stdArgs { maxSuccess = 5000 })
      (vector 100 >>= \xs -> return $
        let ts = foldr (\x a -> insert x a) empty xs
        in  all (\x -> RB.member x ts) (xs :: [Char]))
    
    t5 = quickCheckWith (stdArgs { maxSuccess = 5000 })
      (vector 100 >>= \xs -> return $
        let ts = foldr (\x a -> RB.insert x a) empty (xs :: [Double])
        in all (\x -> RB.member x ts) xs)
    
    t6 = quickCheckWith (stdArgs { maxSuccess = 5000 })
      (vector 100 >>= \xs -> return $
        let ts = foldr (\x a -> RB.insert x a) empty (xs :: [Int])
        in RB.maxElem ts == maximum xs)
    
    t7 = quickCheckWith (stdArgs { maxSuccess = 5000 })
      (vector 100 >>= \xs -> return $
        let ts = foldr (\x a -> RB.insert x a) empty (xs :: [String])
        in RB.minElem ts == minimum xs)
    
    main :: IO ()
    main = t3 >> t4 >> t5 >> t6 >> t7
    

    то же самое - на окамле:

                                   (* ocamlc -c BRTree.ml *)
    
    module type ORD = sig type t end ;;
    
    module ORD = struct   type t end ;;
    
    module type BRTree = functor (Id : ORD) ->
      sig
        type elem  = Id.t
        type color = R | B
        type tree  = E | T of color * tree * elem * tree
    
        val member : elem -> tree -> bool
        val insert : elem -> tree -> tree
        val empty : tree
        val singleton : elem -> tree
        val of_list : elem list -> tree
        val min : tree -> elem
        val max : tree -> elem
      end
    ;;
    
    module BRTree = functor (Id : ORD) ->
      struct
        type elem  = Id.t
        type color = R | B
        type tree  = E | T of color * tree * elem * tree
    
        let balance =
          fun xs ->
          match xs with
          | E -> xs
          | T (B, T (R, T (R, a, x, b), y, c), z, d)
          | T (B, T (R, a, x, T (R, b, y, c)), z, d)
          | T (B, a, x, T (R, T (R, b, y, c), z, d))
          | T (B, a, x, T (R, b, y, T (R, c, z, d))) ->
              T (R, T (B, a, x, b), y, T (B, c, z, d))
          | T (_,_,_,_) -> xs
        ;;
    
        let rec insert =
          fun x ts ->
          match ts with
            | E -> T (R, E, x, E)
            | T (color, left, y, right) ->
               if x < y
               then balance (T (B, insert x left, y, right))
               else
                 if x > y
                 then balance (T (B, left, y, insert x right))
                 else ts
        ;;
    
        let empty = E
        let singleton x = insert x empty
        let of_list xs = List.fold_left (fun acc x -> insert x acc) empty xs
    
        let rec member =
          function
          | x, E -> false
          | x, T (_, a, y, b) ->
             if      x < y then member (x, a)
             else if x > y then member (x, b)
               else true
        ;;
    
        let rec min =
          function | T (_, E, x, _) -> Some x
                   | T (_, a, _, _) -> min a
                   | E -> None
        ;;
    
        let rec max =
          function | T (_, _, x, E) -> Some x
                   | T (_, _, _, b) -> max b
                   | E -> None
        ;;
    
      end
    ;;
    
    
    
                                   (* file testBRTree.ml *)
    
    (* ocamlc -open BRTree BRTree.cmo -c testBRTree.ml *)
    
    module Int = struct type t = int end ;;
    
    module K = BRTree (Int) ;;
    let z1 = K.of_list [9;1;10;2;8;4;3;7;5;6] ;;
    
    module Txt = struct type t = string end ;;
    
    module N = BRTree (Txt) ;;
    let z2 = N.of_list
     ["tb";"ka3";"zu";"sra";"abab";"yb2";"ty";"ab";"we";
      "er";"hber";"ka";"mbwe";"rkam";"bb8"] ;;
    
    module Pair = struct type t = int * string end;;
    
    module P = BRTree (Pair) ;;
    let z3 = P.of_list
      [(17,"zzz"); (3,"aaa"); (12,"ooo"); (7,"kro");
       (1,"ccc"); (2,"ddd"); (8,"ttt")] ;;
    
    
              (*  test suite  *)
    
    let te01 = assert (Some 1 = K.min z1)
    and te02 = assert (Some "zu" = N.max z2)
    and te03 = assert (Some (17,"zzz") = P.max z3)
    in print_endline "start tests" ;
       te01 ; print_endline "ok with Int" ;
       te02 ; print_endline "ok with Txt" ;
       te03 ; print_endline "ok with (Int,Txt)" ;
       print_endline "tests passed" ;;
    

    декартово дерево

         // TODO
    

    k-d дерево

    для сортировки по ключу k-мерного пространства. верхний уровень бинарного дерева сортируется по первой координате, два его потомка - по второй, их потомки - по третьей и т.д. если глубина дерева превышает количество размерностей у пространства, то на очередном уровне все повторяется в точности как для рута: сначала по первой координате, затем по второй и т.д. - пока не достигнуто дно

    пример применения для случая 2-d, ocaml. отрисовка диаграммы Вороного для двумерной поверхности. на плоскости рассыпаны реперные точки, характеризующиеся цветом. все точки, для котороых конкретная реперная является ближайшей, красятся в ее цвет

    осамл:

    
         (*   интерфейс модуля для k-d tree:   *)
    
    module Kdtree :
      sig
    
       (*         index * data                                  *)
        type elt = (int * int) * int
    
        type 'a kdnode = | Nod of 'a * 'a kdnode * 'a kdnode | Nil
    
        val build_tree  : elt list -> elt kdnode
        val search_tree : (int * int -> int * int -> int ) ->
                          (int * int) ->
                          (int * int) -> elt kdnode -> int
    
        val print_element : elt -> unit
        val print_tree    : int -> elt kdnode -> unit
    
      end
    
    
    
        (*   реализация дерева :   *)
    
    module Kdtree =
      struct
    
               (* index * color *)
        type elt = (int * int) * int ;;
    
        type 'a kdnode = | Nod of 'a * 'a kdnode * 'a kdnode | Nil ;;
    
        let print_element ((x , y) , c) =
          print_string @@         string_of_int x ^
                          " , " ^ string_of_int y ^
                          " , " ^ string_of_int c
        ;;
    
                (*   index vs index in the actual axix    *)
        let a_compare axix ((x1 , y1) , _) ((x2 , y2) , _) =
          match axix with
          | 0 -> if x1 > x2 then 1 else if x1 = x2 then 0 else -1
          | _ -> if y1 > y2 then 1 else if y1 = y2 then 0 else -1
        ;;
    
    
        let search_tree dfu (sx , sy as sp) limit tree =
          let best = ref (limit , 0) in
    
          let rec search_tree_impl (node : elt kdnode) (axix : int) : int =
            match node with
            | Nod (px , py as pt , _ as leaf , Nil , Nil) ->
               if dfu (fst !best) sp > dfu pt sp then best := leaf ;
               snd !best
            | Nod (px , py as pt , _ as leaf , Nil , right) ->
               if dfu (fst !best) sp > dfu pt sp then best := leaf ;
               search_tree_impl right (1 + axix mod 2)
            | Nod (px , py as pt , _ as leaf , left , Nil) ->
               if dfu (fst !best) sp > dfu pt sp then best := leaf ;
               search_tree_impl left (1 + axix mod 2)
            | Nod (px , py as pt , _ as leaf , left , right) ->
               if dfu (fst !best) sp > dfu pt sp then best := leaf ;
               let hyper = if axix == 0    (*   distance in one dimension only  *)
                           then dfu (px , 0) (sx , 0)
                           else dfu (0 , py) (0 , sy)
               in if hyper > dfu (fst !best) sp
                  then
                    ( match a_compare axix (sp, 0) (pt, 0) with
                      | 1 -> search_tree_impl right (1 + axix mod 2)
                      | _ -> search_tree_impl left  (1 + axix mod 2) )
                  else
                    ( ignore @@ search_tree_impl right  (1 + axix mod 2) ;
                      search_tree_impl left (1 + axix mod 2)  )
            | Nil -> snd !best
    
          in search_tree_impl tree 0
        ;;
    
        exception NegIndex ;;
        exception ShortList ;;
    
        let rec take' (n : int) (acc : 'a list) (xs : 'a list) : 'a list =
          match n with
          | 0 -> List.rev acc
          | k -> take' (k - 1) ((List.hd xs) :: acc) (List.tl xs)
        ;;
    
        let take =
          fun n xs ->
            if n < 0 then raise NegIndex else
              if n > List.length xs then raise ShortList else
                take' n [] xs
        ;;
    
        let rec drop =
          fun n xs ->
            if n < 0 then raise NegIndex else
              if n > List.length xs then raise ShortList else
                match n with
                | 0 -> xs
                | k -> drop (k - 1) (List.tl xs)
        ;;
    
        let build_tree es =
          let rec build_impl (es: elt list) (axix : int) : elt kdnode =
            match es with
            | []  -> Nil
            | _   ->
               let half = (List.length es) / 2
               and sorted_es = List.sort (a_compare axix) es
               in let left   = sorted_es |> take half
                  and median = sorted_es |> drop half |> List.hd
                  and right  = sorted_es |> drop half |> List.tl
                  in Nod ( median
                         , build_impl left  (1 + axix mod 2)
                         , build_impl right (1 + axix mod 2)
                         )
          in build_impl es 0
        ;;
    
        let rec print_tree depth node =
          match node with
          | Nod (leaf , left , right) ->
             print_string @@ String.make (3 * depth) ' ' ;
             print_element leaf ; print_newline () ;
             print_tree (depth + 1) left ;
             print_tree (depth + 1) right
          | Nil ->
             print_string @@ String.make (3 * depth) ' ' ;
             print_string "⊥" ; print_newline ()
        ;;
    
      end
    
    
    
    
        (*   и утилитные модули. Geo   *)
    
    
        (*  интерфейс: *)
    
    open Geo ;;
    open Graphics ;;
    
    type seed = point * color
    
    val gen_seeds : int -> int -> seed list -> seed list
    
    
    
       (*   реализация : *)
    
    open Geo ;;
    open Graphics ;;
    
    type seed = point * color ;;
    
    let gen_point (limit : int) : point =
      Random.int (limit - 1) , Random.int (limit - 1)
    ;;
    
    let  gen_color () : color =
      rgb ((Random.int 180) + 50) ((Random.int 180) + 50) ((Random.int 180) + 50)
    ;;
    
    let gen_seed (limit : int) : seed =
      (   gen_point limit
      ,   gen_color ()
      )
    ;;
    
    let rec gen_seeds n limit acc =
      match n with
      | 0 -> acc
      | p -> gen_seeds (p - 1) limit ((gen_seed limit) :: acc)
    ;;
    
    
    
    
     (*  модуль Seeds :
    
        интерфейс :    *)
    
    open Graphics ;;
    
    type point = int * int
    type dist_function = point -> point -> int
    
    val min_with : ('a -> 'b) -> 'a -> 'a -> 'a
    val dist_taxiCab : point -> point -> int
    val dist_Euclid : point -> point -> int
    
    
    
      (*   реализация:  *)
    
    open Graphics ;;
    
    type point = int * int ;;
    
    let min_with (f : 'a -> 'b) (x : 'a) (y : 'a) : 'a =
      if (f x) < (f y) then x else y
    ;;
    
    type dist_function = point -> point -> int ;;
    
    let dist_taxiCab (x1 , y1 : point) (x2 , y2 : point) : int =
      let dx = abs @@ x2 - x1
      and dy = abs @@ y2 - y1
      in dx + dy
    ;;
    
    let dist_Euclid (x1 , y1 : point) (x2 , y2 : point) : int =
      let dx = abs @@ x2 - x1
      and dy = abs @@ y2 - y1
      in dx * dx + dy * dy
    ;;
    
    
    
    
      (*   использование - модуль main :  *)
    
    open Graphics ;;
    open Geo ;;
    open Seeds ;;
    open Kdtree ;;
    
    if ( 3 != Array.length @@ Sys.argv )
    then ( print_endline "usage : ./a.out area_size num_ptr" ; exit 1 )
    
    let area_size = Sys.argv.(1) |> int_of_string ;;
    let num_of_points = Sys.argv.(2) |> int_of_string ;;
    
    let seeds : seed list =
      Random.self_init () ;
      gen_seeds num_of_points area_size []
    ;;
    
    let ptrs = Kdtree.build_tree seeds ;;
    
    let draw_voronoi (df : dist_function) : unit =
    
    (*  with interpolation for 2x2 points areas *)
    
      let upper = area_size / 2
      in for y = 0 to upper do
           for x = 0 to upper do
    
             let xint = 2 * x
             and yint = 2 * y
             in set_color @@
                  Kdtree.search_tree df (xint , yint) (area_size * 2 , area_size * 2) ptrs ;
    
                plot (xint    ) (yint    ) ;
                plot (xint + 1) (yint    ) ;
                plot (xint    ) (yint + 1) ;
                plot (xint + 1) (yint + 1) ;
    
           done ;
         done
    ;;
    
    let draw_points () : unit =
      List.iter (fun ((x , y) , _) -> set_color white ; fill_circle x y 3) seeds
    ;;
    
    let _ =
      open_graph "" ;
      auto_synchronize false ;
      draw_voronoi dist_Euclid ;
      draw_points () ;
      synchronize () ;
      ignore @@ read_key () ;
      close_graph ()
    ;;
      

    результат работы:


    глобалы

    это способ хранения и обработки данных. они появились в 1966 году в языке MUMPS (в медицинских БД) и до сих пор активно используются

    в двух словах — глобалы — это иерархический key-value, где под одним key можно хранить целое дерево значений и ключей

    одноуровневое дерево с 2-мя ветвями:

    
    set ^a("+7926x") = "John Sidorov"
    set ^a("+7916y") = "Sergey Smith"
    

    при вставке информации в глобал (комaнда set ) происходят три вещи:

    при получении списка пользователей из глобала база не тратит времени на сортировку. причём можно запросить вывод отсортированного списка, начиная с любого ключа, даже несуществующего (вывод начнётся с первого реального ключа, который идёт за несуществующим)

    добавим ещё несколько ветвей второго и третьего уровня

    
    set ^a("+7926X", "city") = "Moscow"
    set ^a("+7926X", "city", "street") = "Req Square"
    set ^a("+7926X", "age") = 25
    set ^a("+7916Y", "city") = "London"
    set ^a("+7916Y", "city", "street") = "Baker Street"
    set ^a("+7916Y", "age") = 36
    

    интересный момент: можно построить дерево, не задавая значений узлов верхних уровней:

    
    set ^b("a", "b", "c", "d") = 1
    set ^b("a", "b", "c", "e") = 2
    set ^b("a", "b", "f", "g") = 3

  • внутренние узлы: значения в узлах могут быть не определены
  • терминалы: обязательно должны быть определены значения
  • удаление поддеревьев — сильная сторона глобалов. для этого не нужна рекурсия. это происходит невероятно быстро. это можно сделать командой kill

    
    kill ^a("+7926X")
    

    варианты структур при использовании глобалов

    одна вершина и множество ветвей

    это классическая key-value база. а если в качестве значения мы будем сохранять кортеж, то получим реляционную таблицу с первичным ключом

    чтобы при считывании было возможно разделить строку опять на колонки можно использовать:

    не представляет труда на глобалах сделать нечто подобное вторичным индексам в реляционных БД. назовём такие структуры индексными глобалами. индексный глобал — это вспомогательное дерево для быстрого поиска по полям, не являющимися составными частями первичного ключа основного глобала

    двухуровневое дерево, у каждого узла второго уровня фиксированное число ветвей

    это альтернативная реализация таблиц. сравним эту реализацию с предыдущей

    минусы

    медленнее на вставку, так как нужно устанавливать число узлов равное числу колонок

    больше расход дискового пространства. так как индексы глобала (в понимании как индексы у массивов) с названиями колонок занимают место на диске и дублируются для каждой строки

    плюсы

    быстрее доступ к значениям отдельных колонок, так как не нужно парсить строку

    проще менять схему данных

    нагляднее код

    объекты с подобъектами

    это область традиционного применения глобалов. в медицинской сфере огромно число болезней, лекарств, симптомов, методов лечения. создавать на каждого пациента таблицу с миллионом полей нерационально. тем более, что 99% полей будут пустыми

    глобалы идеально подошли для медицины, так как позволяют создать для КАЖДОГО пациента ТОЧНОЕ описание его истории болезни, различных терапий, действий лекарств - в виде дерева, не тратя при этом лишнее дисковое пространство на пустые колонки

    на глобалах удобно делать БД с данными, когда важно накопить и систематизировать максимум разнообразной информации об обьекте. это востребовано в медицине, банковской сфере, маркетинге, архивном деле

    изменение структуры данных, если мы используем глобалы, нам ничего не стоят. в любой момент мы можем добавить любые нужные нам новые свойства к любому объекту, на любом уровне иерархии

    изменения связанные с переименованием ветвей можно запускать в фоновом режиме на работающей БД.

    хеши (даже с вложением )

    иерархические документы: XML, JSON легко хранятся в глобалах

    база дебютов. можно в качестве значения индекса узла глобала использовать оценку силы хода. тогда, чтобы выбрать самый сильный ход достаточно будет выбрать ветвь с наибольшим весом. в глобале все ветви на каждом уровне будут отсортированы по силе хода

    структура. в узлах можно хранить некие кеширующие значения отражающие характеристики всего поддерева. например, объём продаж данного поддерева. в любой момент мы можем получить цифру, отражающую достижения любой ветви

    разреженные массивы

    разреженный массив — это разновидность массива, в котором большинство значений принимает одинаковое значение. на практике часто встречаются настолько огромные разреженные массивы, что нет никакого смысла занимать память одинаковыми элементами. глобалы — хорошие кандидаты для реализации разреженных массивов:

    хранят значения только определённых узлов и не хранят значения неопределённых

    интерфейс доступа к значению узла чрезвычайно похож на то, как во многих языках программирования реализован доступ к элементу многомерного массива

    
    set ^a(1, 2, 3) = 5
    write ^a(1, 2, 3)
    

    поскольку глобал — персистентная структура, то разреженные массивы делать на них имеет смысл тогда, когда заранее известно, что объёма оперативной памяти будет недостаточно

    одним из свойств реализаций разреженных массивов является возврат некоторого значения по умолчанию, если обращение ведётся к неопределённой ячейке

    это можно реализовать, используя функцию $get. В данном примере рассмотрен 3-х мерный массив

    
    set a = $get(^a(x,y,z), defValue)
    

    матрица смежности (связности)

    такие матрицы используются для представления графов. очевидно, что чем больше граф, тем больше нулей будет в матрице

    
    set ^m(id1, id2) = 1
    set ^m(id1, id3) = 1
    set ^m(id1, id4) = 1
    set ^m(id1) = 3
    set ^m(id2, id4) = 1
    set ^m(id2, id5) = 1
    set ^m(id2) = 2
    ...
    

    в данном примере мы сохраняем в глобале ^m матрицу связности, а также число ребёр у каждого узла (кто с кем дружит и число друзей)

    таблица переходов конечного автомата

    поскольку граф переходов конечного автомата — это обыкновенный граф, то и таблица переходов конечного автомата — это та же самая матрица смежности

    клеточные автоматы

    самый известный клеточный автомат — игра «Жизнь», который из-за своих правил (когда у клетки много соседей — она умирает) представляет собой разреженный массив. если у нас огромное поле и нам нужно записывать все промежуточные состояния клеточного автомата, то вполне разумно использовать глобалы

    картография

    как правило на картах ОЧЕНЬ МНОГО пустого пространства. разреженный массив. конечно, никто не хранит карты в виде растровых массивов, используется векторное представление. но что из себя представляют векторные карты? это некая рамка и состоящие из точек полилинии и полигоны. фактически база данных точек и связей между ними

    весь земной шар разбивается на квадратики, потом подквадратики, а подквадратики на подподквадратики и так далее. в общем получаем иерархическую структуру для хранения которых и созданы глобалы. в любой момент мы можем практически моментально затребовать нужный квадрат или его очистить, при этом все подквадратики также будут возвращены или очищены

    бонусы, хранения многомерных матриц в глобалах

    быстрое удаление и/или выборки кусков пространства, кратных строкам, плоскостям, кубам и т.д. для случаев, когда используются целочисленные индексы, может оказаться полезной возможность быстрого удаления и/или выборки кусков пространства, кратных строкам, плоскостям, кубам и т.д

    командой kill мы можем удалить как отдельный элемент, так и строку, и даже целую плоскость. благодаря свойствам глобалов, это происходит очень быстро — в тысячи раз быстрее, чем поэлементное удаление

    выборка столбца матрицы в переменную:

      ; зададим трёхмерный разреженный массив 3x3x3
    
      set ^a(0,0,0) = 1,
          ^a(2,2,0) = 1,
          ^a(2,0,1) = 1,
          ^a(0,2,1) = 1,
          ^a(2,2,2) = 1,
          ^a(2,1,2) = 1
    
      merge column = ^a(2,2)
      write column
    
      ; вывод:
    
      column(0)=1
      column(2)=1