Узнайте, является ли узел предком другого узла в двоичном дереве - PullRequest
0 голосов
/ 26 апреля 2018

Мне нужно добавить постоянное количество полей для каждого узла в дереве, чтобы при задании двух узлов x, y мне нужно было выяснить, является ли x предком y сложности O(1) .

Моей первой мыслью было добавить поле глубины для каждого узла. Тогда я могу прямо исключить запросы, если x.depth >= y.depth, но этого явно недостаточно ...

1 Ответ

0 голосов
/ 27 апреля 2018

Допустим, вы строите дерево:

              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, чтобы вы могли попробовать его самостоятельно. Это просто идея, которую я попробовал на маленьком дереве. Извините за длинное объяснение, но меня тоже интересовала проблема.

...