二进制搜索树中的Sentinel节点

时间:2015-04-29 04:01:19

标签: algorithm data-structures binary-search-tree

我想知道在二进制搜索树中是否可以使用某种前哨根节点来避免必须处理root作为特殊情况?

public void insert(int value) {
    if (root == null) {
        root = new Node(value);
        ++size;
    } else {
        Node node = root;
        while (true) {
            if (value < node.value) {
                if (node.left == null) {
                    node.left = new Node(value);
                    ++size;
                    return;
                } else {
                    node = node.left;
                }
            } else if (value > node.value) {
                if (node.right == null) {
                    node.right = new Node(value);
                    ++size;
                    return;
                } else {
                    node = node.right;
                }
            } else return;
        }
    }
}

例如,在insert()操作中,我必须以特殊方式处理root节点。在delete()操作中会发生同样的情况,事实上,情况会更糟。

我已经考虑过这个问题,但我无法找到任何好的解决方案。是因为它根本不可能还是我错过了什么?

2 个答案:

答案 0 :(得分:1)

null节点本身是sentinel,但是您可以使用具有特殊标志(或特殊子类)的Node实例,而不是使用null,这实际上是空节点。 Nil节点是有意义的,因为它实际上是一个有效的树:空!

通过使用递归,您可以摆脱额外的检查,并且new Node遍布各处(这是我认为真正困扰你的事情)。

这样的事情:

class Node {
  private Value v;
  private boolean is_nil;
  private Node left;
  private Node right;

  public void insert(Value v) {
    if (this.is_nil) {
      this.left = new Node(); // Nil node
      this.right = new Node(); // Nil node
      this.v = v;
      this.is_nil = false;
      return;
    }
    if (v > this.v) {
      this.right.insert(v);
    } else {
      this.left.insert(v);
    }
  }
}

class Tree {
  private Node root;
  public Tree() {
    root = new Node(); // Nil Node.
  }
  public void insert(Value v) {
    root.insert(v);
  }
}

如果您不想使用递归,则while(true)会产生代码异味。

假设我们将其保持为null,我们可以将其重构为。

public void insert(Value v) {
  prev = null;
  current = this.root;
  boolean left_child = false;
  while (current != null) {
    prev = current;
    if (v > current.v) {
      current = current.right;
      left_child = false;
    } else {
      current = current.left;
      left_child = true;
    }
  }
  current = new Node(v);
  if (prev == null) {
    this.root = current;
    return;
  }
  if (left_child) {
    prev.left = current;
  } else {
    prev.right = current;
  }
}

答案 1 :(得分:0)

root 总是是一个特例。根是二叉搜索树的入口点。

插入sentinel根节点意味着您将拥有与树同时构建的根节点。此外,您所说的哨兵只会减少树的平衡(BST将始终位于其根节点的右侧/左侧)。

在插入/删除期间,我想到的唯一一种不管理根节点作为特殊情况的方法是添加空叶节点。通过这种方式,您永远不会有空树,而是具有空节点的树。

insert()期间,您只需用非空节点和两个新空叶(左侧和右侧)替换空叶节点。

delete()期间,作为最后一步(如果在here中实现此类操作),您只需清空节点(它变为空叶)并修剪其现有叶子。

请记住,如果以这种方式实现它,空叶节点占用的空间比具有有意义数据的节点占用的空间多。因此,只有空间不是问题时,这种实现才有意义。

代码看起来像这样:

public class BST {
    private Node root;

    public BST(){
        root = new Node();
    }

    public void insert(int elem){
        root.insert(elem);
    }

    public void delete(int elem){
        root.delete(elem);
    }
}

public class Node{
    private static final int EMPTY_VALUE = /* your empty value */;
    private int element;
    private Node parent;
    private Node left;
    private Node right;

    public Node(){
        this(EMPTY_VALUE, null, null, null);
    }

    public Node(int elem, Node p, Node l, Node r){
        element = elem;
        parent = p;
        left = l;
        right = r;
    }

    public void insert(int elem){
        Node thisNode = this;

        // this cycle goes on until an empty node is found
        while(thisNode.element != EMPTY_VALUE){
            // follow the correct path for the insertion here
        }

        // insert new element here
        // thisNode is an empty node at this point
        thisNode.element = elem;
        thisNode.left = new Node();
        thisNode.right = new Node();
        thisNode.left.parent = thisNode;
        thisNode.right.parent = thisNode;
    }

    public void delete(int elem){
        // manage delete here
    }
}
相关问题