списки
"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") ; }
#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 ; }
#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 ; } } ;
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; }
как правило, множества и хеш-таблицы поддерживают эффективный доступ к произвольным элементам. однако иногда требуется эффективный доступ только к минимальному элементу. структура данных, поддерживающая такой режим доступа, называется 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
// TODO
биномиальные очереди, которые мы, чтобы избежать путаницы с очередями FIFO, будем называть биномиальными кучами - ещё одна распространенная реализация куч. биномиальные кучи устроены сложнее, чем левоориентированные, и, на первый взгляд, не возмещают эту сложность никакими преимуществами. однако в различных вариантах биномиальных куч можно заставить insert и merge выполняться за время O(1)
биномиальные кучи строятся из более простых объектов, называемых биномиальными деревьями
биномиальные деревья индуктивно определяются так:
из этого определения видно, что биномиальное дерево ранга 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
// TODO
// 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-мерного пространства. верхний уровень бинарного дерева сортируется по первой координате, два его потомка - по второй, их потомки - по третьей и т.д. если глубина дерева превышает количество размерностей у пространства, то на очередном уровне все повторяется в точности как для рута: сначала по первой координате, затем по второй и т.д. - пока не достигнуто дно
пример применения для случая 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