В этой статье я покажу двоичное дерево без рекурсии. Я думаю что оно в некоторых случаях будет более удобно, нежели дерево с рекурсией.
Итак, начнем. Допустим есть задачка, получить много данных и вывести совпадения. Я нашел одно решение с рекурсией в интернете и понял как это сделать просто. Мне понравилось это решение, но мы знаем что рекурсия заполняет стек и если будет большая вложенность, то будет много выходов из функций. Я захотел попробовать сделать такой алгоритм, который не нуждается в рекурсии. Я буду писать на C, потому что это такой язык, который могут понять все.
Первым делом определим структуру.
struct node { unsigned long number; unsigned long count; unsigned long step; struct node *parent; struct node *left; struct node *right; };
Здесь number это число, по которому будет идти сортировка node. Count это количество совпадений. Step нужен будет для вывода совпадений в консоль, он будет определять, было ли вхождение в этот node или нет.
Делаем глобальную ссылку на корень дерева.
struct node *root;
Теперь добавляем нашу функцию по вставке чисел, она выглядит немного по другому — например теперь в отличие от рекурсивной функции, она не требует адрес структуры.
static void add_node (const int number) { register struct node *prev = NULL; register unsigned long left = 0; register struct node *p = root; while (1) { if (p == NULL) { p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; if (prev) { if (left) prev->left = p; else prev->right = p; p->parent = prev; } if (root == NULL) { root = p; p->parent = NULL; } return; } prev = p; if (p->number > number) { left = 1; if (p->left && p->number < p->left->number) { register struct node *up = p; register struct node *down = p->left; p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; p->parent = up; p->left = down; return; } p = p->left; } else if (p->number < number) { left = 0; if (p->right && p->number > p->right->number) { register struct node *up = p; register struct node *down = p->right; p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; p->parent = up; p->right = down; return; } p = p->right; } else if (p->number == number) { p->count++; return; } } }
Я указал локальные переменные как регистры, чтобы хоть чуточку быстрее работало. Если посмотреть через дизассемблер, то можно увидеть, что у 64 битного проца хватает регистров.
[0x00401080]> s sym.add_node [0x00401166]> pd 30 ;-- add_node: 0x00401166 55 push rbp 0x00401167 4889e5 mov rbp, rsp 0x0040116a 4155 push r13 0x0040116c 4154 push r12 0x0040116e 53 push rbx 0x0040116f 4883ec18 sub rsp, 0x18 0x00401173 897ddc mov dword [rbp - 0x24], edi 0x00401176 41bc00000000 mov r12d, 0 0x0040117c 41bd00000000 mov r13d, 0 0x00401182 488b1dcf2e00. mov rbx, qword [obj.root] ; [0x404058:8]=0 0x00401189 4885db test rbx, rbx ┌─< 0x0040118c 7559 jne 0x4011e7
Теперь добавление происходит без рекурсии. Далее надо пройтись по дереву и посмотреть есть ли совпадения. Такое тоже возможно не применяя рекурсию. Вот код.
static void find_matches ( ) { register struct node *n = root; register nm = 0; register n->step = 0; while (n) { if (n->step == 0 && n->count > 1) { printf ("%ld: %ld\n", n->number, n->count); nm++; } n->step = 1; if (n->left && n->left->step == 0) { n = n->left; continue; } else if (n->right && n->right->step == 0) { n = n->right; continue; } else if (n->step == 1) { if (n->left) n->left->step = 0; if (n->right) n->right->step = 0; n = n->parent; if (n && n->step == 1 && n->parent == NULL) { n->step == 2; continue; } else if (!n) break; } if (n->step == 1 && n->parent == NULL) break; } }
Да, она выглядит больше во много чем рекурсивная функция, но это цена за реализацию поиска в одном цикле. Я использовал step переменную, чтобы определять, где уже указатель был, а где не был.
Теперь посмотрите полный код.
static void add_node (const int number) { register struct node *prev = NULL; register unsigned long left = 0; register struct node *p = root; while (1) { if (p == NULL) { p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; if (prev) { if (left) prev->left = p; else prev->right = p; p->parent = prev; } if (root == NULL) { root = p; p->parent = NULL; } return; } prev = p; if (p->number > number) { left = 1; if (p->left && p->number < p->left->number) { register struct node *up = p; register struct node *down = p->left; p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; p->parent = up; p->left = down; return; } p = p->left; } else if (p->number < number) { left = 0; if (p->right && p->number > p->right->number) { register struct node *up = p; register struct node *down = p->right; p = calloc (1, sizeof (struct node)); p->number = number; p->count = 1; p->parent = up; p->right = down; return; } p = p->right; } else if (p->number == number) { p->count++; return; } } }
В целом я не видел проблем рекурсивной функции на миллион элементов или 10 миллионов, но возможно, что алгоритм, который я буду использовать, привел в этой статье, возможно будет даже лучше рекурсивной функции. Но для этого надо произвести расчёты, которые я только учусь делать.
Я поменял статью, дерево теперь сбалансированное.
Всем спасибо за внимание.
ссылка на оригинал статьи https://habr.com/ru/articles/590485/
Добавить комментарий