【问题标题】:Find out if a node is an ancestor of another node in a binary tree找出一个节点是否是二叉树中另一个节点的祖先
【发布时间】:2018-10-07 09:34:07
【问题描述】:

我需要向树中的每个节点添加恒定数量的字段,这样当给定两个节点x, y 时,我需要找出x 是否是yO(1) 复杂度中的祖先.

我的第一个想法是为每个节点添加一个“深度”字段。然后我可以直接消除x.depth >= y.depth 的查询,但这显然还不够……

【问题讨论】:

    标签: data-structures time-complexity binary-tree


    【解决方案1】:

    假设你建了一棵树:

                  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 和 0,我们都知道对于一个节点是祖先还是节点,二进制血统(或 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() in O(1) 来自:What's the quickest way to compute log2 of an integer in 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,你可以自己试试。这只是我在小树上尝试的一个想法。抱歉,解释太长了,但我也对这个问题感兴趣。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 2021-09-07
      • 1970-01-01
      • 2015-08-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多