Допустим, вы строите дерево:
a
/ \
/ \
/ \
b c
/ \ / \
/ \ / \
d e f g
\ / \
h i j
/
k
Каждый узел имеет дополнительное поле, uint32_t int lineage
. Если вы реализуете дерево с массивом, это дополнительное поле не требуется. Для всех намерений и целей, предположим, что мы используем узлы.
Вы можете использовать идею, аналогичную:
left = 2 * parent + 1;
right = 2 * parent + 2;
Но вместо этого, в корневом узле, пусть lineage равен 1. Для всех последующих узлов вы вставляете нормально, также пропуская lineage. Это будет целое число, но вы будете делать с ним побитовую арифметику. Если вы идете влево, вы просто сдвигаетесь влево (умножьте на 2), если вы идете вправо, сдвиньте влево + 1.
/**
* Starts recursive insertion
*/
struct node* insert(struct node* node, int key) {
// Create root
if (node == NULL) {
// Root has value 1
node = newNode(key, 1);
// Start recursion to left
} else if (key < node->key) {
node->left = insert(node->left, key, node->lineage << 1);
// Start recursion to right
} else if (key > node->key) {
node->right = insert(node->right, key, (node->lineage << 1) + 1);
}
return node;
}
/**
* Recursive insert function
*/
struct node* insert(struct node* node, int key, uint32_t lineage) {
if (node == NULL) {
return newNode(key, lineage);
}
if (key < node->key) {
node->left = insert(node->left, key, 2 * node->lineage);
}
else if (key > node->key) {
node->right = insert(node->right, key, 2 * node->lineage + 1);
}
return node;
}
По сути, вы создаете двоичные шаблоны. Если вы посмотрите на свое дерево с точки зрения бинарных линий, это будет так:
1
/ \
/ \
/ \
/ \
10 11
/ \ / \
/ \ / \
100 101 110 111
\ / \
1001 1110 1111
/
10010
Или проще:
1
/ \
/ \
/ \
/ \
1L 1R
/ \ / \
/ \ / \
1LL 1LR 1RL 1RR
\ / \
1LLR 1RRL 1RRR
/
1LLRL
Если вы называете их Ls и Rs или 1s и 0s, мы знаем, что для узла быть предком или узлом, двоичная линия (или шаблон LR) должна быть подстрокой дочернего узла, идущего слева направо, с условием, что двоичная строка предка строго меньше, чем у дочернего элемента.
Однако мы использовали целые числа вместо строк, чтобы мы могли выяснить, является ли она подстрокой в постоянном времени.
Важная часть
// Calculate if ancestor in O(1), no loops, no recursion
bool is_ancestor(struct node* parent, struct node* child) {
// Both pointers must be non-null
if (parent == NULL || child == NULL) {
return false;
}
// Get their lineages
uint32_t p_lin = parent->lineage;
uint32_t c_lin = child->lineage;
// Calculate the number of bits in
// binary lineage number
int p_bits = log2(p_lin);
int c_bits = log2(c_lin);
// Ancestors must
// have less bits than
// children. If this is false,
// than the parent pointer
// is at a lower point in the tree
if (p_bits >= c_bits) {
return false;
}
// Calculate difference in bits
// which represents the number of
// levels in between the child and parent
int diff = c_bits - p_bits;
// Shift child lineage to
// the right by that much
// to get rid of those bits, and
// only leave the amount of
// bits they should have in
// common
c_lin >>= diff;
// First N bits should be
// exactly the same
if (c_lin == p_lin) {
return true;
}
// If we got here, the child`
// is lower in the tree, but
// there is no path from the
// ancestor to the child
return false;
}
Вот log2()
в O(1)
из: Какой самый быстрый способ вычисления log2 целого числа в C #?
int log2(uint32_t n) {
int bits = 0;
if (n > 0xffff) {
n >>= 16;
bits = 0x10;
}
if (n > 0xff) {
n >>= 8;
bits |= 0x8;
}
if (n > 0xf) {
n >>= 4;
bits |= 0x4;
}
if (n > 0x3) {
n >>= 2;
bits |= 0x2;
}
if (n > 0x1) {
bits |= 0x1;
}
return bits;
}
Использование:
#include <stdio.h>
#include <stdlib.h>
#include <cstdint>
#include "tree.c" // Your tree
int main() {
/* Let us create following BST
50
/ \
30 70
/ \ / \
20 40 60 80 */
struct node *root = NULL;
root = insert(root, 50);
insert(root, 30);
insert(root, 20);
insert(root, 40);
insert(root, 70);
insert(root, 60);
insert(root, 80);
printf("preorder:\n");
preorder(root);
struct node* parent = get(root, 30);
struct node* child = get(root, 40);
bool ancestor = is_ancestor(parent, child);
printf("\n %d is a child or %d: %d\n", parent->key, child->key, ancestor);
return 0;
}
Выход:
preorder:
k: 50 lineage: 1
k: 30 lineage: 2
k: 20 lineage: 4
k: 40 lineage: 5
k: 70 lineage: 3
k: 60 lineage: 6
k: 80 lineage: 7
30 is a child or 40: 1
Если это имеет смысл, я могу дать вам полный код, tree.c
, чтобы вы могли попробовать его самостоятельно. Это просто идея, которую я попробовал на маленьком дереве. Извините за длинное объяснение, но меня тоже интересовала проблема.