【问题标题】:Creating a Binary Search Tree node with Parent node使用父节点创建二叉搜索树节点
【发布时间】:2021-03-15 00:27:50
【问题描述】:

我的 BST 定义如下:

typedef struct trnode {
    Item item;
    struct trnode *left;
    struct trnode *right;
} Trnode;

typedef struct tree {
    Trnode *root;
    size_t size;
} Tree;

我经常遇到的问题是我想知道特定树节点的父节点是什么。定义一个包含父节点的树节点是否很常见,例如:

typedef struct trnode {
    Item item;
    struct trnode *parent;
    struct trnode *left;
    struct trnode *right;
} Trnode;

或者包含了不应该做的事情,如果是这样:为什么不呢?


更新:有人要求查看我的删除代码。这是未经测试的,对我来说很难写(我是 C 的初学者,也只是学习 BST 数据结构)。无论如何,这里是:

Node * SeekInsertionParent(const Node *root, const Node *pnode)
{
    // cmp returns -1 (less than), +1 (greater than), or 0 (equal)
    int cmp = CmpNodes(pnode, root);
    if (cmp == 0) return NULL; // ignore this for now
    Node *child = (cmp < 0)? root->left : root->right;
    if (child == NULL)
        return root;
    else
        SeekInsertionParent(child, pnode);
}
Bool DeleteItem(Tree *ptree, const Item *pi)
{
    Node *parent;
    Node *node = SeekItem(pi, ptree);
    if (node == NULL)
        return false;

    // (1) if no children, then it's a leaf, just delete it
    if (!node->left && !node->right) {
        free(node);
        return true;
    }

    // (2) if it has one child, link that child to the parent then free the node
    if (!node->left || !node->right) {
        Node *descendant = (node->left)? node->left : node->right;
        descendant->parent = parent;
        if (parent->left == node)
            parent->left = descendant;
        else
            parent->right = descendant;
        free(node);
    }

    // (3) if it has two children, then:
    //     (a) attach the child same-side child to the parent node;
    //     (b) using the root of the attachment, find the place to insert the other-side child
    else {
        Node *insert_at, *other_side;
        if (parent->left == node) {
            node->left->parent = parent;
            parent->left = node->left;
            other_side = node->right;
        } else {
            node->right->parent = parent;
            parent->right = node->right;
            other_side = node->left;
        }

        free(node);
        insert_at = SeekInsertionParent(parent, other_node);

        if (insert_at->left == NULL) {
            insert_at->left=node;
            node->parent=insert_at;
        } else {
            insert_at->right=node;
            node->parent=insert_at;
        }
    }
    return true;
}

【问题讨论】:

  • 您可能想阅读我之前问过的stackoverflow.com/questions/41421881/…
  • 不存储父指针。维护它需要大量工作,而且很少值得。 [另外:根节点的括号指针应该是什么?]
  • 不要这样做,因为你会导致 O(n) 内存过剩。
  • @carl.hiass 指针占用了保存地址所需的空间,在 32 位机器上为 4 个字节,在 64 位机器上为 8 个字节。这意味着如果添加父指针,在 32 位机器上将有 4*n 字节的内存盈余,在 64 位机器上将有 8*n 字节的内存盈余。

标签: c binary-search-tree


【解决方案1】:

正如所承诺的,简化版本,使用指针到指针。

重点是:你不需要维护父指针,因为它总是可以计算的。我创建了两个辅助函数来获取指向指向给定节点(或值)的指针的指针。插入函数中也可以使用相同的辅助函数。


#if 0
#include <stdlib.h>
#include <stdbool.h>
#define Bool bool
#else
typedef enum bool{ false, true} Bool;
void free(void*);
#define NULL (void*) 0
#endif

typedef struct node {
    int item;
    struct node *left;
    struct node *right;
} Node;


        // Helper function to find the pointer that points to the node containing item
static Node **node_seek_parent_pp_value(Node **pp, int item)
{
    // cmp returns -1 (less than), +1 (greater than), or 0 (equal)
    while (*pp) {
        int cmp;
        cmp = (*pp)->item == item ? 0 : (*pp)->item < item ? 1 : -1;
        if (cmp == 0) break; // Found!
        pp = (cmp < 0)? &(*pp)->left : &(*pp)->right;
        }

   return pp;
}

        // Same helper function, but with a pointer to item argument
static Node **node_seek_parent_pp_ptr(Node **pp, Node *pnode)
{
if (!pnode) return NULL;
return node_seek_parent_pp_value(pp, pnode->item);
}

Bool DeleteItem(Node **pp, int item)
{
    Node *del;

    pp = node_seek_parent_pp_value(pp, item);
    if (!pp || !*pp) return false; // Not found

    // (1) if fewer than two children
    if (!(*pp)->left || !(*pp)->right) {
        del = *pp;
        *pp = del->left ? del->left : del->right;
    }

    // (2) if it has two children, then:
    //     (a) detach one child subtree
    //     (b) insert it onto the other child
    //     (c) put this other child in place of the node to be deleted.
    else {
        Node **target, *orphan;
        del = *pp;
        orphan = del->right;
        target = node_seek_parent_pp_ptr(&del->left, orphan);
        if (!target) { // should not happen ...
            return false;
            }
        *target = orphan;
        *pp = del->left;
        }

    free(del);
    return true;
}

Bool InsertNode(Node **pp, Node * this)
{
pp = node_seek_parent_pp_ptr(pp, this);
if (!pp || *pp) return false; // this is NULL, or already existing
*pp = this; // insert
return true; // Success!
}

【讨论】:

    【解决方案2】:

    通过添加父节点,您将添加 O(n) 内存,这不是您想要做的,因为大多数时候您的算法将在 O(logN) 中运行。

    如果你真的想实现它,你可以简单地找到double LinkedList的模型并复制它来构建BST with parent。

    请注意,您可以从 XOR Linkedlist 获得灵感,以潜在地解决内存过剩问题:

    Trnode(current) = Trnode(parent)^Trnode(current->left ^ current->left)
    Trnode(current->left) = Trnode(current)^Trnode(current->left->left^current->left->right)
    

    这真的很值得,特别是如果你不需要改变树:

    • 从树中删除仅知道其地址的 Trnode 或
    • 当只知道现有节点的地址时,在现有节点之前或之后插入新节点。

    【讨论】:

      【解决方案3】:

      您可能想阅读this 以了解如何在不使用节点表示中的parent 指针的情况下完成删除。在CLRS,给出了

      我们可以用一个链接的数据结构来表示这样一个二叉搜索树,其中 每个节点都是一个对象。除了密钥和卫星数据外,每个节点还包含 属性leftrightp 分别指向其左子节点、右子节点和父节点对应的节点。如果缺少子项或父项,则相应的属性包含值NIL。根节点是唯一的节点 父级为NIL的树

      他们使用parent 指针,因为它有助于实现deletefind-next-largerfind-next-smaller 等过程。在您的代码中使用它不是问题,但它会花费O(n) 额外的内存空间.请记住,如果您不在节点表示中使用parent 指针,那么当您需要需要某些节点的父级的过程时,您需要实现单独的函数,该函数将tree_node 作为参数并返回tree_node 的父级.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多