二叉搜索树(Java)

1、二叉搜索树的来历:array查询方便,但添加元素比较麻烦(元素个数超过容量需要扩容)。linked list方便添加元素,但是查询比较困难(只能从头一个个往下找)。因此发明了二叉搜索树这种数据结构,它是一种linked list结构,但集合了array和linked list的优点:方便查询;方便添加元素。

2、二叉搜索树的结构:就是一种linked list结构(这种class的filed里面至少包含一个reference能够指向未来的其他object地址),里面含2个reference(可以分别叫做left和right),一个可以比较大小的Key,一个值Value,以及整数N(表示该节点下面所连接的所有节点个数,包括该节点本身)。

3、二叉搜索树的特点:对于任意一个节点,它的Key值一定大于left指向节点的Key值,小于right指向节点的Key值。


具体如下:

public class BST<Value, Key extends Comparable<Key>>
{
    //最头上的reference需要记一下。
    Node root;

    //Node结构,包含两个reference,整数N,以及Key和Value。
    private Node
    {
        Node left, right;
        int N;
        Key key;
        Value val;
        Node(Key key, Value val)
        {
            this.key = key;
            this.val = val;
            N = 1;
        }
    }

    
    //显示整个二叉树所有元素的个数
    public int size() { return size(root); }

    //以x为首的二叉树所有元素的个数
    private int size(Node x)
    {
        if(x == null) return 0;
        else return x.N;
    }

    public Value min() { return min(root).val; }

    private Node min(Node x) 
    {
        if(x.left == null) return x;
        else return x.left;
    }

    public void deleteMin() { root = deleteMin(root); }

    //循环找到一个节点,该节点的左侧为空,即最左侧节点。直接返回它的右侧。
    private Node deelteMin(Node x)
    {
        if(x.left == null) return x.right;
        else return deleteMin(x.left);
    }

    //暴露给客户端的方法。由于此操作要改变原来的结构,需要返回reference,与上一级产生关联。
    public void addElement(Key key, Value val) { root = addElement(key, val, root); }

    /**
      1、如果用void的话,相当于可以找到需要插入元素的位置并添加一个新的元素,但是无法与上一级
         元素产生关联,等于做无用功。
      2、插入的新元素要么覆写已经存在的元素,要么插入到二叉树结构的最底层。
      3、两个级别的分支判断:第一级两个分支,判断是否为null;第二级别在非null的基础上进行key
         值的比较,又开辟3个小分支。
         注意return需要写在第一级别层面。
      4、最底层返回的是新创建元素(可能是已存在元素)的reference,然后可能将此reference关联给
         上一级元素的left或right,然后而可能再将这个上一级元素的reference返回上上级元素的left
         或right(它们早已相关联,看上去这次操作似乎没意义,但必须再执行一次)。
      5、addElement方法返回的是某一级别的reference(第4条)。
    */
    private Node addElement(Key key, Value val, Node x)
    {
        //如果本级节点为空,相当于找到了应该插入的位置,故应创建一个新元素,并返回该元素的reference
        //给上一级,供其产生关联。若root本身是个空元素,那么就直接将新元素的reference传给root。但一
        //般情况下,该reference供给上一级的right或left。
        //第一级别第一分支,要有return。
        if(x == null) return new Node(key, val);
        //如果本级节点不为空,那么需要对key值进行比较,判断该元素的位置是不是本级节点,还是在本级节点
        //的左边或右边。
        //第一级别第二分支,下面开始第二级别的3个小分支。
        int cmp = key.compareTo(x.key);
        //如果要插入的元素就是本级节点,执行覆写操作。
        if(cmp == 0) x.val = val;
        //如果要插入元素的key值比较大,说明它的位置在本级节点的右侧。x的right向右侧关联。本级
        //addElement(key, val, x.right)所返回的要么是个已经关联的元素的reference,要么就是
        //新元素的reference。
        else if(cmp > 0) x.right = addElement(key, val, x.right);
        //向左侧关联。
        else x.left = addElement(key, val, x.left);
        //第一级别第二分支需要对N进行更新。不能直接用x.left.N,排除null错误。
        //如果本层节点不为空,那么它的N值必会增加(或不变),必须进行更新。
        x.N = size(x.left) + size(x.right) + 1;
        //第一级别第二分支进展至此,本级left或right关联结束,N也更新完成,return本级reference本身,
        //返回上一级。
        return x;
    }
        
    //删除任意元素必然从root入手。
    //同添加元素一样,都改变了原有数据结构,要return回reference供给上一级进行关联。否则做无用功(void)。
    public void deleteElement(Key key) { root = deleteElement(key, root); }

    /**
      删除元素,就是将所要删除元素的reference重新赋值给合适的“新”元素(该元素已存在于二叉树中,只不过要
      换位置),更新一下N值,再返回这个reference给上一级关联。
      一共就3种情况:
      1、要删除的元素下面没有其他子元素。
      2、要删除的元素下面只有一个子元素。
      3、要删除的元素下面有两个子元素。
      更新原则是:
      1、换成null。
      2、换成这个子元素。
      3、换成以右侧子元素为首二叉树的最元素,也就是所有子元素中刚刚比本级元素大的数。
    */
    private Node deleteElement(Key key, Node x)
    {
        //若本级为空:要么没找到要删除的元素,返回null给上一级;要么root本身就是空元素,自然返回null。
        //一级分支第一种情况。
        if(x == null) return null;
        //不为空是一级分支第二种情况。此时比较key值。
        int cmp = key.compareTo(x.key);
        //若比较小,说明要删除的元素在右侧。右侧返回可能为一个已经关联的元素的reference,也可能是null或换了位置的
        //“新”元素。
        if(cmp > 0) x.right = deleteElement(key, x.right);
        else if(cmp < 0) x.left = deleteElement(key, x,left);
        else
            {
                //右侧为空,左侧可能为空或不为空,对应1.2条,返回x.left就对了。
                if(x.right == null) return x.left;
                if(x.left== null) return x.right;
                //x一会儿要赋给别的元素,先用个t暂时保存一下此时的x。
                Node t = x;
                //现在x是右侧最小值。
                x = min(x.right);
                //右侧二叉树先把最小元素抠掉,再把头上的元素作为x的right。
                x.right = deleteMin(x.right);
                //x的left还是原来的left,在t中保存着。
                x.left = t.left;
            }
        x.N = size(x.left) + size(x.right) + 1; 
        return x;            
    }
}

猜你喜欢

转载自blog.csdn.net/Anonymous2011/article/details/80852016