В этой статье я покажу двоичное дерево без рекурсии. Я думаю что оно в некоторых случаях будет более удобно, нежели дерево с рекурсией.
Итак, начнем. Допустим есть задачка, получить много данных и вывести совпадения. Я нашел одно решение с рекурсией в интернете и понял как это сделать просто. Мне понравилось это решение, но мы знаем что рекурсия заполняет стек и если будет большая вложенность, то будет много выходов из функций. Я захотел попробовать сделать такой алгоритм, который не нуждается в рекурсии. Я буду писать на 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 миллионов, но возможно, что алгоритм, который я буду использовать, привел в этой статье, возможно будет даже лучше рекурсивной функции. Но для этого надо произвести расчёты, которые я только учусь делать.
Я поменял статью, дерево теперь сбалансированное.
Всем спасибо за внимание.